Repository: olivere/elastic
Branch: release-branch.v7
Commit: 4cdb89f6e627
Files: 646
Total size: 2.9 MB
Directory structure:
gitextract_wuqpv25u/
├── .github/
│ └── workflows/
│ ├── codeql-v7.yml
│ └── test-v7.yml
├── .gitignore
├── CHANGELOG-3.0.md
├── CHANGELOG-5.0.md
├── CHANGELOG-6.0.md
├── CHANGELOG-7.0.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── CONTRIBUTORS
├── ISSUE_TEMPLATE.md
├── LICENSE
├── Makefile
├── README.md
├── acknowledged_response.go
├── aws/
│ ├── sign_v4.go
│ ├── sign_v4_test.go
│ └── v4/
│ ├── CREDITS
│ ├── aws_v4.go
│ └── aws_v4_test.go
├── backoff.go
├── backoff_test.go
├── bulk.go
├── bulk_create_request.go
├── bulk_create_request_easyjson.go
├── bulk_create_request_test.go
├── bulk_delete_request.go
├── bulk_delete_request_easyjson.go
├── bulk_delete_request_test.go
├── bulk_index_request.go
├── bulk_index_request_easyjson.go
├── bulk_index_request_test.go
├── bulk_processor.go
├── bulk_processor_test.go
├── bulk_request.go
├── bulk_test.go
├── bulk_update_request.go
├── bulk_update_request_easyjson.go
├── bulk_update_request_test.go
├── canonicalize.go
├── canonicalize_test.go
├── cat_aliases.go
├── cat_aliases_test.go
├── cat_allocation.go
├── cat_allocation_test.go
├── cat_count.go
├── cat_count_integration_test.go
├── cat_count_test.go
├── cat_fielddata.go
├── cat_fielddata_test.go
├── cat_health.go
├── cat_health_test.go
├── cat_indices.go
├── cat_indices_test.go
├── cat_master.go
├── cat_master_test.go
├── cat_shards.go
├── cat_shards_test.go
├── cat_snapshots.go
├── cat_snapshots_integration_test.go
├── cat_snapshots_test.go
├── clear_scroll.go
├── clear_scroll_test.go
├── client.go
├── client_test.go
├── cluster-test/
│ ├── Makefile
│ ├── README.md
│ └── cluster-test.go
├── cluster_health.go
├── cluster_health_test.go
├── cluster_reroute.go
├── cluster_reroute_test.go
├── cluster_state.go
├── cluster_state_test.go
├── cluster_stats.go
├── cluster_stats_integration_test.go
├── cluster_stats_test.go
├── config/
│ ├── config.go
│ ├── config_test.go
│ └── doc.go
├── connection.go
├── count.go
├── count_test.go
├── decoder.go
├── decoder_test.go
├── delete.go
├── delete_by_query.go
├── delete_by_query_test.go
├── delete_test.go
├── doc.go
├── docker-compose.cluster.yml
├── docker-compose.yml
├── docvalue_field.go
├── docvalue_field_test.go
├── errors.go
├── errors_test.go
├── example_test.go
├── exists.go
├── exists_test.go
├── explain.go
├── explain_test.go
├── fetch_source_context.go
├── fetch_source_context_test.go
├── field_caps.go
├── field_caps_test.go
├── geo_point.go
├── geo_point_test.go
├── get.go
├── get_test.go
├── go.mod
├── highlight.go
├── highlight_test.go
├── index.go
├── index_test.go
├── indices_analyze.go
├── indices_analyze_test.go
├── indices_clear_cache.go
├── indices_clear_cache_test.go
├── indices_close.go
├── indices_close_test.go
├── indices_component_templates_test.go
├── indices_create.go
├── indices_create_test.go
├── indices_delete.go
├── indices_delete_component_template.go
├── indices_delete_index_template.go
├── indices_delete_integration_test.go
├── indices_delete_template.go
├── indices_delete_test.go
├── indices_exists.go
├── indices_exists_template.go
├── indices_exists_template_test.go
├── indices_exists_test.go
├── indices_flush.go
├── indices_flush_synced.go
├── indices_flush_synced_test.go
├── indices_flush_test.go
├── indices_forcemerge.go
├── indices_forcemerge_test.go
├── indices_freeze.go
├── indices_freeze_test.go
├── indices_get.go
├── indices_get_aliases.go
├── indices_get_aliases_test.go
├── indices_get_component_template.go
├── indices_get_field_mapping.go
├── indices_get_field_mapping_test.go
├── indices_get_index_template.go
├── indices_get_index_template_test.go
├── indices_get_mapping.go
├── indices_get_mapping_test.go
├── indices_get_settings.go
├── indices_get_settings_test.go
├── indices_get_template.go
├── indices_get_template_test.go
├── indices_get_test.go
├── indices_index_templates_test.go
├── indices_open.go
├── indices_open_test.go
├── indices_put_alias.go
├── indices_put_alias_test.go
├── indices_put_component_template.go
├── indices_put_index_template.go
├── indices_put_mapping.go
├── indices_put_mapping_test.go
├── indices_put_settings.go
├── indices_put_settings_test.go
├── indices_put_template.go
├── indices_refresh.go
├── indices_refresh_test.go
├── indices_rollover.go
├── indices_rollover_test.go
├── indices_segments.go
├── indices_segments_test.go
├── indices_shrink.go
├── indices_shrink_test.go
├── indices_stats.go
├── indices_stats_test.go
├── indices_unfreeze.go
├── indices_unfreeze_test.go
├── ingest_delete_pipeline.go
├── ingest_delete_pipeline_test.go
├── ingest_get_pipeline.go
├── ingest_get_pipeline_test.go
├── ingest_put_pipeline.go
├── ingest_put_pipeline_test.go
├── ingest_simulate_pipeline.go
├── ingest_simulate_pipeline_test.go
├── inner_hit.go
├── inner_hit_test.go
├── logger.go
├── mget.go
├── mget_test.go
├── msearch.go
├── msearch_test.go
├── mtermvectors.go
├── mtermvectors_test.go
├── nodes_info.go
├── nodes_info_test.go
├── nodes_stats.go
├── nodes_stats_test.go
├── percolate_test.go
├── ping.go
├── ping_test.go
├── pit.go
├── pit_close.go
├── pit_open.go
├── pit_test.go
├── plugins.go
├── plugins_test.go
├── query.go
├── recipes/
│ ├── aws-connect/
│ │ ├── .gitignore
│ │ └── main.go
│ ├── aws-connect-v4/
│ │ ├── .gitignore
│ │ └── main.go
│ ├── aws-mapping-v4/
│ │ ├── .gitignore
│ │ └── main.go
│ ├── bulk_insert/
│ │ ├── .gitignore
│ │ └── bulk_insert.go
│ ├── bulk_processor/
│ │ ├── .gitignore
│ │ └── main.go
│ ├── connect/
│ │ ├── .gitignore
│ │ └── connect.go
│ ├── connect_with_config/
│ │ ├── .gitignore
│ │ └── connect_with_config.go
│ ├── go.mod
│ ├── go.sum
│ ├── mapping/
│ │ ├── .gitignore
│ │ └── mapping.go
│ ├── middleware/
│ │ ├── .gitignore
│ │ └── main.go
│ ├── scroll/
│ │ ├── .gitignore
│ │ └── scroll.go
│ ├── search_with_point_in_time/
│ │ ├── .gitignore
│ │ └── search.go
│ ├── sliced_scroll/
│ │ ├── .gitignore
│ │ └── sliced_scroll.go
│ ├── suggesters/
│ │ └── completion/
│ │ ├── .gitignore
│ │ └── main.go
│ ├── text_vs_keyword/
│ │ ├── .gitignore
│ │ └── main.go
│ └── tracing/
│ ├── .gitignore
│ ├── README.md
│ ├── otel/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── run-tracer.sh
│ │ └── tracing.go
│ ├── run-tracer.sh
│ └── tracing.go
├── reindex.go
├── reindex_test.go
├── request.go
├── request_test.go
├── rescore.go
├── rescorer.go
├── response.go
├── response_test.go
├── retrier.go
├── retrier_test.go
├── retry.go
├── retry_test.go
├── runtime_mappings.go
├── runtime_mappings_test.go
├── script.go
├── script_delete.go
├── script_delete_test.go
├── script_get.go
├── script_get_test.go
├── script_put.go
├── script_put_test.go
├── script_test.go
├── scroll.go
├── scroll_test.go
├── search.go
├── search_aggs.go
├── search_aggs_bucket_adjacency_matrix.go
├── search_aggs_bucket_adjacency_matrix_test.go
├── search_aggs_bucket_auto_date_histogram.go
├── search_aggs_bucket_auto_date_histogram_test.go
├── search_aggs_bucket_children.go
├── search_aggs_bucket_children_test.go
├── search_aggs_bucket_composite.go
├── search_aggs_bucket_composite_test.go
├── search_aggs_bucket_count_thresholds.go
├── search_aggs_bucket_date_histogram.go
├── search_aggs_bucket_date_histogram_test.go
├── search_aggs_bucket_date_range.go
├── search_aggs_bucket_date_range_test.go
├── search_aggs_bucket_diversified_sampler.go
├── search_aggs_bucket_diversified_sampler_test.go
├── search_aggs_bucket_filter.go
├── search_aggs_bucket_filter_test.go
├── search_aggs_bucket_filters.go
├── search_aggs_bucket_filters_test.go
├── search_aggs_bucket_geo_distance.go
├── search_aggs_bucket_geo_distance_test.go
├── search_aggs_bucket_geohash_grid.go
├── search_aggs_bucket_geohash_grid_test.go
├── search_aggs_bucket_geotile_grid.go
├── search_aggs_bucket_geotile_grid_test.go
├── search_aggs_bucket_global.go
├── search_aggs_bucket_global_test.go
├── search_aggs_bucket_histogram.go
├── search_aggs_bucket_histogram_test.go
├── search_aggs_bucket_ip_range.go
├── search_aggs_bucket_ip_range_test.go
├── search_aggs_bucket_missing.go
├── search_aggs_bucket_missing_test.go
├── search_aggs_bucket_multi_terms.go
├── search_aggs_bucket_multi_terms_test.go
├── search_aggs_bucket_nested.go
├── search_aggs_bucket_nested_test.go
├── search_aggs_bucket_range.go
├── search_aggs_bucket_range_test.go
├── search_aggs_bucket_rare_terms.go
├── search_aggs_bucket_rare_terms_test.go
├── search_aggs_bucket_reverse_nested.go
├── search_aggs_bucket_reverse_nested_test.go
├── search_aggs_bucket_sampler.go
├── search_aggs_bucket_sampler_test.go
├── search_aggs_bucket_significant_terms.go
├── search_aggs_bucket_significant_terms_test.go
├── search_aggs_bucket_significant_text.go
├── search_aggs_bucket_significant_text_test.go
├── search_aggs_bucket_terms.go
├── search_aggs_bucket_terms_integration_test.go
├── search_aggs_bucket_terms_test.go
├── search_aggs_matrix_stats.go
├── search_aggs_matrix_stats_test.go
├── search_aggs_metrics_avg.go
├── search_aggs_metrics_avg_test.go
├── search_aggs_metrics_cardinality.go
├── search_aggs_metrics_cardinality_test.go
├── search_aggs_metrics_extended_stats.go
├── search_aggs_metrics_extended_stats_test.go
├── search_aggs_metrics_geo_bounds.go
├── search_aggs_metrics_geo_bounds_test.go
├── search_aggs_metrics_geo_centroid.go
├── search_aggs_metrics_geo_centroid_test.go
├── search_aggs_metrics_max.go
├── search_aggs_metrics_max_test.go
├── search_aggs_metrics_median_absolute_deviation.go
├── search_aggs_metrics_median_absolute_deviation_test.go
├── search_aggs_metrics_min.go
├── search_aggs_metrics_min_test.go
├── search_aggs_metrics_percentile_ranks.go
├── search_aggs_metrics_percentile_ranks_test.go
├── search_aggs_metrics_percentiles.go
├── search_aggs_metrics_percentiles_test.go
├── search_aggs_metrics_scripted_metric.go
├── search_aggs_metrics_scripted_metric_test.go
├── search_aggs_metrics_stats.go
├── search_aggs_metrics_stats_test.go
├── search_aggs_metrics_sum.go
├── search_aggs_metrics_sum_test.go
├── search_aggs_metrics_top_hits.go
├── search_aggs_metrics_top_hits_test.go
├── search_aggs_metrics_top_metrics.go
├── search_aggs_metrics_top_metrics_test.go
├── search_aggs_metrics_value_count.go
├── search_aggs_metrics_value_count_test.go
├── search_aggs_metrics_weighted_avg.go
├── search_aggs_metrics_weighted_avg_test.go
├── search_aggs_pipeline_avg_bucket.go
├── search_aggs_pipeline_avg_bucket_test.go
├── search_aggs_pipeline_bucket_script.go
├── search_aggs_pipeline_bucket_script_test.go
├── search_aggs_pipeline_bucket_selector.go
├── search_aggs_pipeline_bucket_selector_test.go
├── search_aggs_pipeline_bucket_sort.go
├── search_aggs_pipeline_bucket_sort_test.go
├── search_aggs_pipeline_cumulative_sum.go
├── search_aggs_pipeline_cumulative_sum_test.go
├── search_aggs_pipeline_derivative.go
├── search_aggs_pipeline_derivative_test.go
├── search_aggs_pipeline_extended_stats_bucket.go
├── search_aggs_pipeline_extended_stats_bucket_test.go
├── search_aggs_pipeline_max_bucket.go
├── search_aggs_pipeline_max_bucket_test.go
├── search_aggs_pipeline_min_bucket.go
├── search_aggs_pipeline_min_bucket_test.go
├── search_aggs_pipeline_mov_avg.go
├── search_aggs_pipeline_mov_avg_test.go
├── search_aggs_pipeline_mov_fn.go
├── search_aggs_pipeline_mov_fn_test.go
├── search_aggs_pipeline_percentiles_bucket.go
├── search_aggs_pipeline_percentiles_bucket_test.go
├── search_aggs_pipeline_serial_diff.go
├── search_aggs_pipeline_serial_diff_test.go
├── search_aggs_pipeline_stats_bucket.go
├── search_aggs_pipeline_stats_bucket_test.go
├── search_aggs_pipeline_sum_bucket.go
├── search_aggs_pipeline_sum_bucket_test.go
├── search_aggs_pipeline_test.go
├── search_aggs_test.go
├── search_collapse_builder.go
├── search_collapse_builder_test.go
├── search_queries_bool.go
├── search_queries_bool_test.go
├── search_queries_boosting.go
├── search_queries_boosting_test.go
├── search_queries_combined_fields.go
├── search_queries_combined_fields_test.go
├── search_queries_common_terms.go
├── search_queries_common_terms_test.go
├── search_queries_constant_score.go
├── search_queries_constant_score_test.go
├── search_queries_dis_max.go
├── search_queries_dis_max_test.go
├── search_queries_distance_feature_query.go
├── search_queries_distance_feature_query_test.go
├── search_queries_exists.go
├── search_queries_exists_test.go
├── search_queries_fsq.go
├── search_queries_fsq_score_funcs.go
├── search_queries_fsq_test.go
├── search_queries_fuzzy.go
├── search_queries_fuzzy_test.go
├── search_queries_geo_bounding_box.go
├── search_queries_geo_bounding_box_test.go
├── search_queries_geo_distance.go
├── search_queries_geo_distance_test.go
├── search_queries_geo_polygon.go
├── search_queries_geo_polygon_test.go
├── search_queries_has_child.go
├── search_queries_has_child_test.go
├── search_queries_has_parent.go
├── search_queries_has_parent_test.go
├── search_queries_ids.go
├── search_queries_ids_test.go
├── search_queries_interval.go
├── search_queries_interval_filter.go
├── search_queries_interval_integration_test.go
├── search_queries_interval_rules_all_of.go
├── search_queries_interval_rules_any_of.go
├── search_queries_interval_rules_fuzzy.go
├── search_queries_interval_rules_match.go
├── search_queries_interval_rules_prefix.go
├── search_queries_interval_rules_wildcard.go
├── search_queries_interval_test.go
├── search_queries_match.go
├── search_queries_match_all.go
├── search_queries_match_all_test.go
├── search_queries_match_bool_prefix.go
├── search_queries_match_bool_prefix_test.go
├── search_queries_match_none.go
├── search_queries_match_none_test.go
├── search_queries_match_phrase.go
├── search_queries_match_phrase_prefix.go
├── search_queries_match_phrase_prefix_test.go
├── search_queries_match_phrase_test.go
├── search_queries_match_test.go
├── search_queries_more_like_this.go
├── search_queries_more_like_this_test.go
├── search_queries_multi_match.go
├── search_queries_multi_match_test.go
├── search_queries_nested.go
├── search_queries_nested_test.go
├── search_queries_parent_id.go
├── search_queries_parent_id_test.go
├── search_queries_percolator.go
├── search_queries_percolator_test.go
├── search_queries_pinned.go
├── search_queries_pinned_test.go
├── search_queries_prefix.go
├── search_queries_prefix_example_test.go
├── search_queries_prefix_test.go
├── search_queries_query_string.go
├── search_queries_query_string_test.go
├── search_queries_range.go
├── search_queries_range_test.go
├── search_queries_rank_feature.go
├── search_queries_rank_feature_test.go
├── search_queries_raw_string.go
├── search_queries_raw_string_test.go
├── search_queries_regexp.go
├── search_queries_regexp_test.go
├── search_queries_script.go
├── search_queries_script_score.go
├── search_queries_script_score_test.go
├── search_queries_script_test.go
├── search_queries_simple_query_string.go
├── search_queries_simple_query_string_test.go
├── search_queries_slice.go
├── search_queries_slice_test.go
├── search_queries_span_first.go
├── search_queries_span_first_integration_test.go
├── search_queries_span_first_test.go
├── search_queries_span_near.go
├── search_queries_span_near_integration_test.go
├── search_queries_span_near_test.go
├── search_queries_span_term.go
├── search_queries_span_term_integration_test.go
├── search_queries_span_term_test.go
├── search_queries_term.go
├── search_queries_term_test.go
├── search_queries_terms.go
├── search_queries_terms_set.go
├── search_queries_terms_set_test.go
├── search_queries_terms_test.go
├── search_queries_type.go
├── search_queries_type_test.go
├── search_queries_wildcard.go
├── search_queries_wildcard_test.go
├── search_queries_wrapper.go
├── search_queries_wrapper_integration_test.go
├── search_queries_wrapper_test.go
├── search_request.go
├── search_request_test.go
├── search_shards.go
├── search_shards_test.go
├── search_source.go
├── search_source_test.go
├── search_suggester_test.go
├── search_terms_lookup.go
├── search_terms_lookup_test.go
├── search_test.go
├── setup_test.go
├── snapshot_create.go
├── snapshot_create_repository.go
├── snapshot_create_repository_test.go
├── snapshot_create_test.go
├── snapshot_delete.go
├── snapshot_delete_repository.go
├── snapshot_delete_repository_test.go
├── snapshot_delete_test.go
├── snapshot_get.go
├── snapshot_get_repository.go
├── snapshot_get_repository_test.go
├── snapshot_get_test.go
├── snapshot_restore.go
├── snapshot_restore_test.go
├── snapshot_status.go
├── snapshot_status_test.go
├── snapshot_verify_repository.go
├── snapshot_verify_repository_test.go
├── sort.go
├── sort_test.go
├── suggest_field.go
├── suggest_field_test.go
├── suggester.go
├── suggester_completion.go
├── suggester_completion_test.go
├── suggester_context.go
├── suggester_context_category.go
├── suggester_context_category_test.go
├── suggester_context_geo.go
├── suggester_context_geo_test.go
├── suggester_context_test.go
├── suggester_phrase.go
├── suggester_phrase_test.go
├── suggester_term.go
├── suggester_term_test.go
├── tasks_cancel.go
├── tasks_cancel_test.go
├── tasks_get_task.go
├── tasks_get_task_test.go
├── tasks_list.go
├── tasks_list_test.go
├── termvectors.go
├── termvectors_test.go
├── trace/
│ ├── opencensus/
│ │ ├── transport.go
│ │ ├── transport_test.go
│ │ └── util.go
│ ├── opentelemetry/
│ │ ├── transport.go
│ │ └── util.go
│ └── opentracing/
│ ├── transport.go
│ ├── transport_integration_test.go
│ ├── transport_test.go
│ └── util.go
├── update.go
├── update_by_query.go
├── update_by_query_test.go
├── update_integration_test.go
├── update_test.go
├── uritemplates/
│ ├── LICENSE
│ ├── uritemplates.go
│ ├── utils.go
│ └── utils_test.go
├── validate.go
├── validate_test.go
├── xpack_async_search_delete.go
├── xpack_async_search_get.go
├── xpack_async_search_submit.go
├── xpack_async_search_test.go
├── xpack_ilm_delete_lifecycle.go
├── xpack_ilm_get_lifecycle.go
├── xpack_ilm_put_lifecycle.go
├── xpack_ilm_test.go
├── xpack_info.go
├── xpack_info_test.go
├── xpack_rollup_delete.go
├── xpack_rollup_delete_test.go
├── xpack_rollup_get.go
├── xpack_rollup_get_test.go
├── xpack_rollup_put.go
├── xpack_rollup_put_test.go
├── xpack_rollup_start.go
├── xpack_rollup_start_test.go
├── xpack_rollup_stop.go
├── xpack_rollup_stop_test.go
├── xpack_security_change_password.go
├── xpack_security_change_password_test.go
├── xpack_security_delete_role.go
├── xpack_security_delete_role_mapping.go
├── xpack_security_delete_role_mapping_test.go
├── xpack_security_delete_role_test.go
├── xpack_security_delete_user.go
├── xpack_security_delete_user_test.go
├── xpack_security_disable_user.go
├── xpack_security_disable_user_test.go
├── xpack_security_enable_user.go
├── xpack_security_enable_user_test.go
├── xpack_security_get_role.go
├── xpack_security_get_role_mapping.go
├── xpack_security_get_role_mapping_test.go
├── xpack_security_get_role_test.go
├── xpack_security_get_user.go
├── xpack_security_get_user_test.go
├── xpack_security_put_role.go
├── xpack_security_put_role_mapping.go
├── xpack_security_put_role_mapping_test.go
├── xpack_security_put_role_test.go
├── xpack_security_put_user.go
├── xpack_security_put_user_test.go
├── xpack_test.go
├── xpack_watcher_ack_watch.go
├── xpack_watcher_ack_watch_test.go
├── xpack_watcher_activate_watch.go
├── xpack_watcher_activate_watch_test.go
├── xpack_watcher_deactivate_watch.go
├── xpack_watcher_deactivate_watch_test.go
├── xpack_watcher_delete_watch.go
├── xpack_watcher_delete_watch_test.go
├── xpack_watcher_execute_watch.go
├── xpack_watcher_execute_watch_test.go
├── xpack_watcher_get_watch.go
├── xpack_watcher_get_watch_test.go
├── xpack_watcher_put_watch.go
├── xpack_watcher_put_watch_test.go
├── xpack_watcher_start.go
├── xpack_watcher_start_test.go
├── xpack_watcher_stats.go
├── xpack_watcher_stats_test.go
├── xpack_watcher_stop.go
└── xpack_watcher_stop_test.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/codeql-v7.yml
================================================
name: "Code scanning - action"
on:
push:
branches:
- '*.v7'
pull_request:
branches:
- '*.v7'
schedule:
- cron: '0 19 * * 3'
jobs:
codeql:
strategy:
matrix:
go: [stable]
os: [ubuntu-latest]
name: Run ${{ matrix.go }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
steps:
- name: Prepare tests
run: |
sudo apt-get install -y netcat
sudo sysctl -w vm.max_map_count=262144
- name: Setup Go ${{ matrix.go }}
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 2
- uses: actions/cache@v4
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: go
- name: Run Docker containers
run: docker compose up -d
- name: Check Docker containers
run: docker ps -a
- name: Get dependencies
run: |
go get -u github.com/google/go-cmp/cmp
go get -u github.com/fortytw2/leaktest
go get . ./aws/... ./config/... ./trace/... ./uritemplates/...
- name: Wait for Elasticsearch
run: |
while ! nc -z localhost 9200; do sleep 1; done
while ! nc -z localhost 9210; do sleep 1; done
sleep 5
- name: Run the tests
run: |
go test -race -deprecations -strict-decoder -v . ./aws/... ./config/... ./trace/... ./uritemplates/...
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
================================================
FILE: .github/workflows/test-v7.yml
================================================
on:
push:
branches:
- '*.v7'
pull_request:
branches:
- '*.v7'
name: Test v7
jobs:
test:
strategy:
matrix:
go: [stable]
os: [ubuntu-latest]
name: Run ${{ matrix.go }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
steps:
- name: Prepare tests
run: |
sudo apt-get install -y netcat
sudo sysctl -w vm.max_map_count=262144
- name: Setup Go ${{ matrix.go }}
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Checkout code
uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Run Docker containers
run: docker compose up -d
- name: Check Docker containers
run: docker ps -a
- name: Get dependencies
run: |
go get -u github.com/google/go-cmp/cmp
go get -u github.com/fortytw2/leaktest
go get . ./aws/... ./config/... ./trace/... ./uritemplates/...
- name: Wait for Elasticsearch
run: |
while ! nc -z localhost 9200; do sleep 1; done
while ! nc -z localhost 9210; do sleep 1; done
- name: Run the tests
run: |
go test -race -deprecations -strict-decoder -v . ./aws/... ./config/... ./trace/... ./uritemplates/...
================================================
FILE: .gitignore
================================================
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
.envrc
/.vscode/
/.idea/
/data
/debug.test
/generator
/cluster-test/cluster-test
/cluster-test/*.log
/cluster-test/es-chaos-monkey
/dist
/go.sum
/spec
/tmp
/CHANGELOG-3.0.html
================================================
FILE: CHANGELOG-3.0.md
================================================
# Elastic 3.0
Elasticsearch 2.0 comes with some [breaking changes](https://www.elastic.co/guide/en/elasticsearch/reference/2.0/breaking-changes-2.0.html). You will probably need to upgrade your application and/or rewrite part of it due to those changes.
We use that window of opportunity to also update Elastic (the Go client) from version 2.0 to 3.0. This will introduce both changes due to the Elasticsearch 2.0 update as well as changes that make Elastic cleaner by removing some old cruft.
So, to summarize:
1. Elastic 2.0 is compatible with Elasticsearch 1.7+ and is still actively maintained.
2. Elastic 3.0 is compatible with Elasticsearch 2.0+ and will soon become the new master branch.
The rest of the document is a list of all changes in Elastic 3.0.
## Pointer types
All types have changed to be pointer types, not value types. This not only is cleaner but also simplifies the API as illustrated by the following example:
Example for Elastic 2.0 (old):
```go
q := elastic.NewMatchAllQuery()
res, err := elastic.Search("one").Query(&q).Do() // notice the & here
```
Example for Elastic 3.0 (new):
```go
q := elastic.NewMatchAllQuery()
res, err := elastic.Search("one").Query(q).Do() // no more &
// ... which can be simplified as:
res, err := elastic.Search("one").Query(elastic.NewMatchAllQuery()).Do()
```
It also helps to prevent [subtle issues](https://github.com/olivere/elastic/issues/115#issuecomment-130753046).
## Query/filter merge
One of the biggest changes in Elasticsearch 2.0 is the [merge of queries and filters](https://www.elastic.co/guide/en/elasticsearch/reference/2.0/_query_dsl_changes.html#_queries_and_filters_merged). In Elasticsearch 1.x, you had a whole range of queries and filters that were basically identical (e.g. `term_query` and `term_filter`).
The practical aspect of the merge is that you can now basically use queries where once you had to use filters instead. For Elastic 3.0 this means: We could remove a whole bunch of files. Yay!
Notice that some methods still come by "filter", e.g. `PostFilter`. However, they accept a `Query` now when they used to accept a `Filter` before.
Example for Elastic 2.0 (old):
```go
q := elastic.NewMatchAllQuery()
f := elastic.NewTermFilter("tag", "important")
res, err := elastic.Search().Index("one").Query(&q).PostFilter(f)
```
Example for Elastic 3.0 (new):
```go
q := elastic.NewMatchAllQuery()
f := elastic.NewTermQuery("tag", "important") // it's a query now!
res, err := elastic.Search().Index("one").Query(q).PostFilter(f)
```
## Facets are removed
[Facets have been removed](https://www.elastic.co/guide/en/elasticsearch/reference/2.0/_removed_features.html#_facets_have_been_removed) in Elasticsearch 2.0. You need to use aggregations now.
## Errors
Elasticsearch 2.0 returns more information about an error in the HTTP response body. Elastic 3.0 now reads this information and makes it accessible by the consumer.
Errors and all its details are now returned in [`Error`](https://github.com/olivere/elastic/blob/release-branch.v3/errors.go#L59).
### HTTP Status 404 (Not Found)
When Elasticsearch does not find an entity or an index, it generally returns HTTP status code 404. In Elastic 2.0 this was a valid result and didn't raise an error from the `Do` functions. This has now changed in Elastic 3.0.
Starting with Elastic 3.0, there are only two types of responses considered successful. First, responses with HTTP status codes [200..299]. Second, HEAD requests which return HTTP status 404. The latter is used by Elasticsearch to e.g. check for existence of indices or documents. All other responses will return an error.
To check for HTTP Status 404 (with non-HEAD requests), e.g. when trying to get or delete a missing document, you can use the [`IsNotFound`](https://github.com/olivere/elastic/blob/release-branch.v3/errors.go#L84) helper (see below).
The following example illustrates how to check for a missing document in Elastic 2.0 and what has changed in 3.0.
Example for Elastic 2.0 (old):
```go
res, err = client.Get().Index("one").Type("tweet").Id("no-such-id").Do()
if err != nil {
// Something else went wrong (but 404 is NOT an error in Elastic 2.0)
}
if !res.Found {
// Document has not been found
}
```
Example for Elastic 3.0 (new):
```go
res, err = client.Get().Index("one").Type("tweet").Id("no-such-id").Do()
if err != nil {
if elastic.IsNotFound(err) {
// Document has not been found
} else {
// Something else went wrong
}
}
```
### HTTP Status 408 (Timeouts)
Elasticsearch now responds with HTTP status code 408 (Timeout) when a request fails due to a timeout. E.g. if you specify a timeout with the Cluster Health API, the HTTP response status will be 408 if the timeout is raised. See [here](https://github.com/elastic/elasticsearch/commit/fe3179d9cccb569784434b2135ca9ae13d5158d3) for the specific commit to the Cluster Health API.
To check for HTTP Status 408, we introduced the [`IsTimeout`](https://github.com/olivere/elastic/blob/release-branch.v3/errors.go#L101) helper.
Example for Elastic 2.0 (old):
```go
health, err := client.ClusterHealth().WaitForStatus("yellow").Timeout("1s").Do()
if err != nil {
// ...
}
if health.TimedOut {
// We have a timeout
}
```
Example for Elastic 3.0 (new):
```go
health, err := client.ClusterHealth().WaitForStatus("yellow").Timeout("1s").Do()
if elastic.IsTimeout(err) {
// We have a timeout
}
```
### Bulk Errors
The error response of a bulk operation used to be a simple string in Elasticsearch 1.x.
In Elasticsearch 2.0, it returns a structured JSON object with a lot more details about the error.
These errors are now captured in an object of type [`ErrorDetails`](https://github.com/olivere/elastic/blob/release-branch.v3/errors.go#L59) which is used in [`BulkResponseItem`](https://github.com/olivere/elastic/blob/release-branch.v3/bulk.go#L206).
### Removed specific Elastic errors
The specific error types `ErrMissingIndex`, `ErrMissingType`, and `ErrMissingId` have been removed. They were only used by `DeleteService` and are replaced by a generic error message.
## Numeric types
Elastic 3.0 has settled to use `float64` everywhere. It used to be a mix of `float32` and `float64` in Elastic 2.0. E.g. all boostable queries in Elastic 3.0 now have a boost type of `float64` where it used to be `float32`.
## Pluralization
Some services accept zero, one or more indices or types to operate on.
E.g. in the `SearchService` accepts a list of zero, one, or more indices to
search and therefor had a func called `Index(index string)` and a func
called `Indices(indices ...string)`.
Elastic 3.0 now only uses the singular form that, when applicable, accepts a
variadic type. E.g. in the case of the `SearchService`, you now only have
one func with the following signature: `Index(indices ...string)`.
Notice this is only limited to `Index(...)` and `Type(...)`. There are other
services with variadic functions. These have not been changed.
## Multiple calls to variadic functions
Some services with variadic functions have cleared the underlying slice when
called while other services just add to the existing slice. This has now been
normalized to always add to the underlying slice.
Example for Elastic 2.0 (old):
```go
// Would only cleared scroll id "two"
// because ScrollId cleared the values when called multiple times
client.ClearScroll().ScrollId("one").ScrollId("two").Do()
```
Example for Elastic 3.0 (new):
```go
// Now (correctly) clears both scroll id "one" and "two"
// because ScrollId no longer clears the values when called multiple times
client.ClearScroll().ScrollId("one").ScrollId("two").Do()
```
## Ping service requires URL
The `Ping` service raised some issues because it is different from all
other services. If not explicitly given a URL, it always pings `127.0.0.1:9200`.
Users expected to ping the cluster, but that is not possible as the cluster
can be a set of many nodes: So which node do we ping then?
To make it more clear, the `Ping` function on the client now requires users
to explicitly set the URL of the node to ping.
## Meta fields
Many of the meta fields e.g. `_parent` or `_routing` are now
[part of the top-level of a document](https://www.elastic.co/guide/en/elasticsearch/reference/2.0/_mapping_changes.html#migration-meta-fields)
and are no longer returned as parts of the `fields` object. We had to change
larger parts of e.g. the `Reindexer` to get it to work seamlessly with Elasticsearch 2.0.
Notice that all stored meta-fields are now [returned by default](https://www.elastic.co/guide/en/elasticsearch/reference/2.0/_crud_and_routing_changes.html#_all_stored_meta_fields_returned_by_default).
## HasParentQuery / HasChildQuery
`NewHasParentQuery` and `NewHasChildQuery` must now include both parent/child type and query. It is now in line with the Java API.
Example for Elastic 2.0 (old):
```go
allQ := elastic.NewMatchAllQuery()
q := elastic.NewHasChildFilter("tweet").Query(&allQ)
```
Example for Elastic 3.0 (new):
```go
q := elastic.NewHasChildQuery("tweet", elastic.NewMatchAllQuery())
```
## SetBasicAuth client option
You can now tell Elastic to pass HTTP Basic Auth credentials with each request. In previous versions of Elastic you had to set up your own `http.Transport` to do this. This should make it more convenient to use Elastic in combination with [Shield](https://www.elastic.co/products/shield) in its [basic setup](https://www.elastic.co/guide/en/shield/current/enable-basic-auth.html).
Example:
```go
client, err := elastic.NewClient(elastic.SetBasicAuth("user", "secret"))
if err != nil {
log.Fatal(err)
}
```
## Delete-by-Query API
The Delete-by-Query API is [a plugin now](https://www.elastic.co/guide/en/elasticsearch/reference/2.0/_removed_features.html#_delete_by_query_is_now_a_plugin). It is no longer core part of Elasticsearch. You can [install it as a plugin as described here](https://www.elastic.co/guide/en/elasticsearch/plugins/2.0/plugins-delete-by-query.html).
Elastic 3.0 still contains the `DeleteByQueryService`, but you need to install the plugin first. If you don't install it and use `DeleteByQueryService` you will most probably get a 404.
An older version of this document stated the following:
> Elastic 3.0 still contains the `DeleteByQueryService` but it will fail with `ErrPluginNotFound` when the plugin is not installed.
>
> Example for Elastic 3.0 (new):
>
> ```go
> _, err := client.DeleteByQuery().Query(elastic.NewTermQuery("client", "1")).Do()
> if err == elastic.ErrPluginNotFound {
> // Delete By Query API is not available
> }
> ```
I have decided that this is not a good way to handle the case of a missing plugin. The main reason is that with this logic, you'd always have to check if the plugin is missing in case of an error. This is not only slow, but it also puts logic into a service where it should really be just opaque and return the response of Elasticsearch.
If you rely on certain plugins to be installed, you should check on startup. That's where the following two helpers come into play.
## HasPlugin and SetRequiredPlugins
Some of the core functionality of Elasticsearch has now been moved into plugins. E.g. the Delete-by-Query API is [a plugin now](https://www.elastic.co/guide/en/elasticsearch/plugins/2.0/plugins-delete-by-query.html).
You need to make sure to add these plugins to your Elasticsearch installation to still be able to use the `DeleteByQueryService`. You can test this now with the `HasPlugin(name string)` helper in the client.
Example for Elastic 3.0 (new):
```go
err, found := client.HasPlugin("delete-by-query")
if err == nil && found {
// ... Delete By Query API is available
}
```
To simplify this process, there is now a `SetRequiredPlugins` helper that can be passed as an option func when creating a new client. If the plugin is not installed, the client wouldn't be created in the first place.
```go
// Will raise an error if the "delete-by-query" plugin is NOT installed
client, err := elastic.NewClient(elastic.SetRequiredPlugins("delete-by-query"))
if err != nil {
log.Fatal(err)
}
```
Notice that there also is a way to define [mandatory plugins](https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-plugins.html#_mandatory_plugins) in the Elasticsearch configuration file.
## Common Query has been renamed to Common Terms Query
The `CommonQuery` has been renamed to `CommonTermsQuery` to be in line with the [Java API](https://www.elastic.co/guide/en/elasticsearch/reference/2.0/_java_api_changes.html#_query_filter_refactoring).
## Remove `MoreLikeThis` and `MoreLikeThisField`
The More Like This API and the More Like This Field query [have been removed](https://www.elastic.co/guide/en/elasticsearch/reference/2.0/_query_dsl_changes.html#_more_like_this) and replaced with the `MoreLikeThisQuery`.
## Remove Filtered Query
With the merge of queries and filters, the [filtered query became deprecated](https://www.elastic.co/guide/en/elasticsearch/reference/2.0/_query_dsl_changes.html#_literal_filtered_literal_query_and_literal_query_literal_filter_deprecated). While it is only deprecated and therefore still available in Elasticsearch 2.0, we have decided to remove it from Elastic 3.0. Why? Because we think that when you're already forced to rewrite many of your application code, it might be a good chance to get rid of things that are deprecated as well. So you might simply change your filtered query with a boolean query as [described here](https://www.elastic.co/guide/en/elasticsearch/reference/2.0/_query_dsl_changes.html#_literal_filtered_literal_query_and_literal_query_literal_filter_deprecated).
## Remove FuzzyLikeThis and FuzzyLikeThisField
Both have been removed from Elasticsearch 2.0 as well.
## Remove LimitFilter
The `limit` filter is [deprecated in Elasticsearch 2.0](https://www.elastic.co/guide/en/elasticsearch/reference/2.0/_query_dsl_changes.html#_literal_limit_literal_filter_deprecated) and becomes a no-op. Now is a good chance to remove it from your application as well. Use the `terminate_after` parameter in your search [as described here](https://www.elastic.co/guide/en/elasticsearch/reference/2.0/search-request-body.html) to achieve similar effects.
## Remove `_cache` and `_cache_key` from filters
Both have been [removed from Elasticsearch 2.0 as well](https://www.elastic.co/guide/en/elasticsearch/reference/2.0/_query_dsl_changes.html#_filter_auto_caching).
## Partial fields are gone
Partial fields are [removed in Elasticsearch 2.0](https://www.elastic.co/guide/en/elasticsearch/reference/2.0/_search_changes.html#_partial_fields) in favor of [source filtering](https://www.elastic.co/guide/en/elasticsearch/reference/2.0/search-request-source-filtering.html).
## Scripting
A [`Script`](https://github.com/olivere/elastic/blob/release-branch.v3/script.go) type has been added to Elastic 3.0. In Elastic 2.0, there were various places (e.g. aggregations) where you could just add the script as a string, specify the scripting language, add parameters etc. With Elastic 3.0, you should now always use the `Script` type.
Example for Elastic 2.0 (old):
```go
update, err := client.Update().Index("twitter").Type("tweet").Id("1").
Script("ctx._source.retweets += num").
ScriptParams(map[string]interface{}{"num": 1}).
Upsert(map[string]interface{}{"retweets": 0}).
Do()
```
Example for Elastic 3.0 (new):
```go
update, err := client.Update().Index("twitter").Type("tweet").Id("1").
Script(elastic.NewScript("ctx._source.retweets += num").Param("num", 1)).
Upsert(map[string]interface{}{"retweets": 0}).
Do()
```
## Cluster State
The combination of `Metric(string)` and `Metrics(...string)` has been replaced by a single func with the signature `Metric(...string)`.
## Unexported structs in response
Services generally return a typed response from a `Do` func. Those structs are exported so that they can be passed around in your own application. In Elastic 3.0 however, we changed that (most) sub-structs are now unexported, meaning: You can only pass around the whole response, not sub-structures of it. This makes it easier for restructuring responses according to the Elasticsearch API. See [`ClusterStateResponse`](https://github.com/olivere/elastic/blob/release-branch.v3/cluster_state.go#L182) as an example.
## Add offset to Histogram aggregation
Histogram aggregations now have an [offset](https://github.com/elastic/elasticsearch/pull/9505) option.
## Services
### REST API specification
As you might know, Elasticsearch comes with a REST API specification. The specification describes the endpoints in a JSON structure.
Most services in Elastic predated the REST API specification. We are in the process of bringing all these services in line with the specification. Services can be generated by `go generate` (not 100% automatic though). This is an ongoing process.
This probably doesn't mean a lot to you. However, you can now be more confident that Elastic supports all features that the REST API specification describes.
At the same time, the file names of the services are renamed to match the REST API specification naming.
### REST API Test Suite
The REST API specification of Elasticsearch comes along with a test suite that official clients typically use to test for conformance. Up until now, Elastic didn't run this test suite. However, we are in the process of setting up infrastructure and tests to match this suite as well.
This process in not completed though.
================================================
FILE: CHANGELOG-5.0.md
================================================
# Changes in Elastic 5.0
## Enforce context.Context in PerformRequest and Do
We enforce the usage of `context.Context` everywhere you execute a request.
You need to change all your `Do()` calls to pass a context: `Do(ctx)`.
This enables automatic request cancelation and many other patterns.
If you don't need this, simply pass `context.TODO()` or `context.Background()`.
## Warmers removed
Warmers are no longer necessary and have been [removed in ES 5.0](https://www.elastic.co/guide/en/elasticsearch/reference/master/breaking_50_index_apis.html#_warmers).
## Optimize removed
Optimize was deprecated in ES 2.0 and has been [removed in ES 5.0](https://www.elastic.co/guide/en/elasticsearch/reference/master/breaking_50_rest_api_changes.html#_literal__optimize_literal_endpoint_removed).
Use [Force Merge](https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-forcemerge.html) instead.
## Missing Query removed
The `missing` query has been [removed](https://www.elastic.co/guide/en/elasticsearch/reference/master/query-dsl-exists-query.html#_literal_missing_literal_query).
Use `exists` query with `must_not` in `bool` query instead.
## And Query removed
The `and` query has been [removed](https://www.elastic.co/guide/en/elasticsearch/reference/master/breaking_50_search_changes.html#_deprecated_queries_removed).
Use `must` clauses in a `bool` query instead.
## Not Query removed
TODO Is it removed?
## Or Query removed
The `or` query has been [removed](https://www.elastic.co/guide/en/elasticsearch/reference/master/breaking_50_search_changes.html#_deprecated_queries_removed).
Use `should` clauses in a `bool` query instead.
## Filtered Query removed
The `filtered` query has been [removed](https://www.elastic.co/guide/en/elasticsearch/reference/master/breaking_50_search_changes.html#_deprecated_queries_removed).
Use `bool` query instead, which supports `filter` clauses too.
## Limit Query removed
The `limit` query has been [removed](https://www.elastic.co/guide/en/elasticsearch/reference/master/breaking_50_search_changes.html#_deprecated_queries_removed).
Use the `terminate_after` parameter instead.
# Template Query removed
The `template` query has been [deprecated](https://www.elastic.co/guide/en/elasticsearch/reference/5.x/query-dsl-template-query.html). You should use
Search Templates instead.
We remove it from Elastic 5.0 as the 5.0 update is already a good opportunity
to get rid of old stuff.
## `_timestamp` and `_ttl` removed
Both of these fields were deprecated and are now [removed](https://www.elastic.co/guide/en/elasticsearch/reference/master/breaking_50_mapping_changes.html#_literal__timestamp_literal_and_literal__ttl_literal).
## Search template Put/Delete API returns `acknowledged` only
The response type for Put/Delete search templates has changed.
It only returns a single `acknowledged` flag now.
## Fields has been renamed to Stored Fields
The `fields` parameter has been renamed to `stored_fields`.
See [here](https://www.elastic.co/guide/en/elasticsearch/reference/5.x/breaking_50_search_changes.html#_literal_fields_literal_parameter).
## Fielddatafields has been renamed to Docvaluefields
The `fielddata_fields` parameter [has been renamed](https://www.elastic.co/guide/en/elasticsearch/reference/5.x/breaking_50_search_changes.html#_literal_fielddata_fields_literal_parameter)
to `docvalue_fields`.
## Type exists endpoint changed
The endpoint for checking whether a type exists has been changed from
`HEAD {index}/{type}` to `HEAD {index}/_mapping/{type}`.
See [here](https://www.elastic.co/guide/en/elasticsearch/reference/5.0/breaking_50_rest_api_changes.html#_literal_head_index_type_literal_replaced_with_literal_head_index__mapping_type_literal).
## Refresh parameter changed
The `?refresh` parameter previously could be a boolean value. It indicated
whether changes made by a request (e.g. by the Bulk API) should be immediately
visible in search, or not. Using `refresh=true` had the positive effect of
immediately seeing the changes when searching; the negative effect is that
it is a rather big performance hit.
With 5.0, you now have the choice between these 3 values.
* `"true"` - Refresh immediately
* `"false"` - Do not refresh (the default value)
* `"wait_for"` - Wait until ES made the document visible in search
See [?refresh](https://www.elastic.co/guide/en/elasticsearch/reference/master/docs-refresh.html) in the documentation.
Notice that `true` and `false` (the boolean values) are no longer available
now in Elastic. You must use a string instead, with one of the above values.
## ReindexerService removed
The `ReindexerService` was a custom solution that was started in the ES 1.x era
to automate reindexing data, from one index to another or even between clusters.
ES 2.3 introduced its own [Reindex API](https://www.elastic.co/guide/en/elasticsearch/reference/master/docs-reindex.html)
so we're going to remove our custom solution and ask you to use the native reindexer.
The `ReindexService` is available via `client.Reindex()` (which used to point
to the custom reindexer).
## Delete By Query back in core
The [Delete By Query API](https://www.elastic.co/guide/en/elasticsearch/reference/master/docs-delete-by-query.html)
was moved into a plugin in 2.0. Now its back in core with a complete rewrite based on the Bulk API.
It has it's own endpoint at `/_delete_by_query`.
Delete By Query, Reindex, and Update By Query are very similar under the hood.
## Reindex, Delete By Query, and Update By Query response changed
The response from the above APIs changed a bit. E.g. the `retries` value
used to be an `int64` and returns separate values for `bulk` and `search` now:
```
// Old
{
...
"retries": 123,
...
}
```
```
// New
{
...
"retries": {
"bulk": 123,
"search": 0
},
...
}
```
## ScanService removed
The `ScanService` is removed. Use the (new) `ScrollService` instead.
## New ScrollService
There was confusion around `ScanService` and `ScrollService` doing basically
the same. One was returning slices and didn't support all query details, the
other returned one document after another and wasn't safe for concurrent use.
So we merged the two and merged it into a new `ScrollService` that
removes all the problems with the older services.
In other words:
If you used `ScanService`, switch to `ScrollService`.
If you used the old `ScrollService`, you might need to fix some things but
overall it should just work.
Changes:
- We replaced `elastic.EOS` with `io.EOF` to indicate the "end of scroll".
TODO Not implemented yet
## Suggesters
They have been [completely rewritten in ES 5.0](https://www.elastic.co/guide/en/elasticsearch/reference/master/breaking_50_suggester.html).
Some changes:
- Suggesters no longer have an [output](https://www.elastic.co/guide/en/elasticsearch/reference/master/breaking_50_suggester.html#_simpler_completion_indexing).
TODO Fix all structural changes in suggesters
## Percolator
Percolator has [changed considerably](https://www.elastic.co/guide/en/elasticsearch/reference/5.x/breaking_50_percolator.html).
Elastic 5.0 adds the new
[Percolator Query](https://www.elastic.co/guide/en/elasticsearch/reference/5.x/query-dsl-percolate-query.html)
which can be used in combination with the new
[Percolator type](https://www.elastic.co/guide/en/elasticsearch/reference/5.x/percolator.html).
The Percolate service is removed from Elastic 5.0.
## Remove Consistency, add WaitForActiveShards
The `consistency` parameter has been removed in a lot of places, e.g. the Bulk,
Index, Delete, Delete-by-Query, Reindex, Update, and Update-by-Query API.
It has been replaced by a somewhat similar `wait_for_active_shards` parameter.
See https://github.com/elastic/elasticsearch/pull/19454.
================================================
FILE: CHANGELOG-6.0.md
================================================
# Changes from 5.0 to 6.0
See [breaking changes](https://www.elastic.co/guide/en/elasticsearch/reference/master/breaking-changes-6.0.html).
## _all removed
6.0 has removed support for the `_all` field.
## Boolean values coerced
Only use `true` or `false` for boolean values, not `0` or `1` or `on` or `off`.
## Single Type Indices
Notice that 6.0 and future versions will default to single type indices, i.e. you may not use multiple types when e.g. adding an index with a mapping.
See [here for details](https://www.elastic.co/guide/en/elasticsearch/reference/6.7/removal-of-types.html#_what_are_mapping_types).
================================================
FILE: CHANGELOG-7.0.md
================================================
# Changes from 6.0 to 7.0
See [breaking changes](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/breaking-changes-7.0.html).
## SearchHit.Source changed from `*json.RawMessage` to `json.RawMessage`
The `SearchHit` structure changed from
```
// SearchHit is a single hit.
type SearchHit struct {
...
Source *json.RawMessage `json:"_source,omitempty"` // stored document source
...
}
```
to
```
// SearchHit is a single hit.
type SearchHit struct {
...
Source json.RawMessage `json:"_source,omitempty"` // stored document source
...
}
```
As `json.RawMessage` is a `[]byte`, there is no need to specify it
as `*json.RawMessage` as `json.RawMessage` is perfectly ok to represent
a `nil` value.
So when deserializing the search hits, you need to change your code from:
```
for _, hit := range searchResult.Hits.Hits {
var doc Doc
err := json.Unmarshal(*hit.Source, &doc) // notice the * here
if err != nil {
// Deserialization failed
}
}
```
to
```
for _, hit := range searchResult.Hits.Hits {
var doc Doc
err := json.Unmarshal(hit.Source, &doc) // it's missing here
if err != nil {
// Deserialization failed
}
}
```
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at oliver@eilhard.net. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
================================================
FILE: CONTRIBUTING.md
================================================
# How to contribute
Elastic is an open-source project and we are looking forward to each
contribution.
Notice that while the [official Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html) is rather good, it is a high-level
overview of the features of Elasticsearch. However, Elastic tries to resemble
the Java API of Elasticsearch which you can find [on GitHub](https://github.com/elastic/elasticsearch).
This explains why you might think that some options are strange or missing
in Elastic, while often they're just different. Please check the Java API first.
Having said that: Elasticsearch is moving fast and it might be very likely
that we missed some features or changes. Feel free to change that.
## Your Pull Request
To make it easy to review and understand your changes, please keep the
following things in mind before submitting your pull request:
* You compared the existing implementation with the Java API, did you?
* Please work on the latest possible state of `olivere/elastic`.
Use `release-branch.v2` for targeting Elasticsearch 1.x and
`release-branch.v3` for targeting 2.x.
* Create a branch dedicated to your change.
* If possible, write a test case which confirms your change.
* Make sure your changes and your tests work with all recent versions of
Elasticsearch. We currently support Elasticsearch 1.7.x in the
release-branch.v2 and Elasticsearch 2.x in the release-branch.v3.
* Test your changes before creating a pull request (`go test ./...`).
* Don't mix several features or bug fixes in one pull request.
* Create a meaningful commit message.
* Explain your change, e.g. provide a link to the issue you are fixing and
probably a link to the Elasticsearch documentation and/or source code.
* Format your source with `go fmt`.
## Additional Resources
* [GitHub documentation](https://help.github.com/)
* [GitHub pull request documentation](https://help.github.com/en/articles/creating-a-pull-request)
================================================
FILE: CONTRIBUTORS
================================================
# This is a list of people who have contributed code
# to the Elastic repository.
#
# It is just my small "thank you" to all those that helped
# making Elastic what it is.
#
# Please keep this list sorted.
0x6875790d0a [@huydx](https://github.com/huydx)
Aaron Tami [@aarontami](https://github.com/aarontami)
Adam Alix [@adamalix](https://github.com/adamalix)
Adam Weiner [@adamweiner](https://github.com/adamweiner)
Adam Szaraniec [@mimol91](https://github.com/mimol91)
Adrian Lungu [@AdrianLungu](https://github.com/AdrianLungu)
alehano [@alehano](https://github.com/alehano)
Alejandro Carstens [@alejandro-carstens](https://github.com/alejandro-carstens)
Alex [@akotlar](https://github.com/akotlar)
Alexander Sack [@asac](https://github.com/asac)
Alexandre Olivier [@aliphen](https://github.com/aliphen)
Alexey Sharov [@nizsheanez](https://github.com/nizsheanez)
Aman Jain [@amanjain97](https://github.com/amanjain97)
Anders [@ANerd](https://github.com/ANerd)
AndreKR [@AndreKR](https://github.com/AndreKR)
André Bierlein [@ligustah](https://github.com/ligustah)
Andrew Dunham [@andrew-d](https://github.com/andrew-d)
Andrew Gaul [@andrewgaul](https://github.com/andrewgaul)
Andy Walker [@alaska](https://github.com/alaska)
Arpit Agarwal [@arpiagar](https://github.com/arpiagar)
Arquivei [@arquivei](https://github.com/arquivei)
Artemiy Elozhenko [@artezh](https://github.com/artezh)
arthurgustin [@arthurgustin](https://github.com/arthurgustin)
Bas van Dijk [@basvandijk](https://github.com/basvandijk)
Benjamin Fernandes [@LotharSee](https://github.com/LotharSee)
Benjamin Zarzycki [@kf6nux](https://github.com/kf6nux)
bestgopher [@bestgopher](https://github.com/bestgopher)
Björn Gerdau [@kernle32dll](https://github.com/kernle32dll)
Boris Popovschi [@Zyqsempai](https://github.com/Zyqsempai)
Bowei Xu [@vancexu](https://github.com/vancexu)
Braden Bassingthwaite [@bbassingthwaite-va](https://github.com/bbassingthwaite-va)
Brady Love [@bradylove](https://github.com/bradylove)
Bryan Conklin [@bmconklin](https://github.com/bmconklin)
Bruce Zhou [@brucez-isell](https://github.com/brucez-isell)
Carl Dunham [@carldunham](https://github.com/carldunham)
Carl Johan Gustavsson [@cjgu](https://github.com/cjgu)
Carson [@carson0321](https://github.com/carson0321)
Cat [@cat-turner](https://github.com/cat-turner)
César Jiménez [@cesarjimenez](https://github.com/cesarjimenez)
cforbes [@cforbes](https://github.com/cforbes)
張泰瑋(Chang Tai Wei) [@david30907d](https://github.com/david30907d)
cheshire [@NikitaSerenko](https://github.com/NikitaSerenko)
Chris M [@tebriel](https://github.com/tebriel)
Chris Rice [@donutmonger](https://github.com/donutmonger)
Claudiu Olteanu [@claudiuolteanu](https://github.com/claudiuolteanu)
Chris Duncan [@veqryn](https://github.com/veqryn)
Chris Ludden [@cludden](https://github.com/cludden)
Christophe Courtaut [@kri5](https://github.com/kri5)
cmitchell [@cmitchell](https://github.com/cmitchell)
Connor Peet [@connor4312](https://github.com/connor4312)
Conrad Pankoff [@deoxxa](https://github.com/deoxxa)
Corey Scott [@corsc](https://github.com/corsc)
Chris Petersen [@ex-nerd](https://github.com/ex-nerd)
czxichen [@czxichen](https://github.com/czxichen)
Daniel Barrett [@shendaras](https://github.com/shendaras)
Daniel Heckrath [@DanielHeckrath](https://github.com/DanielHeckrath)
Daniel Imfeld [@dimfeld](https://github.com/dimfeld)
Daniel Santos [@danlsgiga](https://github.com/danlsgiga)
David Emanuel Buchmann [@wuurrd](https://github.com/wuurrd)
Devin Christensen [@quixoten](https://github.com/quixoten)
diacone [@diacone](https://github.com/diacone)
Diego Becciolini [@itizir](https://github.com/itizir)
Don Smith III [@cactauz](https://github.com/cactauz)
Dwayne Schultz [@myshkin5](https://github.com/myshkin5)
Elizabeth Jarrett [@mejarrett](https://github.com/mejarrett)
Elliot Williams [@elliotwms](https://github.com/elliotwms)
Ellison Leão [@ellisonleao](https://github.com/ellisonleao)
Emil Gedda [@EmilGedda](https://github.com/EmilGedda)
Erik Grinaker [@erikgrinaker](https://github.com/erikgrinaker)
Erwin [@eticzon](https://github.com/eticzon)
Etienne Lafarge [@elafarge](https://github.com/elafarge)
Eugene Egorov [@EugeneEgorov](https://github.com/EugeneEgorov)
Evan Shaw [@edsrzf](https://github.com/edsrzf)
Fanfan [@wenpos](https://github.com/wenpos)
Faolan C-P [@fcheslack](https://github.com/fcheslack)
Filip Tepper [@filiptepper](https://github.com/filiptepper)
Garrett Kelley [@GarrettKelley](https://github.com/GarrettKelley)
Gaspard Douady [@plopik](https://github.com/plopik)
Gaylord Aulke [@blafasel42](https://github.com/blafasel42)
Gerhard Häring [@ghaering](https://github.com/ghaering)
gregoryfranklin [@gregoryfranklin](https://github.com/gregoryfranklin)
Guilherme Silveira [@guilherme-santos](https://github.com/guilherme-santos)
Guillaume J. Charmes [@creack](https://github.com/creack)
Guiseppe [@gm42](https://github.com/gm42)
Han Yu [@MoonighT](https://github.com/MoonighT)
Harmen [@alicebob](https://github.com/alicebob)
Haroldo Vélez [@Haroldov](https://github.com/Haroldov)
Harrison Wright [@wright8191](https://github.com/wright8191)
Henry Clifford [@hcliff](https://github.com/hcliff)
Henry Stern [@hstern](https://github.com/hstern)
Herbert Lu [@lryong](https://github.com/lryong)
Igor Dubinskiy [@idubinskiy](https://github.com/idubinskiy)
initialcontext [@initialcontext](https://github.com/initialcontext)
Isaac Saldana [@isaldana](https://github.com/isaldana)
Ishan Jain [@ishanjain28](https://github.com/ishanjain28)
J Barkey Wolf [@jjhbw](https://github.com/jjhbw)
Jack Lindamood [@cep21](https://github.com/cep21)
Jacob [@jdelgad](https://github.com/jdelgad)
Jan Düpmeier [@jduepmeier](https://github.com/jduepmeier)
Jayme Rotsaert [@jrots](https://github.com/jrots)
Jean-Alexandre Beaumont [@Enteris](https://github.com/Enteris)
Jean-François Roche [@jfroche](https://github.com/jfroche)
Jeff Rand [@jeffrand](https://github.com/jeffrand)
Jeremy Canady [@jrmycanady](https://github.com/jrmycanady)
Jérémie Vexiau [@texvex](https://github.com/texvex)
Jesper Bränn [@Yopi](https://github.com/Yopi)
Jim Berlage [@jimberlage](https://github.com/jimberlage)
Joe Buck [@four2five](https://github.com/four2five)
John Barker [@j16r](https://github.com/j16r)
John Goodall [@jgoodall](https://github.com/jgoodall)
John Stanford [@jxstanford](https://github.com/jxstanford)
Jonas Groenaas Drange [@semafor](https://github.com/semafor)
Josef Fröhle [@Dexus](https://github.com/Dexus)
José Martínez [@xose](https://github.com/xose)
Josh Chorlton [@jchorl](https://github.com/jchorl)
Jpnock [@Jpnock](https://github.com/Jpnock)
jun [@coseyo](https://github.com/coseyo)
Junpei Tsuji [@jun06t](https://github.com/jun06t)
Karen Yang [@kyangtt](https://github.com/kyangtt)
kartlee [@kartlee](https://github.com/kartlee)
Keith Hatton [@khatton-ft](https://github.com/khatton-ft)
kel [@liketic](https://github.com/liketic)
Kenta SUZUKI [@suzuken](https://github.com/suzuken)
Kevin Mulvey [@kmulvey](https://github.com/kmulvey)
Kyle Brandt [@kylebrandt](https://github.com/kylebrandt)
Larry Cinnabar [@larrycinnabar](https://github.com/larrycinnabar)
Leandro Piccilli [@lpic10](https://github.com/lpic10)
Lee [@leezhm](https://github.com/leezhm)
lechnertech [@lechnertech](https://github.com/lechnertech)
M. Zulfa Achsani [@misterciput](https://github.com/misterciput)
Maciej Lisiewski [@c2h5oh](https://github.com/c2h5oh)
Mara Kim [@autochthe](https://github.com/autochthe)
Marcy Buccellato [@marcybuccellato](https://github.com/marcybuccellato)
Mark Costello [@mcos](https://github.com/mcos)
Martin Häger [@protomouse](https://github.com/protomouse)
Matt Braymer-Hayes [@mattayes](https://github.com/mattayes)
Medhi Bechina [@mdzor](https://github.com/mdzor)
Mike Beshai [@mbesh](https://github.com/mbesh)
Mikhail Balabin [@mablabin](https://github.com/mbalabin)
mmfrb [@mmfrb](https://github.com/mmfrb)
mnpritula [@mnpritula](https://github.com/mnpritula)
mosa [@mosasiru](https://github.com/mosasiru)
Muhammet Çakır [@cakirmuha](https://github.com/cakirmuha)
Munkyu Im [@munkyu](https://github.com/munkyu)
naimulhaider [@naimulhaider](https://github.com/naimulhaider)
Naoya Yoshizawa [@azihsoyn](https://github.com/azihsoyn)
Naoya Tsutsumi [@tutuming](https://github.com/tutuming)
Nathan Macnamara [@nathanmac](https://github.com/nathanmac)
Nathan Lacey [@nlacey](https://github.com/nlacey)
navins [@ishare](https://github.com/ishare)
NeoCN [@NeoCN](https://github.com/NeoCN)
Nguyen Xuan Dung [@dungnx](https://github.com/dungnx)
Nicholas Wolff [@nwolff](https://github.com/nwolff)
Nick K [@utrack](https://github.com/utrack)
Nick Whyte [@nickw444](https://github.com/nickw444)
Nicolae Vartolomei [@nvartolomei](https://github.com/nvartolomei)
okhowang [@okhowang](https://github.com/okhowang)
Orne Brocaar [@brocaar](https://github.com/brocaar)
ottramst [@ottramst](https://github.com/ottramst)
Paul [@eyeamera](https://github.com/eyeamera)
Paul Oldenburg [@lr-paul](https://github.com/lr-paul)
Pedro [@otherview](https://github.com/otherview)
Pete C [@peteclark-ft](https://github.com/peteclark-ft)
Peter Nagy [@nagypeterjob](https://github.com/nagypeterjob)
Paolo [@ppiccolo](https://github.com/ppiccolo)
Phillip Baker [@phillbaker](https://github.com/phillbaker)
QilingZhao [@qilingzhao](https://github.com/qilingzhao)
Igor Panychek [@panychek](https://github.com/panychek)
Radoslaw Wesolowski [@r--w](https://github.com/r--w)
Rafał Gałus [@rgalus](https://github.com/rgalus)
rchicoli [@rchicoli](https://github.com/rchicoli)
Roman Colohanin [@zuzmic](https://github.com/zuzmic)
Ryan Schmukler [@rschmukler](https://github.com/rschmukler)
Ryan Wynn [@rwynn](https://github.com/rwynn)
Sacheendra talluri [@sacheendra](https://github.com/sacheendra)
Sean DuBois [@Sean-Der](https://github.com/Sean-Der)
Sagan Yaroslav [@sgnrslv](https://github.com/sgnrslv)
Shalin LK [@shalinlk](https://github.com/shalinlk)
Simon Schneider [@raynigon](https://github.com/raynigon)
singham [@zhaochenxiao90](https://github.com/zhaochenxiao90)
Slawomir CALUCH [@slawo](https://github.com/slawo)
soarpenguin [@soarpenguin](https://github.com/soarpenguin)
Stephan Krynauw [@skrynauw](https://github.com/skrynauw)
Stephen Kubovic [@stephenkubovic](https://github.com/stephenkubovic)
Stuart Warren [@Woz](https://github.com/stuart-warren)
Sulaiman [@salajlan](https://github.com/salajlan)
Sundar [@sundarv85](https://github.com/sundarv85)
Swarlston [@Swarlston](https://github.com/Swarlston)
Take [ww24](https://github.com/ww24)
Tetsuya Morimoto [@t2y](https://github.com/t2y)
TheZeroSlave [@TheZeroSlave](https://github.com/TheZeroSlave)
Tomasz Elendt [@telendt](https://github.com/telendt)
TimeEmit [@TimeEmit](https://github.com/timeemit)
TusharM [@tusharm](https://github.com/tusharm)
wangtuo [@wangtuo](https://github.com/wangtuo)
Wédney Yuri [@wedneyyuri](https://github.com/wedneyyuri)
Wesley Kim [@wesleyk](https://github.com/wesleyk)
wolfkdy [@wolfkdy](https://github.com/wolfkdy)
Wyndham Blanton [@wyndhblb](https://github.com/wyndhblb)
Yarden Bar [@ayashjorden](https://github.com/ayashjorden)
Yuya Kusakabe [@higebu](https://github.com/higebu)
zakthomas [@zakthomas](https://github.com/zakthomas)
Zach [@snowzach](https://github.com/snowzach)
zhangxin [@visaxin](https://github.com/visaxin)
@林 [@zplzpl](https://github.com/zplzpl)
================================================
FILE: ISSUE_TEMPLATE.md
================================================
Please use the following questions as a guideline to help me answer
your issue/question without further inquiry. Thank you.
### Which version of Elastic are you using?
[ ] elastic.v7 (for Elasticsearch 7.x)
[ ] elastic.v6 (for Elasticsearch 6.x)
[ ] elastic.v5 (for Elasticsearch 5.x)
[ ] elastic.v3 (for Elasticsearch 2.x)
[ ] elastic.v2 (for Elasticsearch 1.x)
### Please describe the expected behavior
### Please describe the actual behavior
### Any steps to reproduce the behavior?
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright © 2012-2015 Oliver Eilhard
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the “Software”), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
================================================
FILE: Makefile
================================================
.PHONY: test
test:
go test -race -deprecations -strict-decoder -v . ./aws/... ./config/... ./trace/... ./uritemplates/...
================================================
FILE: README.md
================================================
# Elastic
**This is a development branch that is actively being worked on. DO NOT USE IN PRODUCTION! If you want to use stable versions of Elastic, please use Go modules for the 7.x release (or later) or a dependency manager like [dep](https://github.com/golang/dep) for earlier releases.**
Elastic is an [Elasticsearch](http://www.elasticsearch.org/) client for the
[Go](http://www.golang.org/) programming language.
[](https://github.com/olivere/elastic/actions)
[](https://pkg.go.dev/github.com/olivere/elastic/v7?tab=doc)
[](https://raw.githubusercontent.com/olivere/elastic/master/LICENSE)
See the [wiki](https://github.com/olivere/elastic/wiki) for additional information about Elastic.
## Releases
**The release branches (e.g. [`release-branch.v7`](https://github.com/olivere/elastic/tree/release-branch.v7))
are actively being worked on and can break at any time.
If you want to use stable versions of Elastic, please use Go modules.**
Here's the version matrix:
Elasticsearch version | Elastic version | Package URL | Remarks |
----------------------|------------------|-------------|---------|
7.x | 7.0 | [`github.com/olivere/elastic/v7`](https://github.com/olivere/elastic) ([source](https://github.com/olivere/elastic/tree/release-branch.v7) [doc](http://godoc.org/github.com/olivere/elastic)) | Use Go modules.
6.x | 6.0 | [`github.com/olivere/elastic`](https://github.com/olivere/elastic) ([source](https://github.com/olivere/elastic/tree/release-branch.v6) [doc](http://godoc.org/github.com/olivere/elastic)) | Use a dependency manager (see below).
5.x | 5.0 | [`gopkg.in/olivere/elastic.v5`](https://gopkg.in/olivere/elastic.v5) ([source](https://github.com/olivere/elastic/tree/release-branch.v5) [doc](http://godoc.org/gopkg.in/olivere/elastic.v5)) | Actively maintained.
2.x | 3.0 | [`gopkg.in/olivere/elastic.v3`](https://gopkg.in/olivere/elastic.v3) ([source](https://github.com/olivere/elastic/tree/release-branch.v3) [doc](http://godoc.org/gopkg.in/olivere/elastic.v3)) | Deprecated. Please update.
1.x | 2.0 | [`gopkg.in/olivere/elastic.v2`](https://gopkg.in/olivere/elastic.v2) ([source](https://github.com/olivere/elastic/tree/release-branch.v2) [doc](http://godoc.org/gopkg.in/olivere/elastic.v2)) | Deprecated. Please update.
0.9-1.3 | 1.0 | [`gopkg.in/olivere/elastic.v1`](https://gopkg.in/olivere/elastic.v1) ([source](https://github.com/olivere/elastic/tree/release-branch.v1) [doc](http://godoc.org/gopkg.in/olivere/elastic.v1)) | Deprecated. Please update.
**Example:**
You have installed Elasticsearch 7.0.0 and want to use Elastic.
As listed above, you should use Elastic 7.0 (code is in `release-branch.v7`).
To use the required version of Elastic in your application, you
should use [Go modules](https://github.com/golang/go/wiki/Modules)
to manage dependencies. Make sure to use a version such as `7.0.0` or later.
To use Elastic, import:
```go
import "github.com/olivere/elastic/v7"
```
### Elastic 7.0
Elastic 7.0 targets Elasticsearch 7.x which [was released on April 10th 2019](https://www.elastic.co/guide/en/elasticsearch/reference/7.0/release-notes-7.0.0.html).
As always with major version, there are a lot of [breaking changes](https://www.elastic.co/guide/en/elasticsearch/reference/7.0/release-notes-7.0.0.html#breaking-7.0.0).
We will use this as an opportunity to [clean up and refactor Elastic](https://github.com/olivere/elastic/blob/release-branch.v7/CHANGELOG-7.0.md),
as we already did in earlier (major) releases.
### Elastic 6.0
Elastic 6.0 targets Elasticsearch 6.x which was [released on 14th November 2017](https://www.elastic.co/blog/elasticsearch-6-0-0-released).
Notice that there are a lot of [breaking changes in Elasticsearch 6.0](https://www.elastic.co/guide/en/elasticsearch/reference/6.7/breaking-changes-6.0.html)
and we used this as an opportunity to [clean up and refactor Elastic](https://github.com/olivere/elastic/blob/release-branch.v6/CHANGELOG-6.0.md)
as we did in the transition from earlier versions of Elastic.
### Elastic 5.0
Elastic 5.0 targets Elasticsearch 5.0.0 and later. Elasticsearch 5.0.0 was
[released on 26th October 2016](https://www.elastic.co/blog/elasticsearch-5-0-0-released).
Notice that there are will be a lot of [breaking changes in Elasticsearch 5.0](https://www.elastic.co/guide/en/elasticsearch/reference/5.0/breaking-changes-5.0.html)
and we used this as an opportunity to [clean up and refactor Elastic](https://github.com/olivere/elastic/blob/release-branch.v5/CHANGELOG-5.0.md)
as we did in the transition from Elastic 2.0 (for Elasticsearch 1.x) to Elastic 3.0 (for Elasticsearch 2.x).
Furthermore, the jump in version numbers will give us a chance to be in sync with the Elastic Stack.
### Elastic 3.0
Elastic 3.0 targets Elasticsearch 2.x and is published via [`gopkg.in/olivere/elastic.v3`](https://gopkg.in/olivere/elastic.v3).
Elastic 3.0 will only get critical bug fixes. You should update to a recent version.
### Elastic 2.0
Elastic 2.0 targets Elasticsearch 1.x and is published via [`gopkg.in/olivere/elastic.v2`](https://gopkg.in/olivere/elastic.v2).
Elastic 2.0 will only get critical bug fixes. You should update to a recent version.
### Elastic 1.0
Elastic 1.0 is deprecated. You should really update Elasticsearch and Elastic
to a recent version.
However, if you cannot update for some reason, don't worry. Version 1.0 is
still available. All you need to do is go-get it and change your import path
as described above.
## Status
We use Elastic in production since 2012. Elastic is stable but the API changes
now and then. We strive for API compatibility.
However, Elasticsearch sometimes introduces [breaking changes](https://www.elastic.co/guide/en/elasticsearch/reference/master/breaking-changes.html)
and we sometimes have to adapt.
Having said that, there have been no big API changes that required you
to rewrite your application big time. More often than not it's renaming APIs
and adding/removing features so that Elastic is in sync with Elasticsearch.
Elastic has been used in production starting with Elasticsearch 0.90 up to recent 7.x
versions.
We recently switched to [GitHub Actions for testing](https://github.com/olivere/elastic/actions).
Before that, we used [Travis CI](https://travis-ci.org/olivere/elastic) successfully for years).
Elasticsearch has quite a few features. Most of them are implemented
by Elastic. I add features and APIs as required. It's straightforward
to implement missing pieces. I'm accepting pull requests :-)
Having said that, I hope you find the project useful.
## Getting Started
The first thing you do is to create a [Client](https://github.com/olivere/elastic/blob/master/client.go).
The client connects to Elasticsearch on `http://127.0.0.1:9200` by default.
You typically create one client for your app. Here's a complete example of
creating a client, creating an index, adding a document, executing a search etc.
An example is available [here](https://olivere.github.io/elastic/).
Here's a [link to a complete working example for v6](https://gist.github.com/olivere/e4a376b4783c0914e44ea4f745ce2ebf).
Here are a few tips on how to get used to Elastic:
1. Head over to the [Wiki](https://github.com/olivere/elastic/wiki) for detailed information and
topics like e.g. [how to add a middleware](https://github.com/olivere/elastic/wiki/HttpTransport)
or how to [connect to AWS](https://github.com/olivere/elastic/wiki/Using-with-AWS-Elasticsearch-Service).
2. If you are unsure how to implement something, read the tests (all `_test.go` files).
They not only serve as a guard against changes, but also as a reference.
3. The [recipes](https://github.com/olivere/elastic/tree/release-branch.v6/recipes)
contains small examples on how to implement something, e.g. bulk indexing, scrolling etc.
## API Status
### Document APIs
- [x] Index API
- [x] Get API
- [x] Delete API
- [x] Delete By Query API
- [x] Update API
- [x] Update By Query API
- [x] Multi Get API
- [x] Bulk API
- [x] Reindex API
- [x] Term Vectors
- [x] Multi termvectors API
### Search APIs
- [x] Search
- [x] Search Template
- [ ] Multi Search Template
- [x] Search Shards API
- [x] Suggesters
- [x] Term Suggester
- [x] Phrase Suggester
- [x] Completion Suggester
- [x] Context Suggester
- [x] Multi Search API
- [x] Count API
- [x] Validate API
- [x] Explain API
- [x] Profile API
- [x] Field Capabilities API
### Aggregations
- Metrics Aggregations
- [x] Avg
- [ ] Boxplot (X-pack)
- [x] Cardinality
- [x] Extended Stats
- [x] Geo Bounds
- [x] Geo Centroid
- [x] Matrix stats
- [x] Max
- [x] Median absolute deviation
- [x] Min
- [x] Percentile Ranks
- [x] Percentiles
- [ ] Rate (X-pack)
- [ ] Scripted Metric
- [x] Stats
- [ ] String stats (X-pack)
- [x] Sum
- [ ] T-test (X-pack)
- [x] Top Hits
- [x] Top metrics (X-pack)
- [x] Value Count
- [x] Weighted avg
- Bucket Aggregations
- [x] Adjacency Matrix
- [x] Auto-interval Date Histogram
- [x] Children
- [x] Composite
- [x] Date Histogram
- [x] Date Range
- [x] Diversified Sampler
- [x] Filter
- [x] Filters
- [x] Geo Distance
- [x] Geohash Grid
- [x] Geotile grid
- [x] Global
- [x] Histogram
- [x] IP Range
- [x] Missing
- [x] Nested
- [ ] Parent
- [x] Range
- [ ] Rare terms
- [x] Reverse Nested
- [x] Sampler
- [x] Significant Terms
- [x] Significant Text
- [x] Terms
- [ ] Variable width histogram
- Pipeline Aggregations
- [x] Avg Bucket
- [x] Bucket Script
- [x] Bucket Selector
- [x] Bucket Sort
- [ ] Cumulative cardinality (X-pack)
- [x] Cumulative Sum
- [x] Derivative
- [ ] Extended Stats Bucket
- [ ] Inference bucket (X-pack)
- [x] Max Bucket
- [x] Min Bucket
- [x] Moving Average
- [x] Moving function
- [ ] Moving percentiles (X-pack)
- [ ] Normalize (X-pack)
- [x] Percentiles Bucket
- [x] Serial Differencing
- [x] Stats Bucket
- [x] Sum Bucket
- [x] Aggregation Metadata
### Indices APIs
- [x] Create Index
- [x] Delete Index
- [x] Get Index
- [x] Indices Exists
- [x] Open / Close Index
- [x] Shrink Index
- [x] Rollover Index
- [x] Put Mapping
- [x] Get Mapping
- [x] Get Field Mapping
- [x] Types Exists
- [x] Index Aliases
- [x] Update Indices Settings
- [x] Get Settings
- [x] Analyze
- [x] Explain Analyze
- [x] Index Templates
- [x] Indices Stats
- [x] Indices Segments
- [ ] Indices Recovery
- [ ] Indices Shard Stores
- [x] Clear Cache
- [x] Flush
- [x] Synced Flush
- [x] Refresh
- [x] Force Merge
### Index Lifecycle Management APIs
- [x] Create Policy
- [x] Get Policy
- [x] Delete Policy
- [ ] Move to Step
- [ ] Remove Policy
- [ ] Retry Policy
- [ ] Get Ilm Status
- [ ] Explain Lifecycle
- [ ] Start Ilm
- [ ] Stop Ilm
### cat APIs
- [X] cat aliases
- [X] cat allocation
- [X] cat count
- [X] cat fielddata
- [X] cat health
- [X] cat indices
- [x] cat master
- [ ] cat nodeattrs
- [ ] cat nodes
- [ ] cat pending tasks
- [ ] cat plugins
- [ ] cat recovery
- [ ] cat repositories
- [ ] cat thread pool
- [ ] cat shards
- [ ] cat segments
- [X] cat snapshots
- [ ] cat templates
### Cluster APIs
- [x] Cluster Health
- [x] Cluster State
- [x] Cluster Stats
- [ ] Pending Cluster Tasks
- [x] Cluster Reroute
- [ ] Cluster Update Settings
- [x] Nodes Stats
- [x] Nodes Info
- [ ] Nodes Feature Usage
- [ ] Remote Cluster Info
- [x] Task Management API
- [ ] Nodes hot_threads
- [ ] Cluster Allocation Explain API
### Rollup APIs (XPack)
- [x] Create Job
- [x] Delete Job
- [x] Get Job
- [x] Start Job
- [x] Stop Job
### Query DSL
- [x] Match All Query
- [x] Inner hits
- Full text queries
- [x] Match Query
- [x] Match Boolean Prefix Query
- [x] Match Phrase Query
- [x] Match Phrase Prefix Query
- [x] Multi Match Query
- [x] Common Terms Query
- [x] Query String Query
- [x] Simple Query String Query
- [x] Combined Fields Query
- [x] Intervals Query
- Term level queries
- [x] Term Query
- [x] Terms Query
- [x] Terms Set Query
- [x] Range Query
- [x] Exists Query
- [x] Prefix Query
- [x] Wildcard Query
- [x] Regexp Query
- [x] Fuzzy Query
- [x] Type Query
- [x] Ids Query
- Compound queries
- [x] Constant Score Query
- [x] Bool Query
- [x] Dis Max Query
- [x] Function Score Query
- [x] Boosting Query
- Joining queries
- [x] Nested Query
- [x] Has Child Query
- [x] Has Parent Query
- [x] Parent Id Query
- Geo queries
- [ ] GeoShape Query
- [x] Geo Bounding Box Query
- [x] Geo Distance Query
- [x] Geo Polygon Query
- Specialized queries
- [x] Distance Feature Query
- [x] More Like This Query
- [x] Script Query
- [x] Script Score Query
- [x] Percolate Query
- Span queries
- [x] Span Term Query
- [ ] Span Multi Term Query
- [x] Span First Query
- [x] Span Near Query
- [ ] Span Or Query
- [ ] Span Not Query
- [ ] Span Containing Query
- [ ] Span Within Query
- [ ] Span Field Masking Query
- [ ] Minimum Should Match
- [ ] Multi Term Query Rewrite
### Modules
- Snapshot and Restore
- [x] Repositories
- [x] Snapshot get
- [x] Snapshot create
- [x] Snapshot delete
- [ ] Restore
- [ ] Snapshot status
- [ ] Monitoring snapshot/restore status
- [ ] Stopping currently running snapshot and restore
- Scripting
- [x] GetScript
- [x] PutScript
- [x] DeleteScript
### Sorting
- [x] Sort by score
- [x] Sort by field
- [x] Sort by geo distance
- [x] Sort by script
- [x] Sort by doc
### Scrolling
Scrolling is supported via a `ScrollService`. It supports an iterator-like interface.
The `ClearScroll` API is implemented as well.
A pattern for [efficiently scrolling in parallel](https://github.com/olivere/elastic/wiki/ScrollParallel)
is described in the [Wiki](https://github.com/olivere/elastic/wiki).
## How to contribute
Read [the contribution guidelines](https://github.com/olivere/elastic/blob/master/CONTRIBUTING.md).
## Credits
Thanks a lot for the great folks working hard on
[Elasticsearch](https://www.elastic.co/products/elasticsearch)
and
[Go](https://golang.org/).
Elastic uses portions of the
[uritemplates](https://github.com/jtacoma/uritemplates) library
by Joshua Tacoma,
[backoff](https://github.com/cenkalti/backoff) by Cenk Altı and
[leaktest](https://github.com/fortytw2/leaktest) by Ian Chiles.
## LICENSE
MIT-LICENSE. See [LICENSE](http://olivere.mit-license.org/)
or the LICENSE file provided in the repository for details.
================================================
FILE: acknowledged_response.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// AcknowledgedResponse is returned from various APIs. It simply indicates
// whether the operation is acknowledged or not.
type AcknowledgedResponse struct {
Acknowledged bool `json:"acknowledged"`
ShardsAcknowledged bool `json:"shards_acknowledged"`
Index string `json:"index,omitempty"`
}
================================================
FILE: aws/sign_v4.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package aws
import (
"net/http"
"github.com/smartystreets/go-aws-auth"
)
// NewV4SigningClient returns an *http.Client that will sign all requests with AWS V4 Signing.
func NewV4SigningClient(credentials awsauth.Credentials) *http.Client {
return NewV4SigningClientWithHTTPClient(credentials, http.DefaultClient)
}
// NewV4SigningClientWithHTTPClient returns an *http.Client that will sign all requests with AWS V4 Signing.
func NewV4SigningClientWithHTTPClient(credentials awsauth.Credentials, httpClient *http.Client) *http.Client {
return &http.Client{
Transport: V4Transport{
HTTPClient: httpClient,
Credentials: credentials,
},
}
}
// V4Transport is a RoundTripper that will sign requests with AWS V4 Signing
type V4Transport struct {
HTTPClient *http.Client
Credentials awsauth.Credentials
}
// RoundTrip uses the underlying RoundTripper transport, but signs request first with AWS V4 Signing
func (st V4Transport) RoundTrip(req *http.Request) (*http.Response, error) {
// Instead of directly modifying the request then calling http.DefaultTransport,
// instead restart the request with the HTTPClient.Do function,
// because the HTTPClient includes safeguards around not forwarding the
// signed Authorization header to untrusted domains.
return st.HTTPClient.Do(awsauth.Sign4(req, st.Credentials))
}
================================================
FILE: aws/sign_v4_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package aws
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"testing"
awsauth "github.com/smartystreets/go-aws-auth"
"github.com/olivere/elastic/v7"
)
func TestSigningClient(t *testing.T) {
var req *http.Request
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/":
req = r // capture the HTTP request
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, `{
"name" : "Qg28M36",
"cluster_name" : "docker-cluster",
"cluster_uuid" : "rwHa7BBnRC2h8KoDfCbmuQ",
"version" : {
"number" : "6.3.2",
"build_flavor" : "oss",
"build_type" : "tar",
"build_hash" : "053779d",
"build_date" : "2018-07-20T05:20:23.451332Z",
"build_snapshot" : false,
"lucene_version" : "7.3.1",
"minimum_wire_compatibility_version" : "5.6.0",
"minimum_index_compatibility_version" : "5.0.0"
},
"tagline" : "You Know, for Search"
}`)
return
default:
w.WriteHeader(http.StatusInternalServerError)
return
}
}))
defer ts.Close()
cred := awsauth.Credentials{
AccessKeyID: "dev",
SecretAccessKey: "secret",
}
signingClient := NewV4SigningClient(cred)
// Create a simple Ping request via Elastic
client, err := elastic.NewClient(
elastic.SetURL(ts.URL),
elastic.SetHttpClient(signingClient),
elastic.SetHealthcheck(false),
elastic.SetSniff(false),
)
if err != nil {
t.Fatal(err)
}
res, code, err := client.Ping(ts.URL).Do(context.Background())
if err != nil {
t.Fatal(err)
}
if want, have := http.StatusOK, code; want != have {
t.Fatalf("want Status=%d, have %d", want, have)
}
if want, have := "You Know, for Search", res.TagLine; want != have {
t.Fatalf("want TagLine=%q, have %q", want, have)
}
// Check the request recorded in the HTTP test server (see above)
if req == nil {
t.Fatal("expected to capture HTTP request")
}
if have := req.Header.Get("Authorization"); have == "" {
t.Fatal("expected Authorization header")
}
if have := req.Header.Get("X-Amz-Date"); have == "" {
t.Fatal("expected X-Amz-Date header")
}
if want, have := `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855`, req.Header.Get("X-Amz-Content-Sha256"); want != have {
t.Fatalf("want header of X-Amz-Content-Sha256=%q, have %q", want, have)
}
}
================================================
FILE: aws/v4/CREDITS
================================================
This package contains code that is Copyright (c) 2016 Anthony Atkinson
and licensed under the MIT license.
See https://github.com/sha1sum/aws_signing_client.
================================================
FILE: aws/v4/aws_v4.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package v4
import (
"bytes"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
"time"
"github.com/aws/aws-sdk-go/aws/credentials"
v4 "github.com/aws/aws-sdk-go/aws/signer/v4"
)
// NewV4SigningClient returns an *http.Client that will sign all requests with AWS V4 Signing.
func NewV4SigningClient(creds *credentials.Credentials, region string) *http.Client {
return NewV4SigningClientWithHTTPClient(creds, region, http.DefaultClient)
}
// NewV4SigningClientWithHTTPClient returns an *http.Client that will sign all requests with AWS V4 Signing.
func NewV4SigningClientWithHTTPClient(creds *credentials.Credentials, region string, httpClient *http.Client) *http.Client {
return &http.Client{
Transport: Transport{
client: httpClient,
creds: creds,
signer: v4.NewSigner(creds),
region: region,
},
}
}
// Transport is a RoundTripper that will sign requests with AWS V4 Signing
type Transport struct {
client *http.Client
creds *credentials.Credentials
signer *v4.Signer
region string
}
// RoundTrip uses the underlying RoundTripper transport, but signs request first with AWS V4 Signing
func (st Transport) RoundTrip(req *http.Request) (*http.Response, error) {
if h, ok := req.Header["Authorization"]; ok && len(h) > 0 && strings.HasPrefix(h[0], "AWS4") {
// Received a signed request, just pass it on.
return st.client.Do(req)
}
if strings.Contains(req.URL.RawPath, "%2C") {
// Escaping path
req.URL.RawPath = url.PathEscape(req.URL.RawPath)
}
now := time.Now().UTC()
req.Header.Set("Date", now.Format(time.RFC3339))
var err error
switch req.Body {
case nil:
_, err = st.signer.Sign(req, nil, "es", st.region, now)
default:
switch body := req.Body.(type) {
case io.ReadSeeker:
_, err = st.signer.Sign(req, body, "es", st.region, now)
default:
buf, err := ioutil.ReadAll(req.Body)
if err != nil {
return nil, err
}
req.Body = ioutil.NopCloser(bytes.NewReader(buf))
_, err = st.signer.Sign(req, bytes.NewReader(buf), "es", st.region, time.Now().UTC())
}
}
if err != nil {
return nil, err
}
return st.client.Do(req)
}
================================================
FILE: aws/v4/aws_v4_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package v4
import (
"context"
"crypto/tls"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/olivere/elastic/v7"
)
func TestSigningClient(t *testing.T) {
var req *http.Request
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/":
req = r // capture the HTTP request
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, `{
"name" : "Qg28M36",
"cluster_name" : "docker-cluster",
"cluster_uuid" : "rwHa7BBnRC2h8KoDfCbmuQ",
"version" : {
"number" : "6.3.2",
"build_flavor" : "oss",
"build_type" : "tar",
"build_hash" : "053779d",
"build_date" : "2018-07-20T05:20:23.451332Z",
"build_snapshot" : false,
"lucene_version" : "7.3.1",
"minimum_wire_compatibility_version" : "5.6.0",
"minimum_index_compatibility_version" : "5.0.0"
},
"tagline" : "You Know, for Search"
}`)
return
default:
w.WriteHeader(http.StatusInternalServerError)
return
}
}))
defer ts.Close()
cred := credentials.NewStaticCredentials("dev", "secret", "")
// Don't do this in production!
insecureHttpClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
}
signingClient := NewV4SigningClientWithHTTPClient(cred, "us-east-1", insecureHttpClient)
// Create a simple Ping request via Elastic
client, err := elastic.NewClient(
elastic.SetURL(ts.URL),
elastic.SetHttpClient(signingClient),
elastic.SetHealthcheck(false),
elastic.SetSniff(false),
)
if err != nil {
t.Fatal(err)
}
res, code, err := client.Ping(ts.URL).Do(context.Background())
if err != nil {
t.Fatal(err)
}
if want, have := http.StatusOK, code; want != have {
t.Fatalf("want Status=%d, have %d", want, have)
}
if want, have := "You Know, for Search", res.TagLine; want != have {
t.Fatalf("want TagLine=%q, have %q", want, have)
}
// Check the request recorded in the HTTP test server (see above)
if req == nil {
t.Fatal("expected to capture HTTP request")
}
if have := req.Header.Get("Authorization"); have == "" {
t.Fatal("expected Authorization header")
}
if have := req.Header.Get("X-Amz-Date"); have == "" {
t.Fatal("expected X-Amz-Date header")
}
/*
if want, have := `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855`, req.Header.Get("X-Amz-Content-Sha256"); want != have {
t.Fatalf("want header of X-Amz-Content-Sha256=%q, have %q", want, have)
}
*/
}
================================================
FILE: backoff.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"math"
"math/rand"
"sync"
"time"
)
// BackoffFunc specifies the signature of a function that returns the
// time to wait before the next call to a resource. To stop retrying
// return false in the 2nd return value.
type BackoffFunc func(retry int) (time.Duration, bool)
// Backoff allows callers to implement their own Backoff strategy.
type Backoff interface {
// Next implements a BackoffFunc.
Next(retry int) (time.Duration, bool)
}
// -- ZeroBackoff --
// ZeroBackoff is a fixed backoff policy whose backoff time is always zero,
// meaning that the operation is retried immediately without waiting,
// indefinitely.
type ZeroBackoff struct{}
// Next implements BackoffFunc for ZeroBackoff.
func (b ZeroBackoff) Next(retry int) (time.Duration, bool) {
return 0, true
}
// -- StopBackoff --
// StopBackoff is a fixed backoff policy that always returns false for
// Next(), meaning that the operation should never be retried.
type StopBackoff struct{}
// Next implements BackoffFunc for StopBackoff.
func (b StopBackoff) Next(retry int) (time.Duration, bool) {
return 0, false
}
// -- ConstantBackoff --
// ConstantBackoff is a backoff policy that always returns the same delay.
type ConstantBackoff struct {
interval time.Duration
}
// NewConstantBackoff returns a new ConstantBackoff.
func NewConstantBackoff(interval time.Duration) *ConstantBackoff {
return &ConstantBackoff{interval: interval}
}
// Next implements BackoffFunc for ConstantBackoff.
func (b *ConstantBackoff) Next(retry int) (time.Duration, bool) {
return b.interval, true
}
// -- Exponential --
// ExponentialBackoff implements the simple exponential backoff described by
// Douglas Thain at http://dthain.blogspot.de/2009/02/exponential-backoff-in-distributed.html.
type ExponentialBackoff struct {
t float64 // initial timeout (in msec)
f float64 // exponential factor (e.g. 2)
m float64 // maximum timeout (in msec)
}
// NewExponentialBackoff returns a ExponentialBackoff backoff policy.
// Use initialTimeout to set the first/minimal interval
// and maxTimeout to set the maximum wait interval.
func NewExponentialBackoff(initialTimeout, maxTimeout time.Duration) *ExponentialBackoff {
return &ExponentialBackoff{
t: float64(int64(initialTimeout / time.Millisecond)),
f: 2.0,
m: float64(int64(maxTimeout / time.Millisecond)),
}
}
// Next implements BackoffFunc for ExponentialBackoff.
func (b *ExponentialBackoff) Next(retry int) (time.Duration, bool) {
r := 1.0 + rand.Float64() // random number in [1..2]
m := math.Min(r*b.t*math.Pow(b.f, float64(retry)), b.m)
if m >= b.m {
return 0, false
}
d := time.Duration(int64(m)) * time.Millisecond
return d, true
}
// -- Simple Backoff --
// SimpleBackoff takes a list of fixed values for backoff intervals.
// Each call to Next returns the next value from that fixed list.
// After each value is returned, subsequent calls to Next will only return
// the last element. The values are optionally "jittered" (off by default).
type SimpleBackoff struct {
sync.Mutex
ticks []int
jitter bool
}
// NewSimpleBackoff creates a SimpleBackoff algorithm with the specified
// list of fixed intervals in milliseconds.
func NewSimpleBackoff(ticks ...int) *SimpleBackoff {
return &SimpleBackoff{
ticks: ticks,
jitter: false,
}
}
// Jitter enables or disables jittering values.
func (b *SimpleBackoff) Jitter(flag bool) *SimpleBackoff {
b.Lock()
b.jitter = flag
b.Unlock()
return b
}
// jitter randomizes the interval to return a value of [0.5*millis .. 1.5*millis].
func jitter(millis int) int {
if millis <= 0 {
return 0
}
return millis/2 + rand.Intn(millis)
}
// Next implements BackoffFunc for SimpleBackoff.
func (b *SimpleBackoff) Next(retry int) (time.Duration, bool) {
b.Lock()
defer b.Unlock()
if retry >= len(b.ticks) {
return 0, false
}
ms := b.ticks[retry]
if b.jitter {
ms = jitter(ms)
}
return time.Duration(ms) * time.Millisecond, true
}
================================================
FILE: backoff_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"math/rand"
"testing"
"time"
)
func TestZeroBackoff(t *testing.T) {
b := ZeroBackoff{}
_, ok := b.Next(0)
if !ok {
t.Fatalf("expected %v, got %v", true, ok)
}
}
func TestStopBackoff(t *testing.T) {
b := StopBackoff{}
_, ok := b.Next(0)
if ok {
t.Fatalf("expected %v, got %v", false, ok)
}
}
func TestConstantBackoff(t *testing.T) {
b := NewConstantBackoff(time.Second)
d, ok := b.Next(0)
if !ok {
t.Fatalf("expected %v, got %v", true, ok)
}
if d != time.Second {
t.Fatalf("expected %v, got %v", time.Second, d)
}
}
func TestSimpleBackoff(t *testing.T) {
var tests = []struct {
Duration time.Duration
Continue bool
}{
// #0
{
Duration: 1 * time.Millisecond,
Continue: true,
},
// #1
{
Duration: 2 * time.Millisecond,
Continue: true,
},
// #2
{
Duration: 7 * time.Millisecond,
Continue: true,
},
// #3
{
Duration: 0,
Continue: false,
},
// #4
{
Duration: 0,
Continue: false,
},
}
b := NewSimpleBackoff(1, 2, 7)
for i, tt := range tests {
d, ok := b.Next(i)
if got, want := ok, tt.Continue; got != want {
t.Fatalf("#%d: expected %v, got %v", i, want, got)
}
if got, want := d, tt.Duration; got != want {
t.Fatalf("#%d: expected %v, got %v", i, want, got)
}
}
}
func TestExponentialBackoff(t *testing.T) {
rand.Seed(time.Now().UnixNano())
min := time.Duration(8) * time.Millisecond
max := time.Duration(256) * time.Millisecond
b := NewExponentialBackoff(min, max)
between := func(value time.Duration, a, b int) bool {
x := int(value / time.Millisecond)
return a <= x && x <= b
}
got, ok := b.Next(0)
if !ok {
t.Fatalf("expected %v, got %v", true, ok)
}
if !between(got, 8, 256) {
t.Errorf("expected [%v..%v], got %v", 8, 256, got)
}
got, ok = b.Next(1)
if !ok {
t.Fatalf("expected %v, got %v", true, ok)
}
if !between(got, 8, 256) {
t.Errorf("expected [%v..%v], got %v", 8, 256, got)
}
got, ok = b.Next(2)
if !ok {
t.Fatalf("expected %v, got %v", true, ok)
}
if !between(got, 8, 256) {
t.Errorf("expected [%v..%v], got %v", 8, 256, got)
}
got, ok = b.Next(3)
if !ok {
t.Fatalf("expected %v, got %v", true, ok)
}
if !between(got, 8, 256) {
t.Errorf("expected [%v..%v], got %v", 8, 256, got)
}
got, ok = b.Next(4)
if !ok {
t.Fatalf("expected %v, got %v", true, ok)
}
if !between(got, 8, 256) {
t.Errorf("expected [%v..%v], got %v", 8, 256, got)
}
if _, ok := b.Next(5); ok {
t.Fatalf("expected %v, got %v", false, ok)
}
if _, ok = b.Next(6); ok {
t.Fatalf("expected %v, got %v", false, ok)
}
}
================================================
FILE: bulk.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"errors"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// BulkService allows for batching bulk requests and sending them to
// Elasticsearch in one roundtrip. Use the Add method with BulkIndexRequest,
// BulkUpdateRequest, and BulkDeleteRequest to add bulk requests to a batch,
// then use Do to send them to Elasticsearch.
//
// BulkService will be reset after each Do call. In other words, you can
// reuse BulkService to send many batches. You do not have to create a new
// BulkService for each batch.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-bulk.html
// for more details.
type BulkService struct {
client *Client
retrier Retrier
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
index string
typ string
requests []BulkableRequest
pipeline string
timeout string
refresh string
routing string
waitForActiveShards string
// estimated bulk size in bytes, up to the request index sizeInBytesCursor
sizeInBytes int64
sizeInBytesCursor int
}
// NewBulkService initializes a new BulkService.
func NewBulkService(client *Client) *BulkService {
builder := &BulkService{
client: client,
}
return builder
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *BulkService) Pretty(pretty bool) *BulkService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *BulkService) Human(human bool) *BulkService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *BulkService) ErrorTrace(errorTrace bool) *BulkService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *BulkService) FilterPath(filterPath ...string) *BulkService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *BulkService) Header(name string, value string) *BulkService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *BulkService) Headers(headers http.Header) *BulkService {
s.headers = headers
return s
}
// Reset cleans up the request queue
func (s *BulkService) Reset() {
s.requests = make([]BulkableRequest, 0)
s.sizeInBytes = 0
s.sizeInBytesCursor = 0
}
// Retrier allows to set specific retry logic for this BulkService.
// If not specified, it will use the client's default retrier.
func (s *BulkService) Retrier(retrier Retrier) *BulkService {
s.retrier = retrier
return s
}
// Index specifies the index to use for all batches. You may also leave
// this blank and specify the index in the individual bulk requests.
func (s *BulkService) Index(index string) *BulkService {
s.index = index
return s
}
// Type specifies the type to use for all batches. You may also leave
// this blank and specify the type in the individual bulk requests.
func (s *BulkService) Type(typ string) *BulkService {
s.typ = typ
return s
}
// Timeout is a global timeout for processing bulk requests. This is a
// server-side timeout, i.e. it tells Elasticsearch the time after which
// it should stop processing.
func (s *BulkService) Timeout(timeout string) *BulkService {
s.timeout = timeout
return s
}
// Refresh controls when changes made by this request are made visible
// to search. The allowed values are: "true" (refresh the relevant
// primary and replica shards immediately), "wait_for" (wait for the
// changes to be made visible by a refresh before replying), or "false"
// (no refresh related actions). The default value is "false".
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-refresh.html
// for details.
func (s *BulkService) Refresh(refresh string) *BulkService {
s.refresh = refresh
return s
}
// Routing specifies the routing value.
func (s *BulkService) Routing(routing string) *BulkService {
s.routing = routing
return s
}
// Pipeline specifies the pipeline id to preprocess incoming documents with.
func (s *BulkService) Pipeline(pipeline string) *BulkService {
s.pipeline = pipeline
return s
}
// WaitForActiveShards sets the number of shard copies that must be active
// before proceeding with the bulk operation. Defaults to 1, meaning the
// primary shard only. Set to `all` for all shard copies, otherwise set to
// any non-negative value less than or equal to the total number of copies
// for the shard (number of replicas + 1).
func (s *BulkService) WaitForActiveShards(waitForActiveShards string) *BulkService {
s.waitForActiveShards = waitForActiveShards
return s
}
// Add adds bulkable requests, i.e. BulkIndexRequest, BulkUpdateRequest,
// and/or BulkDeleteRequest.
func (s *BulkService) Add(requests ...BulkableRequest) *BulkService {
s.requests = append(s.requests, requests...)
return s
}
// EstimatedSizeInBytes returns the estimated size of all bulkable
// requests added via Add.
func (s *BulkService) EstimatedSizeInBytes() int64 {
if s.sizeInBytesCursor == len(s.requests) {
return s.sizeInBytes
}
for _, r := range s.requests[s.sizeInBytesCursor:] {
s.sizeInBytes += s.estimateSizeInBytes(r)
s.sizeInBytesCursor++
}
return s.sizeInBytes
}
// estimateSizeInBytes returns the estimates size of the given
// bulkable request, i.e. BulkIndexRequest, BulkUpdateRequest, and
// BulkDeleteRequest.
func (s *BulkService) estimateSizeInBytes(r BulkableRequest) int64 {
lines, _ := r.Source()
size := 0
for _, line := range lines {
// +1 for the \n
size += len(line) + 1
}
return int64(size)
}
// NumberOfActions returns the number of bulkable requests that need to
// be sent to Elasticsearch on the next batch.
func (s *BulkService) NumberOfActions() int {
return len(s.requests)
}
func (s *BulkService) bodyAsString() (string, error) {
// Pre-allocate to reduce allocs
var buf strings.Builder
buf.Grow(int(s.EstimatedSizeInBytes()))
for _, req := range s.requests {
source, err := req.Source()
if err != nil {
return "", err
}
for _, line := range source {
buf.WriteString(line)
buf.WriteByte('\n')
}
}
return buf.String(), nil
}
// Do sends the batched requests to Elasticsearch. Note that, when successful,
// you can reuse the BulkService for the next batch as the list of bulk
// requests is cleared on success.
func (s *BulkService) Do(ctx context.Context) (*BulkResponse, error) {
// No actions?
if s.NumberOfActions() == 0 {
return nil, errors.New("elastic: No bulk actions to commit")
}
// Get body
body, err := s.bodyAsString()
if err != nil {
return nil, err
}
// Build url
path := "/"
if len(s.index) > 0 {
index, err := uritemplates.Expand("{index}", map[string]string{
"index": s.index,
})
if err != nil {
return nil, err
}
path += index + "/"
}
if len(s.typ) > 0 {
typ, err := uritemplates.Expand("{type}", map[string]string{
"type": s.typ,
})
if err != nil {
return nil, err
}
path += typ + "/"
}
path += "_bulk"
// Parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.pipeline != "" {
params.Set("pipeline", s.pipeline)
}
if s.refresh != "" {
params.Set("refresh", s.refresh)
}
if s.routing != "" {
params.Set("routing", s.routing)
}
if s.timeout != "" {
params.Set("timeout", s.timeout)
}
if s.waitForActiveShards != "" {
params.Set("wait_for_active_shards", s.waitForActiveShards)
}
// Get response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "POST",
Path: path,
Params: params,
Body: body,
ContentType: "application/x-ndjson",
Retrier: s.retrier,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return results
ret := new(BulkResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
// Reset so the request can be reused
s.Reset()
return ret, nil
}
// BulkResponse is a response to a bulk execution.
//
// Example:
// {
// "took":3,
// "errors":false,
// "items":[{
// "index":{
// "_index":"index1",
// "_type":"tweet",
// "_id":"1",
// "_version":3,
// "status":201
// }
// },{
// "index":{
// "_index":"index2",
// "_type":"tweet",
// "_id":"2",
// "_version":3,
// "status":200
// }
// },{
// "delete":{
// "_index":"index1",
// "_type":"tweet",
// "_id":"1",
// "_version":4,
// "status":200,
// "found":true
// }
// },{
// "update":{
// "_index":"index2",
// "_type":"tweet",
// "_id":"2",
// "_version":4,
// "status":200
// }
// }]
// }
type BulkResponse struct {
Took int `json:"took,omitempty"`
Errors bool `json:"errors,omitempty"`
Items []map[string]*BulkResponseItem `json:"items,omitempty"`
}
// BulkResponseItem is the result of a single bulk request.
type BulkResponseItem struct {
Index string `json:"_index,omitempty"`
Type string `json:"_type,omitempty"`
Id string `json:"_id,omitempty"`
Version int64 `json:"_version,omitempty"`
Result string `json:"result,omitempty"`
Shards *ShardsInfo `json:"_shards,omitempty"`
SeqNo int64 `json:"_seq_no,omitempty"`
PrimaryTerm int64 `json:"_primary_term,omitempty"`
Status int `json:"status,omitempty"`
ForcedRefresh bool `json:"forced_refresh,omitempty"`
Error *ErrorDetails `json:"error,omitempty"`
GetResult *GetResult `json:"get,omitempty"`
}
// Indexed returns all bulk request results of "index" actions.
func (r *BulkResponse) Indexed() []*BulkResponseItem {
return r.ByAction("index")
}
// Created returns all bulk request results of "create" actions.
func (r *BulkResponse) Created() []*BulkResponseItem {
return r.ByAction("create")
}
// Updated returns all bulk request results of "update" actions.
func (r *BulkResponse) Updated() []*BulkResponseItem {
return r.ByAction("update")
}
// Deleted returns all bulk request results of "delete" actions.
func (r *BulkResponse) Deleted() []*BulkResponseItem {
return r.ByAction("delete")
}
// ByAction returns all bulk request results of a certain action,
// e.g. "index" or "delete".
func (r *BulkResponse) ByAction(action string) []*BulkResponseItem {
if r.Items == nil {
return nil
}
var items []*BulkResponseItem
for _, item := range r.Items {
if result, found := item[action]; found {
items = append(items, result)
}
}
return items
}
// ById returns all bulk request results of a given document id,
// regardless of the action ("index", "delete" etc.).
func (r *BulkResponse) ById(id string) []*BulkResponseItem {
if r.Items == nil {
return nil
}
var items []*BulkResponseItem
for _, item := range r.Items {
for _, result := range item {
if result.Id == id {
items = append(items, result)
}
}
}
return items
}
// Failed returns those items of a bulk response that have errors,
// i.e. those that don't have a status code between 200 and 299.
func (r *BulkResponse) Failed() []*BulkResponseItem {
if r.Items == nil {
return nil
}
var errors []*BulkResponseItem
for _, item := range r.Items {
for _, result := range item {
if !(result.Status >= 200 && result.Status <= 299) {
errors = append(errors, result)
}
}
}
return errors
}
// Succeeded returns those items of a bulk response that have no errors,
// i.e. those have a status code between 200 and 299.
func (r *BulkResponse) Succeeded() []*BulkResponseItem {
if r.Items == nil {
return nil
}
var succeeded []*BulkResponseItem
for _, item := range r.Items {
for _, result := range item {
if result.Status >= 200 && result.Status <= 299 {
succeeded = append(succeeded, result)
}
}
}
return succeeded
}
================================================
FILE: bulk_create_request.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
//go:generate easyjson bulk_create_request.go
import (
"encoding/json"
"fmt"
"strings"
)
// BulkCreateRequest is a request to add a new document to Elasticsearch.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-bulk.html
// for details.
type BulkCreateRequest struct {
BulkableRequest
index string
typ string
id string
opType string
routing string
parent string
version *int64 // default is MATCH_ANY
versionType string // default is "internal"
doc interface{}
pipeline string
retryOnConflict *int
ifSeqNo *int64
ifPrimaryTerm *int64
source []string
useEasyJSON bool
}
//easyjson:json
type bulkCreateRequestCommand map[string]bulkCreateRequestCommandOp
//easyjson:json
type bulkCreateRequestCommandOp struct {
Index string `json:"_index,omitempty"`
Id string `json:"_id,omitempty"`
Type string `json:"_type,omitempty"`
Parent string `json:"parent,omitempty"`
// RetryOnConflict is "_retry_on_conflict" for 6.0 and "retry_on_conflict" for 6.1+.
RetryOnConflict *int `json:"retry_on_conflict,omitempty"`
Routing string `json:"routing,omitempty"`
Version *int64 `json:"version,omitempty"`
VersionType string `json:"version_type,omitempty"`
Pipeline string `json:"pipeline,omitempty"`
IfSeqNo *int64 `json:"if_seq_no,omitempty"`
IfPrimaryTerm *int64 `json:"if_primary_term,omitempty"`
}
// NewBulkCreateRequest returns a new BulkCreateRequest.
// The operation type is "create" by default.
func NewBulkCreateRequest() *BulkCreateRequest {
return &BulkCreateRequest{
opType: "create",
}
}
// UseEasyJSON is an experimental setting that enables serialization
// with github.com/mailru/easyjson, which should in faster serialization
// time and less allocations, but removed compatibility with encoding/json,
// usage of unsafe etc. See https://github.com/mailru/easyjson#issues-notes-and-limitations
// for details. This setting is disabled by default.
func (r *BulkCreateRequest) UseEasyJSON(enable bool) *BulkCreateRequest {
r.useEasyJSON = enable
return r
}
// Index specifies the Elasticsearch index to use for this create request.
// If unspecified, the index set on the BulkService will be used.
func (r *BulkCreateRequest) Index(index string) *BulkCreateRequest {
r.index = index
r.source = nil
return r
}
// Type specifies the Elasticsearch type to use for this create request.
// If unspecified, the type set on the BulkService will be used.
func (r *BulkCreateRequest) Type(typ string) *BulkCreateRequest {
r.typ = typ
r.source = nil
return r
}
// Id specifies the identifier of the document to create.
func (r *BulkCreateRequest) Id(id string) *BulkCreateRequest {
r.id = id
r.source = nil
return r
}
// Routing specifies a routing value for the request.
func (r *BulkCreateRequest) Routing(routing string) *BulkCreateRequest {
r.routing = routing
r.source = nil
return r
}
// Parent specifies the identifier of the parent document (if available).
func (r *BulkCreateRequest) Parent(parent string) *BulkCreateRequest {
r.parent = parent
r.source = nil
return r
}
// Version indicates the version of the document as part of an optimistic
// concurrency model.
func (r *BulkCreateRequest) Version(version int64) *BulkCreateRequest {
v := version
r.version = &v
r.source = nil
return r
}
// VersionType specifies how versions are created. It can be e.g. internal,
// external, external_gte, or force.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-index_.html#index-versioning
// for details.
func (r *BulkCreateRequest) VersionType(versionType string) *BulkCreateRequest {
r.versionType = versionType
r.source = nil
return r
}
// Doc specifies the document to create.
func (r *BulkCreateRequest) Doc(doc interface{}) *BulkCreateRequest {
r.doc = doc
r.source = nil
return r
}
// RetryOnConflict specifies how often to retry in case of a version conflict.
func (r *BulkCreateRequest) RetryOnConflict(retryOnConflict int) *BulkCreateRequest {
r.retryOnConflict = &retryOnConflict
r.source = nil
return r
}
// Pipeline to use while processing the request.
func (r *BulkCreateRequest) Pipeline(pipeline string) *BulkCreateRequest {
r.pipeline = pipeline
r.source = nil
return r
}
// IfSeqNo indicates to only perform the create operation if the last
// operation that has changed the document has the specified sequence number.
func (r *BulkCreateRequest) IfSeqNo(ifSeqNo int64) *BulkCreateRequest {
r.ifSeqNo = &ifSeqNo
return r
}
// IfPrimaryTerm indicates to only perform the create operation if the
// last operation that has changed the document has the specified primary term.
func (r *BulkCreateRequest) IfPrimaryTerm(ifPrimaryTerm int64) *BulkCreateRequest {
r.ifPrimaryTerm = &ifPrimaryTerm
return r
}
// String returns the on-wire representation of the create request,
// concatenated as a single string.
func (r *BulkCreateRequest) String() string {
lines, err := r.Source()
if err != nil {
return fmt.Sprintf("error: %v", err)
}
return strings.Join(lines, "\n")
}
// Source returns the on-wire representation of the create request,
// split into an action-and-meta-data line and an (optional) source line.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-bulk.html
// for details.
func (r *BulkCreateRequest) Source() ([]string, error) {
// { "create" : { "_index" : "test", "_type" : "type1", "_id" : "1" } }
// { "field1" : "value1" }
if r.source != nil {
return r.source, nil
}
lines := make([]string, 2)
// "index" ...
indexCommand := bulkCreateRequestCommandOp{
Index: r.index,
Type: r.typ,
Id: r.id,
Routing: r.routing,
Parent: r.parent,
Version: r.version,
VersionType: r.versionType,
RetryOnConflict: r.retryOnConflict,
Pipeline: r.pipeline,
IfSeqNo: r.ifSeqNo,
IfPrimaryTerm: r.ifPrimaryTerm,
}
command := bulkCreateRequestCommand{
r.opType: indexCommand,
}
var err error
var body []byte
if r.useEasyJSON {
// easyjson
body, err = command.MarshalJSON()
} else {
// encoding/json
body, err = json.Marshal(command)
}
if err != nil {
return nil, err
}
lines[0] = string(body)
// "field1" ...
if r.doc != nil {
switch t := r.doc.(type) {
default:
body, err := json.Marshal(r.doc)
if err != nil {
return nil, err
}
lines[1] = string(body)
case json.RawMessage:
lines[1] = string(t)
case *json.RawMessage:
lines[1] = string(*t)
case string:
lines[1] = t
case *string:
lines[1] = *t
}
} else {
lines[1] = "{}"
}
r.source = lines
return lines, nil
}
================================================
FILE: bulk_create_request_easyjson.go
================================================
// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT.
package elastic
import (
json "encoding/json"
easyjson "github.com/mailru/easyjson"
jlexer "github.com/mailru/easyjson/jlexer"
jwriter "github.com/mailru/easyjson/jwriter"
)
// suppress unused package warning
var (
_ *json.RawMessage
_ *jlexer.Lexer
_ *jwriter.Writer
_ easyjson.Marshaler
)
func easyjson29a8ef77DecodeGithubComOlivereElasticV7(in *jlexer.Lexer, out *bulkCreateRequestCommandOp) {
isTopLevel := in.IsStart()
if in.IsNull() {
if isTopLevel {
in.Consumed()
}
in.Skip()
return
}
in.Delim('{')
for !in.IsDelim('}') {
key := in.UnsafeFieldName(false)
in.WantColon()
if in.IsNull() {
in.Skip()
in.WantComma()
continue
}
switch key {
case "_index":
out.Index = string(in.String())
case "_id":
out.Id = string(in.String())
case "_type":
out.Type = string(in.String())
case "parent":
out.Parent = string(in.String())
case "retry_on_conflict":
if in.IsNull() {
in.Skip()
out.RetryOnConflict = nil
} else {
if out.RetryOnConflict == nil {
out.RetryOnConflict = new(int)
}
*out.RetryOnConflict = int(in.Int())
}
case "routing":
out.Routing = string(in.String())
case "version":
if in.IsNull() {
in.Skip()
out.Version = nil
} else {
if out.Version == nil {
out.Version = new(int64)
}
*out.Version = int64(in.Int64())
}
case "version_type":
out.VersionType = string(in.String())
case "pipeline":
out.Pipeline = string(in.String())
case "if_seq_no":
if in.IsNull() {
in.Skip()
out.IfSeqNo = nil
} else {
if out.IfSeqNo == nil {
out.IfSeqNo = new(int64)
}
*out.IfSeqNo = int64(in.Int64())
}
case "if_primary_term":
if in.IsNull() {
in.Skip()
out.IfPrimaryTerm = nil
} else {
if out.IfPrimaryTerm == nil {
out.IfPrimaryTerm = new(int64)
}
*out.IfPrimaryTerm = int64(in.Int64())
}
default:
in.SkipRecursive()
}
in.WantComma()
}
in.Delim('}')
if isTopLevel {
in.Consumed()
}
}
func easyjson29a8ef77EncodeGithubComOlivereElasticV7(out *jwriter.Writer, in bulkCreateRequestCommandOp) {
out.RawByte('{')
first := true
_ = first
if in.Index != "" {
const prefix string = ",\"_index\":"
first = false
out.RawString(prefix[1:])
out.String(string(in.Index))
}
if in.Id != "" {
const prefix string = ",\"_id\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Id))
}
if in.Type != "" {
const prefix string = ",\"_type\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Type))
}
if in.Parent != "" {
const prefix string = ",\"parent\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Parent))
}
if in.RetryOnConflict != nil {
const prefix string = ",\"retry_on_conflict\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Int(int(*in.RetryOnConflict))
}
if in.Routing != "" {
const prefix string = ",\"routing\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Routing))
}
if in.Version != nil {
const prefix string = ",\"version\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Int64(int64(*in.Version))
}
if in.VersionType != "" {
const prefix string = ",\"version_type\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.VersionType))
}
if in.Pipeline != "" {
const prefix string = ",\"pipeline\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Pipeline))
}
if in.IfSeqNo != nil {
const prefix string = ",\"if_seq_no\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Int64(int64(*in.IfSeqNo))
}
if in.IfPrimaryTerm != nil {
const prefix string = ",\"if_primary_term\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Int64(int64(*in.IfPrimaryTerm))
}
out.RawByte('}')
}
// MarshalJSON supports json.Marshaler interface
func (v bulkCreateRequestCommandOp) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{}
easyjson29a8ef77EncodeGithubComOlivereElasticV7(&w, v)
return w.Buffer.BuildBytes(), w.Error
}
// MarshalEasyJSON supports easyjson.Marshaler interface
func (v bulkCreateRequestCommandOp) MarshalEasyJSON(w *jwriter.Writer) {
easyjson29a8ef77EncodeGithubComOlivereElasticV7(w, v)
}
// UnmarshalJSON supports json.Unmarshaler interface
func (v *bulkCreateRequestCommandOp) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data}
easyjson29a8ef77DecodeGithubComOlivereElasticV7(&r, v)
return r.Error()
}
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *bulkCreateRequestCommandOp) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson29a8ef77DecodeGithubComOlivereElasticV7(l, v)
}
func easyjson29a8ef77DecodeGithubComOlivereElasticV71(in *jlexer.Lexer, out *bulkCreateRequestCommand) {
isTopLevel := in.IsStart()
if in.IsNull() {
in.Skip()
} else {
in.Delim('{')
*out = make(bulkCreateRequestCommand)
for !in.IsDelim('}') {
key := string(in.String())
in.WantColon()
var v1 bulkCreateRequestCommandOp
(v1).UnmarshalEasyJSON(in)
(*out)[key] = v1
in.WantComma()
}
in.Delim('}')
}
if isTopLevel {
in.Consumed()
}
}
func easyjson29a8ef77EncodeGithubComOlivereElasticV71(out *jwriter.Writer, in bulkCreateRequestCommand) {
if in == nil && (out.Flags&jwriter.NilMapAsEmpty) == 0 {
out.RawString(`null`)
} else {
out.RawByte('{')
v2First := true
for v2Name, v2Value := range in {
if v2First {
v2First = false
} else {
out.RawByte(',')
}
out.String(string(v2Name))
out.RawByte(':')
(v2Value).MarshalEasyJSON(out)
}
out.RawByte('}')
}
}
// MarshalJSON supports json.Marshaler interface
func (v bulkCreateRequestCommand) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{}
easyjson29a8ef77EncodeGithubComOlivereElasticV71(&w, v)
return w.Buffer.BuildBytes(), w.Error
}
// MarshalEasyJSON supports easyjson.Marshaler interface
func (v bulkCreateRequestCommand) MarshalEasyJSON(w *jwriter.Writer) {
easyjson29a8ef77EncodeGithubComOlivereElasticV71(w, v)
}
// UnmarshalJSON supports json.Unmarshaler interface
func (v *bulkCreateRequestCommand) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data}
easyjson29a8ef77DecodeGithubComOlivereElasticV71(&r, v)
return r.Error()
}
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *bulkCreateRequestCommand) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson29a8ef77DecodeGithubComOlivereElasticV71(l, v)
}
================================================
FILE: bulk_create_request_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"testing"
"time"
)
func TestBulkCreateRequestSerialization(t *testing.T) {
tests := []struct {
Request BulkableRequest
Expected []string
}{
// #0
{
Request: NewBulkCreateRequest().Index("index1").Id("1").
Doc(tweet{User: "olivere", Created: time.Date(2014, 1, 18, 23, 59, 58, 0, time.UTC)}),
Expected: []string{
`{"create":{"_index":"index1","_id":"1"}}`,
`{"user":"olivere","message":"","retweets":0,"created":"2014-01-18T23:59:58Z"}`,
},
},
// #1
{
Request: NewBulkCreateRequest().Index("index1").Id("1").
Doc(tweet{User: "olivere", Created: time.Date(2014, 1, 18, 23, 59, 58, 0, time.UTC)}),
Expected: []string{
`{"create":{"_index":"index1","_id":"1"}}`,
`{"user":"olivere","message":"","retweets":0,"created":"2014-01-18T23:59:58Z"}`,
},
},
// #2
{
Request: NewBulkCreateRequest().Index("index1").Id("1").RetryOnConflict(42).
Doc(tweet{User: "olivere", Created: time.Date(2014, 1, 18, 23, 59, 58, 0, time.UTC)}),
Expected: []string{
`{"create":{"_index":"index1","_id":"1","retry_on_conflict":42}}`,
`{"user":"olivere","message":"","retweets":0,"created":"2014-01-18T23:59:58Z"}`,
},
},
// #3
{
Request: NewBulkCreateRequest().Index("index1").Id("1").Pipeline("my_pipeline").
Doc(tweet{User: "olivere", Created: time.Date(2014, 1, 18, 23, 59, 58, 0, time.UTC)}),
Expected: []string{
`{"create":{"_index":"index1","_id":"1","pipeline":"my_pipeline"}}`,
`{"user":"olivere","message":"","retweets":0,"created":"2014-01-18T23:59:58Z"}`,
},
},
// #4
{
Request: NewBulkCreateRequest().Index("index1").Id("1").
Routing("123").
Doc(tweet{User: "olivere", Created: time.Date(2014, 1, 18, 23, 59, 58, 0, time.UTC)}),
Expected: []string{
`{"create":{"_index":"index1","_id":"1","routing":"123"}}`,
`{"user":"olivere","message":"","retweets":0,"created":"2014-01-18T23:59:58Z"}`,
},
},
// #5
{
Request: NewBulkCreateRequest().Index("index1").Type("doc").Id("1").
Version(0).
VersionType("external").
Doc(tweet{User: "olivere", Created: time.Date(2014, 1, 18, 23, 59, 58, 0, time.UTC)}),
Expected: []string{
`{"create":{"_index":"index1","_id":"1","_type":"doc","version":0,"version_type":"external"}}`,
`{"user":"olivere","message":"","retweets":0,"created":"2014-01-18T23:59:58Z"}`,
},
},
}
for i, test := range tests {
lines, err := test.Request.Source()
if err != nil {
t.Fatalf("case #%d: expected no error, got: %v", i, err)
}
if lines == nil {
t.Fatalf("case #%d: expected lines, got nil", i)
}
if len(lines) != len(test.Expected) {
t.Fatalf("case #%d: expected %d lines, got %d", i, len(test.Expected), len(lines))
}
for j, line := range lines {
if line != test.Expected[j] {
t.Errorf("case #%d: expected line #%d to be %s, got: %s", i, j, test.Expected[j], line)
}
}
}
}
var bulkCreateRequestSerializationResult string
func BenchmarkBulkCreateRequestSerialization(b *testing.B) {
b.Run("stdlib", func(b *testing.B) {
r := NewBulkCreateRequest().Index(testIndexName).Id("1").
Doc(tweet{User: "olivere", Created: time.Date(2014, 1, 18, 23, 59, 58, 0, time.UTC)})
benchmarkBulkCreateRequestSerialization(b, r.UseEasyJSON(false))
})
b.Run("easyjson", func(b *testing.B) {
r := NewBulkCreateRequest().Index(testIndexName).Id("1").
Doc(tweet{User: "olivere", Created: time.Date(2014, 1, 18, 23, 59, 58, 0, time.UTC)})
benchmarkBulkCreateRequestSerialization(b, r.UseEasyJSON(true))
})
}
func benchmarkBulkCreateRequestSerialization(b *testing.B, r *BulkCreateRequest) {
var s string
for n := 0; n < b.N; n++ {
s = r.String()
r.source = nil // Don't let caching spoil the benchmark
}
bulkCreateRequestSerializationResult = s // ensure the compiler doesn't optimize
b.ReportAllocs()
}
================================================
FILE: bulk_delete_request.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
//go:generate easyjson bulk_delete_request.go
import (
"encoding/json"
"fmt"
"strings"
)
// -- Bulk delete request --
// BulkDeleteRequest is a request to remove a document from Elasticsearch.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-bulk.html
// for details.
type BulkDeleteRequest struct {
BulkableRequest
index string
typ string
id string
parent string
routing string
version int64 // default is MATCH_ANY
versionType string // default is "internal"
ifSeqNo *int64
ifPrimaryTerm *int64
source []string
useEasyJSON bool
}
//easyjson:json
type bulkDeleteRequestCommand map[string]bulkDeleteRequestCommandOp
//easyjson:json
type bulkDeleteRequestCommandOp struct {
Index string `json:"_index,omitempty"`
Type string `json:"_type,omitempty"`
Id string `json:"_id,omitempty"`
Parent string `json:"parent,omitempty"`
Routing string `json:"routing,omitempty"`
Version int64 `json:"version,omitempty"`
VersionType string `json:"version_type,omitempty"`
IfSeqNo *int64 `json:"if_seq_no,omitempty"`
IfPrimaryTerm *int64 `json:"if_primary_term,omitempty"`
}
// NewBulkDeleteRequest returns a new BulkDeleteRequest.
func NewBulkDeleteRequest() *BulkDeleteRequest {
return &BulkDeleteRequest{}
}
// UseEasyJSON is an experimental setting that enables serialization
// with github.com/mailru/easyjson, which should in faster serialization
// time and less allocations, but removed compatibility with encoding/json,
// usage of unsafe etc. See https://github.com/mailru/easyjson#issues-notes-and-limitations
// for details. This setting is disabled by default.
func (r *BulkDeleteRequest) UseEasyJSON(enable bool) *BulkDeleteRequest {
r.useEasyJSON = enable
return r
}
// Index specifies the Elasticsearch index to use for this delete request.
// If unspecified, the index set on the BulkService will be used.
func (r *BulkDeleteRequest) Index(index string) *BulkDeleteRequest {
r.index = index
r.source = nil
return r
}
// Type specifies the Elasticsearch type to use for this delete request.
// If unspecified, the type set on the BulkService will be used.
func (r *BulkDeleteRequest) Type(typ string) *BulkDeleteRequest {
r.typ = typ
r.source = nil
return r
}
// Id specifies the identifier of the document to delete.
func (r *BulkDeleteRequest) Id(id string) *BulkDeleteRequest {
r.id = id
r.source = nil
return r
}
// Parent specifies the parent of the request, which is used in parent/child
// mappings.
func (r *BulkDeleteRequest) Parent(parent string) *BulkDeleteRequest {
r.parent = parent
r.source = nil
return r
}
// Routing specifies a routing value for the request.
func (r *BulkDeleteRequest) Routing(routing string) *BulkDeleteRequest {
r.routing = routing
r.source = nil
return r
}
// Version indicates the version to be deleted as part of an optimistic
// concurrency model.
func (r *BulkDeleteRequest) Version(version int64) *BulkDeleteRequest {
r.version = version
r.source = nil
return r
}
// VersionType can be "internal" (default), "external", "external_gte",
// or "external_gt".
func (r *BulkDeleteRequest) VersionType(versionType string) *BulkDeleteRequest {
r.versionType = versionType
r.source = nil
return r
}
// IfSeqNo indicates to only perform the delete operation if the last
// operation that has changed the document has the specified sequence number.
func (r *BulkDeleteRequest) IfSeqNo(ifSeqNo int64) *BulkDeleteRequest {
r.ifSeqNo = &ifSeqNo
return r
}
// IfPrimaryTerm indicates to only perform the delete operation if the
// last operation that has changed the document has the specified primary term.
func (r *BulkDeleteRequest) IfPrimaryTerm(ifPrimaryTerm int64) *BulkDeleteRequest {
r.ifPrimaryTerm = &ifPrimaryTerm
return r
}
// String returns the on-wire representation of the delete request,
// concatenated as a single string.
func (r *BulkDeleteRequest) String() string {
lines, err := r.Source()
if err != nil {
return fmt.Sprintf("error: %v", err)
}
return strings.Join(lines, "\n")
}
// Source returns the on-wire representation of the delete request,
// split into an action-and-meta-data line and an (optional) source line.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-bulk.html
// for details.
func (r *BulkDeleteRequest) Source() ([]string, error) {
if r.source != nil {
return r.source, nil
}
command := bulkDeleteRequestCommand{
"delete": bulkDeleteRequestCommandOp{
Index: r.index,
Type: r.typ,
Id: r.id,
Routing: r.routing,
Parent: r.parent,
Version: r.version,
VersionType: r.versionType,
IfSeqNo: r.ifSeqNo,
IfPrimaryTerm: r.ifPrimaryTerm,
},
}
var err error
var body []byte
if r.useEasyJSON {
// easyjson
body, err = command.MarshalJSON()
} else {
// encoding/json
body, err = json.Marshal(command)
}
if err != nil {
return nil, err
}
lines := []string{string(body)}
r.source = lines
return lines, nil
}
================================================
FILE: bulk_delete_request_easyjson.go
================================================
// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT.
package elastic
import (
json "encoding/json"
easyjson "github.com/mailru/easyjson"
jlexer "github.com/mailru/easyjson/jlexer"
jwriter "github.com/mailru/easyjson/jwriter"
)
// suppress unused package warning
var (
_ *json.RawMessage
_ *jlexer.Lexer
_ *jwriter.Writer
_ easyjson.Marshaler
)
func easyjson8092efb6DecodeGithubComOlivereElasticV7(in *jlexer.Lexer, out *bulkDeleteRequestCommandOp) {
isTopLevel := in.IsStart()
if in.IsNull() {
if isTopLevel {
in.Consumed()
}
in.Skip()
return
}
in.Delim('{')
for !in.IsDelim('}') {
key := in.UnsafeFieldName(false)
in.WantColon()
if in.IsNull() {
in.Skip()
in.WantComma()
continue
}
switch key {
case "_index":
out.Index = string(in.String())
case "_type":
out.Type = string(in.String())
case "_id":
out.Id = string(in.String())
case "parent":
out.Parent = string(in.String())
case "routing":
out.Routing = string(in.String())
case "version":
out.Version = int64(in.Int64())
case "version_type":
out.VersionType = string(in.String())
case "if_seq_no":
if in.IsNull() {
in.Skip()
out.IfSeqNo = nil
} else {
if out.IfSeqNo == nil {
out.IfSeqNo = new(int64)
}
*out.IfSeqNo = int64(in.Int64())
}
case "if_primary_term":
if in.IsNull() {
in.Skip()
out.IfPrimaryTerm = nil
} else {
if out.IfPrimaryTerm == nil {
out.IfPrimaryTerm = new(int64)
}
*out.IfPrimaryTerm = int64(in.Int64())
}
default:
in.SkipRecursive()
}
in.WantComma()
}
in.Delim('}')
if isTopLevel {
in.Consumed()
}
}
func easyjson8092efb6EncodeGithubComOlivereElasticV7(out *jwriter.Writer, in bulkDeleteRequestCommandOp) {
out.RawByte('{')
first := true
_ = first
if in.Index != "" {
const prefix string = ",\"_index\":"
first = false
out.RawString(prefix[1:])
out.String(string(in.Index))
}
if in.Type != "" {
const prefix string = ",\"_type\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Type))
}
if in.Id != "" {
const prefix string = ",\"_id\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Id))
}
if in.Parent != "" {
const prefix string = ",\"parent\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Parent))
}
if in.Routing != "" {
const prefix string = ",\"routing\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Routing))
}
if in.Version != 0 {
const prefix string = ",\"version\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Int64(int64(in.Version))
}
if in.VersionType != "" {
const prefix string = ",\"version_type\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.VersionType))
}
if in.IfSeqNo != nil {
const prefix string = ",\"if_seq_no\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Int64(int64(*in.IfSeqNo))
}
if in.IfPrimaryTerm != nil {
const prefix string = ",\"if_primary_term\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Int64(int64(*in.IfPrimaryTerm))
}
out.RawByte('}')
}
// MarshalJSON supports json.Marshaler interface
func (v bulkDeleteRequestCommandOp) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{}
easyjson8092efb6EncodeGithubComOlivereElasticV7(&w, v)
return w.Buffer.BuildBytes(), w.Error
}
// MarshalEasyJSON supports easyjson.Marshaler interface
func (v bulkDeleteRequestCommandOp) MarshalEasyJSON(w *jwriter.Writer) {
easyjson8092efb6EncodeGithubComOlivereElasticV7(w, v)
}
// UnmarshalJSON supports json.Unmarshaler interface
func (v *bulkDeleteRequestCommandOp) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data}
easyjson8092efb6DecodeGithubComOlivereElasticV7(&r, v)
return r.Error()
}
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *bulkDeleteRequestCommandOp) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson8092efb6DecodeGithubComOlivereElasticV7(l, v)
}
func easyjson8092efb6DecodeGithubComOlivereElasticV71(in *jlexer.Lexer, out *bulkDeleteRequestCommand) {
isTopLevel := in.IsStart()
if in.IsNull() {
in.Skip()
} else {
in.Delim('{')
*out = make(bulkDeleteRequestCommand)
for !in.IsDelim('}') {
key := string(in.String())
in.WantColon()
var v1 bulkDeleteRequestCommandOp
(v1).UnmarshalEasyJSON(in)
(*out)[key] = v1
in.WantComma()
}
in.Delim('}')
}
if isTopLevel {
in.Consumed()
}
}
func easyjson8092efb6EncodeGithubComOlivereElasticV71(out *jwriter.Writer, in bulkDeleteRequestCommand) {
if in == nil && (out.Flags&jwriter.NilMapAsEmpty) == 0 {
out.RawString(`null`)
} else {
out.RawByte('{')
v2First := true
for v2Name, v2Value := range in {
if v2First {
v2First = false
} else {
out.RawByte(',')
}
out.String(string(v2Name))
out.RawByte(':')
(v2Value).MarshalEasyJSON(out)
}
out.RawByte('}')
}
}
// MarshalJSON supports json.Marshaler interface
func (v bulkDeleteRequestCommand) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{}
easyjson8092efb6EncodeGithubComOlivereElasticV71(&w, v)
return w.Buffer.BuildBytes(), w.Error
}
// MarshalEasyJSON supports easyjson.Marshaler interface
func (v bulkDeleteRequestCommand) MarshalEasyJSON(w *jwriter.Writer) {
easyjson8092efb6EncodeGithubComOlivereElasticV71(w, v)
}
// UnmarshalJSON supports json.Unmarshaler interface
func (v *bulkDeleteRequestCommand) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data}
easyjson8092efb6DecodeGithubComOlivereElasticV71(&r, v)
return r.Error()
}
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *bulkDeleteRequestCommand) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson8092efb6DecodeGithubComOlivereElasticV71(l, v)
}
================================================
FILE: bulk_delete_request_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"testing"
)
func TestBulkDeleteRequestSerialization(t *testing.T) {
tests := []struct {
Request BulkableRequest
Expected []string
}{
// #0
{
Request: NewBulkDeleteRequest().Index("index1").Id("1"),
Expected: []string{
`{"delete":{"_index":"index1","_id":"1"}}`,
},
},
// #1
{
Request: NewBulkDeleteRequest().Index("index1").Id("1").Parent("2"),
Expected: []string{
`{"delete":{"_index":"index1","_id":"1","parent":"2"}}`,
},
},
// #2
{
Request: NewBulkDeleteRequest().Index("index1").Id("1").Routing("3"),
Expected: []string{
`{"delete":{"_index":"index1","_id":"1","routing":"3"}}`,
},
},
}
for i, test := range tests {
lines, err := test.Request.Source()
if err != nil {
t.Fatalf("case #%d: expected no error, got: %v", i, err)
}
if lines == nil {
t.Fatalf("case #%d: expected lines, got nil", i)
}
if len(lines) != len(test.Expected) {
t.Fatalf("case #%d: expected %d lines, got %d", i, len(test.Expected), len(lines))
}
for j, line := range lines {
if line != test.Expected[j] {
t.Errorf("case #%d: expected line #%d to be %s, got: %s", i, j, test.Expected[j], line)
}
}
}
}
var bulkDeleteRequestSerializationResult string
func BenchmarkBulkDeleteRequestSerialization(b *testing.B) {
b.Run("stdlib", func(b *testing.B) {
r := NewBulkDeleteRequest().Index(testIndexName).Id("1")
benchmarkBulkDeleteRequestSerialization(b, r.UseEasyJSON(false))
})
b.Run("easyjson", func(b *testing.B) {
r := NewBulkDeleteRequest().Index(testIndexName).Id("1")
benchmarkBulkDeleteRequestSerialization(b, r.UseEasyJSON(true))
})
}
func benchmarkBulkDeleteRequestSerialization(b *testing.B, r *BulkDeleteRequest) {
var s string
for n := 0; n < b.N; n++ {
s = r.String()
r.source = nil // Don't let caching spoil the benchmark
}
bulkDeleteRequestSerializationResult = s // ensure the compiler doesn't optimize
b.ReportAllocs()
}
================================================
FILE: bulk_index_request.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
//go:generate easyjson bulk_index_request.go
import (
"encoding/json"
"fmt"
"strings"
)
// BulkIndexRequest is a request to add or replace a document to Elasticsearch.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-bulk.html
// for details.
type BulkIndexRequest struct {
BulkableRequest
index string
typ string
id string
opType string
routing string
parent string
version *int64 // default is MATCH_ANY
versionType string // default is "internal"
doc interface{}
pipeline string
retryOnConflict *int
ifSeqNo *int64
ifPrimaryTerm *int64
source []string
useEasyJSON bool
}
//easyjson:json
type bulkIndexRequestCommand map[string]bulkIndexRequestCommandOp
//easyjson:json
type bulkIndexRequestCommandOp struct {
Index string `json:"_index,omitempty"`
Id string `json:"_id,omitempty"`
Type string `json:"_type,omitempty"`
Parent string `json:"parent,omitempty"`
// RetryOnConflict is "_retry_on_conflict" for 6.0 and "retry_on_conflict" for 6.1+.
RetryOnConflict *int `json:"retry_on_conflict,omitempty"`
Routing string `json:"routing,omitempty"`
Version *int64 `json:"version,omitempty"`
VersionType string `json:"version_type,omitempty"`
Pipeline string `json:"pipeline,omitempty"`
IfSeqNo *int64 `json:"if_seq_no,omitempty"`
IfPrimaryTerm *int64 `json:"if_primary_term,omitempty"`
}
// NewBulkIndexRequest returns a new BulkIndexRequest.
// The operation type is "index" by default.
func NewBulkIndexRequest() *BulkIndexRequest {
return &BulkIndexRequest{
opType: "index",
}
}
// UseEasyJSON is an experimental setting that enables serialization
// with github.com/mailru/easyjson, which should in faster serialization
// time and less allocations, but removed compatibility with encoding/json,
// usage of unsafe etc. See https://github.com/mailru/easyjson#issues-notes-and-limitations
// for details. This setting is disabled by default.
func (r *BulkIndexRequest) UseEasyJSON(enable bool) *BulkIndexRequest {
r.useEasyJSON = enable
return r
}
// Index specifies the Elasticsearch index to use for this index request.
// If unspecified, the index set on the BulkService will be used.
func (r *BulkIndexRequest) Index(index string) *BulkIndexRequest {
r.index = index
r.source = nil
return r
}
// Type specifies the Elasticsearch type to use for this index request.
// If unspecified, the type set on the BulkService will be used.
func (r *BulkIndexRequest) Type(typ string) *BulkIndexRequest {
r.typ = typ
r.source = nil
return r
}
// Id specifies the identifier of the document to index.
func (r *BulkIndexRequest) Id(id string) *BulkIndexRequest {
r.id = id
r.source = nil
return r
}
// OpType specifies if this request should follow create-only or upsert
// behavior. This follows the OpType of the standard document index API.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-index_.html#operation-type
// for details.
func (r *BulkIndexRequest) OpType(opType string) *BulkIndexRequest {
r.opType = opType
r.source = nil
return r
}
// Routing specifies a routing value for the request.
func (r *BulkIndexRequest) Routing(routing string) *BulkIndexRequest {
r.routing = routing
r.source = nil
return r
}
// Parent specifies the identifier of the parent document (if available).
func (r *BulkIndexRequest) Parent(parent string) *BulkIndexRequest {
r.parent = parent
r.source = nil
return r
}
// Version indicates the version of the document as part of an optimistic
// concurrency model.
func (r *BulkIndexRequest) Version(version int64) *BulkIndexRequest {
v := version
r.version = &v
r.source = nil
return r
}
// VersionType specifies how versions are created. It can be e.g. internal,
// external, external_gte, or force.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-index_.html#index-versioning
// for details.
func (r *BulkIndexRequest) VersionType(versionType string) *BulkIndexRequest {
r.versionType = versionType
r.source = nil
return r
}
// Doc specifies the document to index.
func (r *BulkIndexRequest) Doc(doc interface{}) *BulkIndexRequest {
r.doc = doc
r.source = nil
return r
}
// RetryOnConflict specifies how often to retry in case of a version conflict.
func (r *BulkIndexRequest) RetryOnConflict(retryOnConflict int) *BulkIndexRequest {
r.retryOnConflict = &retryOnConflict
r.source = nil
return r
}
// Pipeline to use while processing the request.
func (r *BulkIndexRequest) Pipeline(pipeline string) *BulkIndexRequest {
r.pipeline = pipeline
r.source = nil
return r
}
// IfSeqNo indicates to only perform the index operation if the last
// operation that has changed the document has the specified sequence number.
func (r *BulkIndexRequest) IfSeqNo(ifSeqNo int64) *BulkIndexRequest {
r.ifSeqNo = &ifSeqNo
return r
}
// IfPrimaryTerm indicates to only perform the index operation if the
// last operation that has changed the document has the specified primary term.
func (r *BulkIndexRequest) IfPrimaryTerm(ifPrimaryTerm int64) *BulkIndexRequest {
r.ifPrimaryTerm = &ifPrimaryTerm
return r
}
// String returns the on-wire representation of the index request,
// concatenated as a single string.
func (r *BulkIndexRequest) String() string {
lines, err := r.Source()
if err != nil {
return fmt.Sprintf("error: %v", err)
}
return strings.Join(lines, "\n")
}
// Source returns the on-wire representation of the index request,
// split into an action-and-meta-data line and an (optional) source line.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-bulk.html
// for details.
func (r *BulkIndexRequest) Source() ([]string, error) {
// { "index" : { "_index" : "test", "_type" : "type1", "_id" : "1" } }
// { "field1" : "value1" }
if r.source != nil {
return r.source, nil
}
lines := make([]string, 2)
// "index" ...
indexCommand := bulkIndexRequestCommandOp{
Index: r.index,
Type: r.typ,
Id: r.id,
Routing: r.routing,
Parent: r.parent,
Version: r.version,
VersionType: r.versionType,
RetryOnConflict: r.retryOnConflict,
Pipeline: r.pipeline,
IfSeqNo: r.ifSeqNo,
IfPrimaryTerm: r.ifPrimaryTerm,
}
command := bulkIndexRequestCommand{
r.opType: indexCommand,
}
var err error
var body []byte
if r.useEasyJSON {
// easyjson
body, err = command.MarshalJSON()
} else {
// encoding/json
body, err = json.Marshal(command)
}
if err != nil {
return nil, err
}
lines[0] = string(body)
// "field1" ...
if r.doc != nil {
switch t := r.doc.(type) {
default:
body, err := json.Marshal(r.doc)
if err != nil {
return nil, err
}
lines[1] = string(body)
case json.RawMessage:
lines[1] = string(t)
case *json.RawMessage:
lines[1] = string(*t)
case string:
lines[1] = t
case *string:
lines[1] = *t
}
} else {
lines[1] = "{}"
}
r.source = lines
return lines, nil
}
================================================
FILE: bulk_index_request_easyjson.go
================================================
// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT.
package elastic
import (
json "encoding/json"
easyjson "github.com/mailru/easyjson"
jlexer "github.com/mailru/easyjson/jlexer"
jwriter "github.com/mailru/easyjson/jwriter"
)
// suppress unused package warning
var (
_ *json.RawMessage
_ *jlexer.Lexer
_ *jwriter.Writer
_ easyjson.Marshaler
)
func easyjson9de0fcbfDecodeGithubComOlivereElasticV7(in *jlexer.Lexer, out *bulkIndexRequestCommandOp) {
isTopLevel := in.IsStart()
if in.IsNull() {
if isTopLevel {
in.Consumed()
}
in.Skip()
return
}
in.Delim('{')
for !in.IsDelim('}') {
key := in.UnsafeFieldName(false)
in.WantColon()
if in.IsNull() {
in.Skip()
in.WantComma()
continue
}
switch key {
case "_index":
out.Index = string(in.String())
case "_id":
out.Id = string(in.String())
case "_type":
out.Type = string(in.String())
case "parent":
out.Parent = string(in.String())
case "retry_on_conflict":
if in.IsNull() {
in.Skip()
out.RetryOnConflict = nil
} else {
if out.RetryOnConflict == nil {
out.RetryOnConflict = new(int)
}
*out.RetryOnConflict = int(in.Int())
}
case "routing":
out.Routing = string(in.String())
case "version":
if in.IsNull() {
in.Skip()
out.Version = nil
} else {
if out.Version == nil {
out.Version = new(int64)
}
*out.Version = int64(in.Int64())
}
case "version_type":
out.VersionType = string(in.String())
case "pipeline":
out.Pipeline = string(in.String())
case "if_seq_no":
if in.IsNull() {
in.Skip()
out.IfSeqNo = nil
} else {
if out.IfSeqNo == nil {
out.IfSeqNo = new(int64)
}
*out.IfSeqNo = int64(in.Int64())
}
case "if_primary_term":
if in.IsNull() {
in.Skip()
out.IfPrimaryTerm = nil
} else {
if out.IfPrimaryTerm == nil {
out.IfPrimaryTerm = new(int64)
}
*out.IfPrimaryTerm = int64(in.Int64())
}
default:
in.SkipRecursive()
}
in.WantComma()
}
in.Delim('}')
if isTopLevel {
in.Consumed()
}
}
func easyjson9de0fcbfEncodeGithubComOlivereElasticV7(out *jwriter.Writer, in bulkIndexRequestCommandOp) {
out.RawByte('{')
first := true
_ = first
if in.Index != "" {
const prefix string = ",\"_index\":"
first = false
out.RawString(prefix[1:])
out.String(string(in.Index))
}
if in.Id != "" {
const prefix string = ",\"_id\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Id))
}
if in.Type != "" {
const prefix string = ",\"_type\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Type))
}
if in.Parent != "" {
const prefix string = ",\"parent\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Parent))
}
if in.RetryOnConflict != nil {
const prefix string = ",\"retry_on_conflict\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Int(int(*in.RetryOnConflict))
}
if in.Routing != "" {
const prefix string = ",\"routing\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Routing))
}
if in.Version != nil {
const prefix string = ",\"version\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Int64(int64(*in.Version))
}
if in.VersionType != "" {
const prefix string = ",\"version_type\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.VersionType))
}
if in.Pipeline != "" {
const prefix string = ",\"pipeline\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Pipeline))
}
if in.IfSeqNo != nil {
const prefix string = ",\"if_seq_no\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Int64(int64(*in.IfSeqNo))
}
if in.IfPrimaryTerm != nil {
const prefix string = ",\"if_primary_term\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Int64(int64(*in.IfPrimaryTerm))
}
out.RawByte('}')
}
// MarshalJSON supports json.Marshaler interface
func (v bulkIndexRequestCommandOp) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{}
easyjson9de0fcbfEncodeGithubComOlivereElasticV7(&w, v)
return w.Buffer.BuildBytes(), w.Error
}
// MarshalEasyJSON supports easyjson.Marshaler interface
func (v bulkIndexRequestCommandOp) MarshalEasyJSON(w *jwriter.Writer) {
easyjson9de0fcbfEncodeGithubComOlivereElasticV7(w, v)
}
// UnmarshalJSON supports json.Unmarshaler interface
func (v *bulkIndexRequestCommandOp) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data}
easyjson9de0fcbfDecodeGithubComOlivereElasticV7(&r, v)
return r.Error()
}
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *bulkIndexRequestCommandOp) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson9de0fcbfDecodeGithubComOlivereElasticV7(l, v)
}
func easyjson9de0fcbfDecodeGithubComOlivereElasticV71(in *jlexer.Lexer, out *bulkIndexRequestCommand) {
isTopLevel := in.IsStart()
if in.IsNull() {
in.Skip()
} else {
in.Delim('{')
*out = make(bulkIndexRequestCommand)
for !in.IsDelim('}') {
key := string(in.String())
in.WantColon()
var v1 bulkIndexRequestCommandOp
(v1).UnmarshalEasyJSON(in)
(*out)[key] = v1
in.WantComma()
}
in.Delim('}')
}
if isTopLevel {
in.Consumed()
}
}
func easyjson9de0fcbfEncodeGithubComOlivereElasticV71(out *jwriter.Writer, in bulkIndexRequestCommand) {
if in == nil && (out.Flags&jwriter.NilMapAsEmpty) == 0 {
out.RawString(`null`)
} else {
out.RawByte('{')
v2First := true
for v2Name, v2Value := range in {
if v2First {
v2First = false
} else {
out.RawByte(',')
}
out.String(string(v2Name))
out.RawByte(':')
(v2Value).MarshalEasyJSON(out)
}
out.RawByte('}')
}
}
// MarshalJSON supports json.Marshaler interface
func (v bulkIndexRequestCommand) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{}
easyjson9de0fcbfEncodeGithubComOlivereElasticV71(&w, v)
return w.Buffer.BuildBytes(), w.Error
}
// MarshalEasyJSON supports easyjson.Marshaler interface
func (v bulkIndexRequestCommand) MarshalEasyJSON(w *jwriter.Writer) {
easyjson9de0fcbfEncodeGithubComOlivereElasticV71(w, v)
}
// UnmarshalJSON supports json.Unmarshaler interface
func (v *bulkIndexRequestCommand) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data}
easyjson9de0fcbfDecodeGithubComOlivereElasticV71(&r, v)
return r.Error()
}
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *bulkIndexRequestCommand) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson9de0fcbfDecodeGithubComOlivereElasticV71(l, v)
}
================================================
FILE: bulk_index_request_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"testing"
"time"
)
func TestBulkIndexRequestSerialization(t *testing.T) {
tests := []struct {
Request BulkableRequest
Expected []string
}{
// #0
{
Request: NewBulkIndexRequest().Index("index1").Id("1").
Doc(tweet{User: "olivere", Created: time.Date(2014, 1, 18, 23, 59, 58, 0, time.UTC)}),
Expected: []string{
`{"index":{"_index":"index1","_id":"1"}}`,
`{"user":"olivere","message":"","retweets":0,"created":"2014-01-18T23:59:58Z"}`,
},
},
// #1
{
Request: NewBulkIndexRequest().OpType("create").Index("index1").Id("1").
Doc(tweet{User: "olivere", Created: time.Date(2014, 1, 18, 23, 59, 58, 0, time.UTC)}),
Expected: []string{
`{"create":{"_index":"index1","_id":"1"}}`,
`{"user":"olivere","message":"","retweets":0,"created":"2014-01-18T23:59:58Z"}`,
},
},
// #2
{
Request: NewBulkIndexRequest().OpType("index").Index("index1").Id("1").
Doc(tweet{User: "olivere", Created: time.Date(2014, 1, 18, 23, 59, 58, 0, time.UTC)}),
Expected: []string{
`{"index":{"_index":"index1","_id":"1"}}`,
`{"user":"olivere","message":"","retweets":0,"created":"2014-01-18T23:59:58Z"}`,
},
},
// #3
{
Request: NewBulkIndexRequest().OpType("index").Index("index1").Id("1").RetryOnConflict(42).
Doc(tweet{User: "olivere", Created: time.Date(2014, 1, 18, 23, 59, 58, 0, time.UTC)}),
Expected: []string{
`{"index":{"_index":"index1","_id":"1","retry_on_conflict":42}}`,
`{"user":"olivere","message":"","retweets":0,"created":"2014-01-18T23:59:58Z"}`,
},
},
// #4
{
Request: NewBulkIndexRequest().OpType("index").Index("index1").Id("1").Pipeline("my_pipeline").
Doc(tweet{User: "olivere", Created: time.Date(2014, 1, 18, 23, 59, 58, 0, time.UTC)}),
Expected: []string{
`{"index":{"_index":"index1","_id":"1","pipeline":"my_pipeline"}}`,
`{"user":"olivere","message":"","retweets":0,"created":"2014-01-18T23:59:58Z"}`,
},
},
// #5
{
Request: NewBulkIndexRequest().OpType("index").Index("index1").Id("1").
Routing("123").
Doc(tweet{User: "olivere", Created: time.Date(2014, 1, 18, 23, 59, 58, 0, time.UTC)}),
Expected: []string{
`{"index":{"_index":"index1","_id":"1","routing":"123"}}`,
`{"user":"olivere","message":"","retweets":0,"created":"2014-01-18T23:59:58Z"}`,
},
},
// #6
{
Request: NewBulkIndexRequest().OpType("index").Index("index1").Type("doc").Id("1").
Version(0).
VersionType("external").
Doc(tweet{User: "olivere", Created: time.Date(2014, 1, 18, 23, 59, 58, 0, time.UTC)}),
Expected: []string{
`{"index":{"_index":"index1","_id":"1","_type":"doc","version":0,"version_type":"external"}}`,
`{"user":"olivere","message":"","retweets":0,"created":"2014-01-18T23:59:58Z"}`,
},
},
}
for i, test := range tests {
lines, err := test.Request.Source()
if err != nil {
t.Fatalf("case #%d: expected no error, got: %v", i, err)
}
if lines == nil {
t.Fatalf("case #%d: expected lines, got nil", i)
}
if len(lines) != len(test.Expected) {
t.Fatalf("case #%d: expected %d lines, got %d", i, len(test.Expected), len(lines))
}
for j, line := range lines {
if line != test.Expected[j] {
t.Errorf("case #%d: expected line #%d to be %s, got: %s", i, j, test.Expected[j], line)
}
}
}
}
var bulkIndexRequestSerializationResult string
func BenchmarkBulkIndexRequestSerialization(b *testing.B) {
b.Run("stdlib", func(b *testing.B) {
r := NewBulkIndexRequest().Index(testIndexName).Id("1").
Doc(tweet{User: "olivere", Created: time.Date(2014, 1, 18, 23, 59, 58, 0, time.UTC)})
benchmarkBulkIndexRequestSerialization(b, r.UseEasyJSON(false))
})
b.Run("easyjson", func(b *testing.B) {
r := NewBulkIndexRequest().Index(testIndexName).Id("1").
Doc(tweet{User: "olivere", Created: time.Date(2014, 1, 18, 23, 59, 58, 0, time.UTC)})
benchmarkBulkIndexRequestSerialization(b, r.UseEasyJSON(true))
})
}
func benchmarkBulkIndexRequestSerialization(b *testing.B, r *BulkIndexRequest) {
var s string
for n := 0; n < b.N; n++ {
s = r.String()
r.source = nil // Don't let caching spoil the benchmark
}
bulkIndexRequestSerializationResult = s // ensure the compiler doesn't optimize
b.ReportAllocs()
}
================================================
FILE: bulk_processor.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"errors"
"net"
"sync"
"sync/atomic"
"time"
)
var (
// ErrBulkItemRetry is returned in BulkProcessor from a worker when
// a response item needs to be retried.
ErrBulkItemRetry = errors.New("elastic: uncommitted bulk response items")
defaultRetryItemStatusCodes = []int{408, 429, 503, 507}
)
// BulkProcessorService allows to easily process bulk requests. It allows setting
// policies when to flush new bulk requests, e.g. based on a number of actions,
// on the size of the actions, and/or to flush periodically. It also allows
// to control the number of concurrent bulk requests allowed to be executed
// in parallel.
//
// BulkProcessorService, by default, commits either every 1000 requests or when the
// (estimated) size of the bulk requests exceeds 5 MB. However, it does not
// commit periodically. BulkProcessorService also does retry by default, using
// an exponential backoff algorithm. It also will automatically re-enqueue items
// returned with a status of 408, 429, 503 or 507. You can change this
// behavior with RetryItemStatusCodes.
//
// The caller is responsible for setting the index and type on every
// bulk request added to BulkProcessorService.
//
// BulkProcessorService takes ideas from the BulkProcessor of the
// Elasticsearch Java API as documented in
// https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/java-docs-bulk-processor.html.
type BulkProcessorService struct {
c *Client
beforeFn BulkBeforeFunc
afterFn BulkAfterFunc
name string // name of processor
numWorkers int // # of workers (>= 1)
bulkActions int // # of requests after which to commit
bulkSize int // # of bytes after which to commit
flushInterval time.Duration // periodic flush interval
wantStats bool // indicates whether to gather statistics
backoff Backoff // a custom Backoff to use for errors
retryItemStatusCodes []int // array of status codes for bulk response line items that may be retried
}
// NewBulkProcessorService creates a new BulkProcessorService.
func NewBulkProcessorService(client *Client) *BulkProcessorService {
return &BulkProcessorService{
c: client,
numWorkers: 1,
bulkActions: 1000,
bulkSize: 5 << 20, // 5 MB
backoff: NewExponentialBackoff(
time.Duration(200)*time.Millisecond,
time.Duration(10000)*time.Millisecond,
),
retryItemStatusCodes: defaultRetryItemStatusCodes,
}
}
// BulkBeforeFunc defines the signature of callbacks that are executed
// before a commit to Elasticsearch.
type BulkBeforeFunc func(executionId int64, requests []BulkableRequest)
// BulkAfterFunc defines the signature of callbacks that are executed
// after a commit to Elasticsearch. The err parameter signals an error.
type BulkAfterFunc func(executionId int64, requests []BulkableRequest, response *BulkResponse, err error)
// Before specifies a function to be executed before bulk requests get committed
// to Elasticsearch.
func (s *BulkProcessorService) Before(fn BulkBeforeFunc) *BulkProcessorService {
s.beforeFn = fn
return s
}
// After specifies a function to be executed when bulk requests have been
// committed to Elasticsearch. The After callback executes both when the
// commit was successful as well as on failures.
func (s *BulkProcessorService) After(fn BulkAfterFunc) *BulkProcessorService {
s.afterFn = fn
return s
}
// Name is an optional name to identify this bulk processor.
func (s *BulkProcessorService) Name(name string) *BulkProcessorService {
s.name = name
return s
}
// Workers is the number of concurrent workers allowed to be
// executed. Defaults to 1 and must be greater or equal to 1.
func (s *BulkProcessorService) Workers(num int) *BulkProcessorService {
s.numWorkers = num
return s
}
// BulkActions specifies when to flush based on the number of actions
// currently added. Defaults to 1000 and can be set to -1 to be disabled.
func (s *BulkProcessorService) BulkActions(bulkActions int) *BulkProcessorService {
s.bulkActions = bulkActions
return s
}
// BulkSize specifies when to flush based on the size (in bytes) of the actions
// currently added. Defaults to 5 MB and can be set to -1 to be disabled.
func (s *BulkProcessorService) BulkSize(bulkSize int) *BulkProcessorService {
s.bulkSize = bulkSize
return s
}
// FlushInterval specifies when to flush at the end of the given interval.
// This is disabled by default. If you want the bulk processor to
// operate completely asynchronously, set both BulkActions and BulkSize to
// -1 and set the FlushInterval to a meaningful interval.
func (s *BulkProcessorService) FlushInterval(interval time.Duration) *BulkProcessorService {
s.flushInterval = interval
return s
}
// Stats tells bulk processor to gather stats while running.
// Use Stats to return the stats. This is disabled by default.
func (s *BulkProcessorService) Stats(wantStats bool) *BulkProcessorService {
s.wantStats = wantStats
return s
}
// Backoff sets the backoff strategy to use for errors.
func (s *BulkProcessorService) Backoff(backoff Backoff) *BulkProcessorService {
s.backoff = backoff
return s
}
// RetryItemStatusCodes sets an array of status codes that indicate that a bulk
// response line item should be retried.
func (s *BulkProcessorService) RetryItemStatusCodes(retryItemStatusCodes ...int) *BulkProcessorService {
s.retryItemStatusCodes = retryItemStatusCodes
return s
}
// Do creates a new BulkProcessor and starts it.
// Consider the BulkProcessor as a running instance that accepts bulk requests
// and commits them to Elasticsearch, spreading the work across one or more
// workers.
//
// You can interoperate with the BulkProcessor returned by Do, e.g. Start and
// Stop (or Close) it.
//
// Context is an optional context that is passed into the bulk request
// service calls. In contrast to other operations, this context is used in
// a long running process. You could use it to pass e.g. loggers, but you
// shouldn't use it for cancellation.
//
// Calling Do several times returns new BulkProcessors. You probably don't
// want to do this. BulkProcessorService implements just a builder pattern.
func (s *BulkProcessorService) Do(ctx context.Context) (*BulkProcessor, error) {
retryItemStatusCodes := make(map[int]struct{})
for _, code := range s.retryItemStatusCodes {
retryItemStatusCodes[code] = struct{}{}
}
p := newBulkProcessor(
s.c,
s.beforeFn,
s.afterFn,
s.name,
s.numWorkers,
s.bulkActions,
s.bulkSize,
s.flushInterval,
s.wantStats,
s.backoff,
retryItemStatusCodes)
err := p.Start(ctx)
if err != nil {
return nil, err
}
return p, nil
}
// -- Bulk Processor Statistics --
// BulkProcessorStats contains various statistics of a bulk processor
// while it is running. Use the Stats func to return it while running.
type BulkProcessorStats struct {
Flushed int64 // number of times the flush interval has been invoked
Committed int64 // # of times workers committed bulk requests
Indexed int64 // # of requests indexed
Created int64 // # of requests that ES reported as creates (201)
Updated int64 // # of requests that ES reported as updates
Deleted int64 // # of requests that ES reported as deletes
Succeeded int64 // # of requests that ES reported as successful
Failed int64 // # of requests that ES reported as failed
Workers []*BulkProcessorWorkerStats // stats for each worker
}
// BulkProcessorWorkerStats represents per-worker statistics.
type BulkProcessorWorkerStats struct {
Queued int64 // # of requests queued in this worker
LastDuration time.Duration // duration of last commit
}
// newBulkProcessorStats initializes and returns a BulkProcessorStats struct.
func newBulkProcessorStats(workers int) *BulkProcessorStats {
stats := &BulkProcessorStats{
Workers: make([]*BulkProcessorWorkerStats, workers),
}
for i := 0; i < workers; i++ {
stats.Workers[i] = &BulkProcessorWorkerStats{}
}
return stats
}
func (st *BulkProcessorStats) dup() *BulkProcessorStats {
dst := new(BulkProcessorStats)
dst.Flushed = st.Flushed
dst.Committed = st.Committed
dst.Indexed = st.Indexed
dst.Created = st.Created
dst.Updated = st.Updated
dst.Deleted = st.Deleted
dst.Succeeded = st.Succeeded
dst.Failed = st.Failed
for _, src := range st.Workers {
dst.Workers = append(dst.Workers, src.dup())
}
return dst
}
func (st *BulkProcessorWorkerStats) dup() *BulkProcessorWorkerStats {
dst := new(BulkProcessorWorkerStats)
dst.Queued = st.Queued
dst.LastDuration = st.LastDuration
return dst
}
// -- Bulk Processor --
// BulkProcessor encapsulates a task that accepts bulk requests and
// orchestrates committing them to Elasticsearch via one or more workers.
//
// BulkProcessor is returned by setting up a BulkProcessorService and
// calling the Do method.
type BulkProcessor struct {
c *Client
beforeFn BulkBeforeFunc
afterFn BulkAfterFunc
name string
bulkActions int
bulkSize int
numWorkers int
executionId int64
requestsC chan BulkableRequest
workerWg sync.WaitGroup
workers []*bulkWorker
flushInterval time.Duration
flusherStopC chan struct{}
wantStats bool
retryItemStatusCodes map[int]struct{}
backoff Backoff
startedMu sync.Mutex // guards the following block
started bool
statsMu sync.Mutex // guards the following block
stats *BulkProcessorStats
stopReconnC chan struct{} // channel to signal stop reconnection attempts
}
func newBulkProcessor(
client *Client,
beforeFn BulkBeforeFunc,
afterFn BulkAfterFunc,
name string,
numWorkers int,
bulkActions int,
bulkSize int,
flushInterval time.Duration,
wantStats bool,
backoff Backoff,
retryItemStatusCodes map[int]struct{}) *BulkProcessor {
return &BulkProcessor{
c: client,
beforeFn: beforeFn,
afterFn: afterFn,
name: name,
numWorkers: numWorkers,
bulkActions: bulkActions,
bulkSize: bulkSize,
flushInterval: flushInterval,
wantStats: wantStats,
retryItemStatusCodes: retryItemStatusCodes,
backoff: backoff,
}
}
// Start starts the bulk processor. If the processor is already started,
// nil is returned.
func (p *BulkProcessor) Start(ctx context.Context) error {
p.startedMu.Lock()
defer p.startedMu.Unlock()
if p.started {
return nil
}
// We must have at least one worker.
if p.numWorkers < 1 {
p.numWorkers = 1
}
p.requestsC = make(chan BulkableRequest)
p.executionId = 0
p.stats = newBulkProcessorStats(p.numWorkers)
p.stopReconnC = make(chan struct{})
// Create and start up workers.
p.workers = make([]*bulkWorker, p.numWorkers)
for i := 0; i < p.numWorkers; i++ {
p.workerWg.Add(1)
p.workers[i] = newBulkWorker(p, i)
go p.workers[i].work(ctx)
}
// Start the ticker for flush (if enabled)
if int64(p.flushInterval) > 0 {
p.flusherStopC = make(chan struct{})
go p.flusher(p.flushInterval)
}
p.started = true
return nil
}
// Stop is an alias for Close.
func (p *BulkProcessor) Stop() error {
return p.Close()
}
// Close stops the bulk processor previously started with Do.
// If it is already stopped, this is a no-op and nil is returned.
//
// By implementing Close, BulkProcessor implements the io.Closer interface.
func (p *BulkProcessor) Close() error {
p.startedMu.Lock()
defer p.startedMu.Unlock()
// Already stopped? Do nothing.
if !p.started {
return nil
}
// Tell connection checkers to stop
if p.stopReconnC != nil {
close(p.stopReconnC)
p.stopReconnC = nil
}
// Stop flusher (if enabled)
if p.flusherStopC != nil {
p.flusherStopC <- struct{}{}
<-p.flusherStopC
close(p.flusherStopC)
p.flusherStopC = nil
}
// Stop all workers.
close(p.requestsC)
p.workerWg.Wait()
p.started = false
return nil
}
// Stats returns the latest bulk processor statistics.
// Collecting stats must be enabled first by calling Stats(true) on
// the service that created this processor.
func (p *BulkProcessor) Stats() BulkProcessorStats {
p.statsMu.Lock()
defer p.statsMu.Unlock()
return *p.stats.dup()
}
// Add adds a single request to commit by the BulkProcessorService.
//
// The caller is responsible for setting the index and type on the request.
func (p *BulkProcessor) Add(request BulkableRequest) {
p.requestsC <- request
}
// Flush manually asks all workers to commit their outstanding requests.
// It returns only when all workers acknowledge completion.
func (p *BulkProcessor) Flush() error {
p.statsMu.Lock()
p.stats.Flushed++
p.statsMu.Unlock()
for _, w := range p.workers {
w.flushC <- struct{}{}
<-w.flushAckC // wait for completion
}
return nil
}
// flusher is a single goroutine that periodically asks all workers to
// commit their outstanding bulk requests. It is only started if
// FlushInterval is greater than 0.
func (p *BulkProcessor) flusher(interval time.Duration) {
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ticker.C: // Periodic flush
p.Flush() // TODO swallow errors here?
case <-p.flusherStopC:
p.flusherStopC <- struct{}{}
return
}
}
}
// -- Bulk Worker --
// bulkWorker encapsulates a single worker, running in a goroutine,
// receiving bulk requests and eventually committing them to Elasticsearch.
// It is strongly bound to a BulkProcessor.
type bulkWorker struct {
p *BulkProcessor
i int
bulkActions int
bulkSize int
service *BulkService
flushC chan struct{}
flushAckC chan struct{}
}
// newBulkWorker creates a new bulkWorker instance.
func newBulkWorker(p *BulkProcessor, i int) *bulkWorker {
return &bulkWorker{
p: p,
i: i,
bulkActions: p.bulkActions,
bulkSize: p.bulkSize,
service: NewBulkService(p.c),
flushC: make(chan struct{}),
flushAckC: make(chan struct{}),
}
}
// work waits for bulk requests and manual flush calls on the respective
// channels and is invoked as a goroutine when the bulk processor is started.
func (w *bulkWorker) work(ctx context.Context) {
defer func() {
w.p.workerWg.Done()
close(w.flushAckC)
close(w.flushC)
}()
var stop bool
for !stop {
var err error
select {
case req, open := <-w.p.requestsC:
if open {
// Received a new request
if _, err = req.Source(); err == nil {
w.service.Add(req)
if w.commitRequired() {
err = w.commit(ctx)
}
}
} else {
// Channel closed: Stop.
stop = true
if w.service.NumberOfActions() > 0 {
err = w.commit(ctx)
}
}
case <-w.flushC:
// Commit outstanding requests
if w.service.NumberOfActions() > 0 {
err = w.commit(ctx)
}
w.flushAckC <- struct{}{}
}
if err != nil {
w.p.c.errorf("elastic: bulk processor %q was unable to perform work: %v", w.p.name, err)
if !stop {
waitForActive := func() {
// Add back pressure to prevent Add calls from filling up the request queue
ready := make(chan struct{})
go w.waitForActiveConnection(ready)
<-ready
}
if _, ok := err.(net.Error); ok {
waitForActive()
} else if IsConnErr(err) {
waitForActive()
}
}
}
}
}
// commit commits the bulk requests in the given service,
// invoking callbacks as specified.
func (w *bulkWorker) commit(ctx context.Context) error {
var res *BulkResponse
// commitFunc will commit bulk requests and, on failure, be retried
// via exponential backoff
commitFunc := func() error {
var err error
// Save requests because they will be reset in service.Do
reqs := w.service.requests
res, err = w.service.Do(ctx)
if err == nil {
// Overall bulk request was OK. But each bulk response item also has a status
if w.p.retryItemStatusCodes != nil && len(w.p.retryItemStatusCodes) > 0 {
// Check res.Items since some might be soft failures
if res.Items != nil && res.Errors {
// res.Items will be 1 to 1 with reqs in same order
for i, item := range res.Items {
for _, result := range item {
if _, found := w.p.retryItemStatusCodes[result.Status]; found {
w.service.Add(reqs[i])
if err == nil {
err = ErrBulkItemRetry
}
}
}
}
}
}
}
return err
}
// notifyFunc will be called if retry fails
notifyFunc := func(err error) {
w.p.c.errorf("elastic: bulk processor %q failed but may retry: %v", w.p.name, err)
}
id := atomic.AddInt64(&w.p.executionId, 1)
// Update # documents in queue before eventual retries
w.p.statsMu.Lock()
if w.p.wantStats {
w.p.stats.Workers[w.i].Queued = int64(len(w.service.requests))
}
w.p.statsMu.Unlock()
// Save requests because they will be reset in commitFunc
reqs := w.service.requests
// Invoke before callback
if w.p.beforeFn != nil {
w.p.beforeFn(id, reqs)
}
// Commit bulk requests
err := RetryNotify(commitFunc, w.p.backoff, notifyFunc)
w.updateStats(res)
if err != nil {
w.p.c.errorf("elastic: bulk processor %q failed: %v", w.p.name, err)
}
// Invoke after callback
if w.p.afterFn != nil {
w.p.afterFn(id, reqs, res, err)
}
return err
}
func (w *bulkWorker) waitForActiveConnection(ready chan<- struct{}) {
defer close(ready)
t := time.NewTicker(5 * time.Second)
defer t.Stop()
client := w.p.c
stopReconnC := w.p.stopReconnC
w.p.c.errorf("elastic: bulk processor %q is waiting for an active connection", w.p.name)
// loop until a health check finds at least 1 active connection or the reconnection channel is closed
for {
select {
case _, ok := <-stopReconnC:
if !ok {
w.p.c.errorf("elastic: bulk processor %q active connection check interrupted", w.p.name)
return
}
case <-t.C:
client.healthcheck(context.Background(), 3*time.Second, true)
if client.mustActiveConn() == nil {
// found an active connection
// exit and signal done to the WaitGroup
return
}
}
}
}
func (w *bulkWorker) updateStats(res *BulkResponse) {
// Update stats
if res != nil {
w.p.statsMu.Lock()
if w.p.wantStats {
w.p.stats.Committed++
if res != nil {
w.p.stats.Indexed += int64(len(res.Indexed()))
w.p.stats.Created += int64(len(res.Created()))
w.p.stats.Updated += int64(len(res.Updated()))
w.p.stats.Deleted += int64(len(res.Deleted()))
w.p.stats.Succeeded += int64(len(res.Succeeded()))
w.p.stats.Failed += int64(len(res.Failed()))
}
w.p.stats.Workers[w.i].Queued = int64(len(w.service.requests))
w.p.stats.Workers[w.i].LastDuration = time.Duration(int64(res.Took)) * time.Millisecond
}
w.p.statsMu.Unlock()
}
}
// commitRequired returns true if the service has to commit its
// bulk requests. This can be either because the number of actions
// or the estimated size in bytes is larger than specified in the
// BulkProcessorService.
func (w *bulkWorker) commitRequired() bool {
if w.bulkActions >= 0 && w.service.NumberOfActions() >= w.bulkActions {
return true
}
if w.bulkSize >= 0 && w.service.EstimatedSizeInBytes() >= int64(w.bulkSize) {
return true
}
return false
}
================================================
FILE: bulk_processor_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"math/rand"
"reflect"
"sync/atomic"
"testing"
"time"
)
func TestBulkProcessorDefaults(t *testing.T) {
client := setupTestClientAndCreateIndex(t, SetHealthcheckTimeoutStartup(15*time.Second), SetSnifferTimeoutStartup(15*time.Second))
p := client.BulkProcessor()
if p == nil {
t.Fatalf("expected BulkProcessorService; got: %v", p)
}
if got, want := p.name, ""; got != want {
t.Errorf("expected %q; got: %q", want, got)
}
if got, want := p.numWorkers, 1; got != want {
t.Errorf("expected %d; got: %d", want, got)
}
if got, want := p.bulkActions, 1000; got != want {
t.Errorf("expected %d; got: %d", want, got)
}
if got, want := p.bulkSize, 5*1024*1024; got != want {
t.Errorf("expected %d; got: %d", want, got)
}
if got, want := p.flushInterval, time.Duration(0); got != want {
t.Errorf("expected %v; got: %v", want, got)
}
if got, want := p.wantStats, false; got != want {
t.Errorf("expected %v; got: %v", want, got)
}
if got, want := p.retryItemStatusCodes, defaultRetryItemStatusCodes; !reflect.DeepEqual(got, want) {
t.Errorf("expected %v; got: %v", want, got)
}
if p.backoff == nil {
t.Fatalf("expected non-nil backoff; got: %v", p.backoff)
}
}
func TestBulkProcessorCommitOnBulkActions(t *testing.T) {
//client := setupTestClientAndCreateIndexAndLog(t, SetTraceLog(log.New(os.Stdout, "", 0)))
client := setupTestClientAndCreateIndex(t, SetHealthcheckTimeoutStartup(15*time.Second), SetSnifferTimeoutStartup(15*time.Second))
testBulkProcessor(t,
10000,
client.BulkProcessor().
Name("Actions-1").
Workers(1).
BulkActions(100).
BulkSize(-1),
)
testBulkProcessor(t,
10000,
client.BulkProcessor().
Name("Actions-2").
Workers(2).
BulkActions(100).
BulkSize(-1),
)
}
func TestBulkProcessorCommitOnBulkSize(t *testing.T) {
//client := setupTestClientAndCreateIndexAndLog(t, SetTraceLog(log.New(os.Stdout, "", 0)))
client := setupTestClientAndCreateIndex(t, SetHealthcheckTimeoutStartup(15*time.Second), SetSnifferTimeoutStartup(15*time.Second))
testBulkProcessor(t,
10000,
client.BulkProcessor().
Name("Size-1").
Workers(1).
BulkActions(-1).
BulkSize(64*1024),
)
testBulkProcessor(t,
10000,
client.BulkProcessor().
Name("Size-2").
Workers(2).
BulkActions(-1).
BulkSize(64*1024),
)
}
func TestBulkProcessorBasedOnFlushInterval(t *testing.T) {
//client := setupTestClientAndCreateIndexAndLog(t, SetTraceLog(log.New(os.Stdout, "", 0)))
client := setupTestClientAndCreateIndex(t, SetHealthcheckTimeoutStartup(15*time.Second), SetSnifferTimeoutStartup(15*time.Second))
var beforeRequests int64
var befores int64
var afters int64
var failures int64
var afterRequests int64
beforeFn := func(executionId int64, requests []BulkableRequest) {
atomic.AddInt64(&beforeRequests, int64(len(requests)))
atomic.AddInt64(&befores, 1)
}
afterFn := func(executionId int64, requests []BulkableRequest, response *BulkResponse, err error) {
atomic.AddInt64(&afters, 1)
if err != nil {
atomic.AddInt64(&failures, 1)
}
atomic.AddInt64(&afterRequests, int64(len(requests)))
}
svc := client.BulkProcessor().
Name("FlushInterval-1").
Workers(2).
BulkActions(-1).
BulkSize(-1).
FlushInterval(1 * time.Second).
Before(beforeFn).
After(afterFn)
p, err := svc.Do(context.Background())
if err != nil {
t.Fatal(err)
}
const numDocs = 1000 // low-enough number that flush should be invoked
for i := 1; i <= numDocs; i++ {
tweet := tweet{User: "olivere", Message: fmt.Sprintf("%d. %s", i, randomString(rand.Intn(64)))}
request := NewBulkIndexRequest().Index(testIndexName).Id(fmt.Sprintf("%d", i)).Doc(tweet)
p.Add(request)
}
// Should flush at least once
time.Sleep(2 * time.Second)
err = p.Close()
if err != nil {
t.Fatal(err)
}
if p.stats.Flushed == 0 {
t.Errorf("expected at least 1 flush; got: %d", p.stats.Flushed)
}
if got, want := beforeRequests, int64(numDocs); got != want {
t.Errorf("expected %d requests to before callback; got: %d", want, got)
}
if got, want := afterRequests, int64(numDocs); got != want {
t.Errorf("expected %d requests to after callback; got: %d", want, got)
}
if befores == 0 {
t.Error("expected at least 1 call to before callback")
}
if afters == 0 {
t.Error("expected at least 1 call to after callback")
}
if failures != 0 {
t.Errorf("expected 0 calls to failure callback; got: %d", failures)
}
// Check number of documents that were bulk indexed
_, err = p.c.Refresh(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
count, err := p.c.Count(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if count != int64(numDocs) {
t.Fatalf("expected %d documents; got: %d", numDocs, count)
}
}
func TestBulkProcessorClose(t *testing.T) {
//client := setupTestClientAndCreateIndexAndLog(t, SetTraceLog(log.New(os.Stdout, "", 0)))
client := setupTestClientAndCreateIndex(t, SetHealthcheckTimeoutStartup(15*time.Second), SetSnifferTimeoutStartup(15*time.Second))
var beforeRequests int64
var befores int64
var afters int64
var failures int64
var afterRequests int64
beforeFn := func(executionId int64, requests []BulkableRequest) {
atomic.AddInt64(&beforeRequests, int64(len(requests)))
atomic.AddInt64(&befores, 1)
}
afterFn := func(executionId int64, requests []BulkableRequest, response *BulkResponse, err error) {
atomic.AddInt64(&afters, 1)
if err != nil {
atomic.AddInt64(&failures, 1)
}
atomic.AddInt64(&afterRequests, int64(len(requests)))
}
p, err := client.BulkProcessor().
Name("FlushInterval-1").
Workers(2).
BulkActions(-1).
BulkSize(-1).
FlushInterval(30 * time.Second). // 30 seconds to flush
Before(beforeFn).After(afterFn).
Do(context.Background())
if err != nil {
t.Fatal(err)
}
const numDocs = 1000 // low-enough number that flush should be invoked
for i := 1; i <= numDocs; i++ {
tweet := tweet{User: "olivere", Message: fmt.Sprintf("%d. %s", i, randomString(rand.Intn(64)))}
request := NewBulkIndexRequest().Index(testIndexName).Id(fmt.Sprintf("%d", i)).Doc(tweet)
p.Add(request)
}
// Should not flush because 30s > 1s
time.Sleep(1 * time.Second)
// Close should flush
err = p.Close()
if err != nil {
t.Fatal(err)
}
if p.stats.Flushed != 0 {
t.Errorf("expected no flush; got: %d", p.stats.Flushed)
}
if got, want := beforeRequests, int64(numDocs); got != want {
t.Errorf("expected %d requests to before callback; got: %d", want, got)
}
if got, want := afterRequests, int64(numDocs); got != want {
t.Errorf("expected %d requests to after callback; got: %d", want, got)
}
if befores == 0 {
t.Error("expected at least 1 call to before callback")
}
if afters == 0 {
t.Error("expected at least 1 call to after callback")
}
if failures != 0 {
t.Errorf("expected 0 calls to failure callback; got: %d", failures)
}
// Check number of documents that were bulk indexed
_, err = p.c.Refresh(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
count, err := p.c.Count(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if count != int64(numDocs) {
t.Fatalf("expected %d documents; got: %d", numDocs, count)
}
}
func TestBulkProcessorFlush(t *testing.T) {
//client := setupTestClientAndCreateIndexAndLog(t, SetTraceLog(log.New(os.Stdout, "", 0)))
client := setupTestClientAndCreateIndex(t, SetHealthcheckTimeoutStartup(15*time.Second), SetSnifferTimeoutStartup(15*time.Second))
p, err := client.BulkProcessor().
Name("ManualFlush").
Workers(10).
BulkActions(-1).
BulkSize(-1).
FlushInterval(30 * time.Second). // 30 seconds to flush
Stats(true).
Do(context.Background())
if err != nil {
t.Fatal(err)
}
const numDocs = 100
for i := 1; i <= numDocs; i++ {
tweet := tweet{User: "olivere", Message: fmt.Sprintf("%d. %s", i, randomString(rand.Intn(64)))}
request := NewBulkIndexRequest().Index(testIndexName).Id(fmt.Sprintf("%d", i)).Doc(tweet)
p.Add(request)
}
// Should not flush because 30s > 1s
time.Sleep(1 * time.Second)
// No flush yet
stats := p.Stats()
if stats.Flushed != 0 {
t.Errorf("expected no flush; got: %d", p.stats.Flushed)
}
// Manual flush
err = p.Flush()
if err != nil {
t.Fatal(err)
}
time.Sleep(1 * time.Second)
// Now flushed
stats = p.Stats()
if got, want := p.stats.Flushed, int64(1); got != want {
t.Errorf("expected %d flush; got: %d", want, got)
}
// Close should not start another flush
err = p.Close()
if err != nil {
t.Fatal(err)
}
// Still 1 flush
stats = p.Stats()
if got, want := p.stats.Flushed, int64(1); got != want {
t.Errorf("expected %d flush; got: %d", want, got)
}
// Check number of documents that were bulk indexed
_, err = p.c.Refresh(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
count, err := p.c.Count(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if count != int64(numDocs) {
t.Fatalf("expected %d documents; got: %d", numDocs, count)
}
}
// -- Helper --
func testBulkProcessor(t *testing.T, numDocs int, svc *BulkProcessorService) {
var beforeRequests int64
var befores int64
var afters int64
var failures int64
var afterRequests int64
beforeFn := func(executionId int64, requests []BulkableRequest) {
atomic.AddInt64(&beforeRequests, int64(len(requests)))
atomic.AddInt64(&befores, 1)
}
afterFn := func(executionId int64, requests []BulkableRequest, response *BulkResponse, err error) {
atomic.AddInt64(&afters, 1)
if err != nil {
atomic.AddInt64(&failures, 1)
}
atomic.AddInt64(&afterRequests, int64(len(requests)))
}
p, err := svc.Before(beforeFn).After(afterFn).Stats(true).Do(context.Background())
if err != nil {
t.Fatal(err)
}
for i := 1; i <= numDocs; i++ {
tweet := tweet{User: "olivere", Message: fmt.Sprintf("%07d. %s", i, randomString(1+rand.Intn(63)))}
request := NewBulkIndexRequest().Index(testIndexName).Id(fmt.Sprintf("%d", i)).Doc(tweet)
p.Add(request)
}
err = p.Close()
if err != nil {
t.Fatal(err)
}
stats := p.Stats()
if stats.Flushed != 0 {
t.Errorf("expected no flush; got: %d", stats.Flushed)
}
if stats.Committed <= 0 {
t.Errorf("expected committed > %d; got: %d", 0, stats.Committed)
}
if got, want := stats.Indexed, int64(numDocs); got != want {
t.Errorf("expected indexed = %d; got: %d", want, got)
}
if got, want := stats.Created, int64(0); got != want {
t.Errorf("expected created = %d; got: %d", want, got)
}
if got, want := stats.Updated, int64(0); got != want {
t.Errorf("expected updated = %d; got: %d", want, got)
}
if got, want := stats.Deleted, int64(0); got != want {
t.Errorf("expected deleted = %d; got: %d", want, got)
}
if got, want := stats.Succeeded, int64(numDocs); got != want {
t.Errorf("expected succeeded = %d; got: %d", want, got)
}
if got, want := stats.Failed, int64(0); got != want {
t.Errorf("expected failed = %d; got: %d", want, got)
}
if got, want := beforeRequests, int64(numDocs); got != want {
t.Errorf("expected %d requests to before callback; got: %d", want, got)
}
if got, want := afterRequests, int64(numDocs); got != want {
t.Errorf("expected %d requests to after callback; got: %d", want, got)
}
if befores == 0 {
t.Error("expected at least 1 call to before callback")
}
if afters == 0 {
t.Error("expected at least 1 call to after callback")
}
if failures != 0 {
t.Errorf("expected 0 calls to failure callback; got: %d", failures)
}
// Check number of documents that were bulk indexed
_, err = p.c.Refresh(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
count, err := p.c.Count(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if count != int64(numDocs) {
t.Fatalf("expected %d documents; got: %d", numDocs, count)
}
}
================================================
FILE: bulk_request.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"fmt"
)
// -- Bulkable request (index/update/delete) --
// BulkableRequest is a generic interface to bulkable requests.
type BulkableRequest interface {
fmt.Stringer
Source() ([]string, error)
}
================================================
FILE: bulk_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"math/rand"
"net/http"
"net/http/httptest"
"testing"
)
func TestBulk(t *testing.T) {
client := setupTestClientAndCreateIndex(t) //, SetTraceLog(log.New(os.Stdout, "", 0)))
tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
tweet2 := tweet{User: "sandrae", Message: "Dancing all night long. Yeah."}
index1Req := NewBulkIndexRequest().Index(testIndexName).Id("1").Doc(tweet1)
index2Req := NewBulkIndexRequest().Index(testIndexName).Id("2").Doc(tweet2)
delete1Req := NewBulkDeleteRequest().Index(testIndexName).Id("1")
bulkRequest := client.Bulk()
bulkRequest = bulkRequest.Add(index1Req)
bulkRequest = bulkRequest.Add(index2Req)
bulkRequest = bulkRequest.Add(delete1Req)
if bulkRequest.NumberOfActions() != 3 {
t.Errorf("expected bulkRequest.NumberOfActions %d; got %d", 3, bulkRequest.NumberOfActions())
}
bulkResponse, err := bulkRequest.Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if bulkResponse == nil {
t.Errorf("expected bulkResponse to be != nil; got nil")
}
if bulkRequest.NumberOfActions() != 0 {
t.Errorf("expected bulkRequest.NumberOfActions %d; got %d", 0, bulkRequest.NumberOfActions())
}
// Document with Id="1" should not exist
exists, err := client.Exists().Index(testIndexName).Id("1").Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if exists {
t.Errorf("expected exists %v; got %v", false, exists)
}
// Document with Id="2" should exist
exists, err = client.Exists().Index(testIndexName).Id("2").Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if !exists {
t.Errorf("expected exists %v; got %v", true, exists)
}
// Update
updateDoc := struct {
Retweets int `json:"retweets"`
}{
42,
}
update1Req := NewBulkUpdateRequest().Index(testIndexName).Id("2").Doc(&updateDoc)
bulkRequest = client.Bulk()
bulkRequest = bulkRequest.Add(update1Req)
if bulkRequest.NumberOfActions() != 1 {
t.Errorf("expected bulkRequest.NumberOfActions %d; got %d", 1, bulkRequest.NumberOfActions())
}
bulkResponse, err = bulkRequest.Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if bulkResponse == nil {
t.Errorf("expected bulkResponse to be != nil; got nil")
}
if bulkRequest.NumberOfActions() != 0 {
t.Errorf("expected bulkRequest.NumberOfActions %d; got %d", 0, bulkRequest.NumberOfActions())
}
// Document with Id="1" should have a retweets count of 42
doc, err := client.Get().Index(testIndexName).Id("2").Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if doc == nil {
t.Fatal("expected doc to be != nil; got nil")
}
if !doc.Found {
t.Fatalf("expected doc to be found; got found = %v", doc.Found)
}
if doc.Source == nil {
t.Fatal("expected doc source to be != nil; got nil")
}
var updatedTweet tweet
err = json.Unmarshal(doc.Source, &updatedTweet)
if err != nil {
t.Fatal(err)
}
if updatedTweet.Retweets != 42 {
t.Errorf("expected updated tweet retweets = %v; got %v", 42, updatedTweet.Retweets)
}
// Update with script
update2Req := NewBulkUpdateRequest().Index(testIndexName).Id("2").
RetryOnConflict(3).
Script(NewScript("ctx._source.retweets += params.v").Param("v", 1))
bulkRequest = client.Bulk()
bulkRequest = bulkRequest.Add(update2Req)
if bulkRequest.NumberOfActions() != 1 {
t.Errorf("expected bulkRequest.NumberOfActions %d; got %d", 1, bulkRequest.NumberOfActions())
}
bulkResponse, err = bulkRequest.Refresh("wait_for").Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if bulkResponse == nil {
t.Errorf("expected bulkResponse to be != nil; got nil")
}
if bulkRequest.NumberOfActions() != 0 {
t.Errorf("expected bulkRequest.NumberOfActions %d; got %d", 0, bulkRequest.NumberOfActions())
}
// Document with Id="1" should have a retweets count of 43
doc, err = client.Get().Index(testIndexName).Id("2").Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if doc == nil {
t.Fatal("expected doc to be != nil; got nil")
}
if !doc.Found {
t.Fatalf("expected doc to be found; got found = %v", doc.Found)
}
if doc.Source == nil {
t.Fatal("expected doc source to be != nil; got nil")
}
err = json.Unmarshal(doc.Source, &updatedTweet)
if err != nil {
t.Fatal(err)
}
if updatedTweet.Retweets != 43 {
t.Errorf("expected updated tweet retweets = %v; got %v", 43, updatedTweet.Retweets)
}
}
func TestBulkWithIndexSetOnClient(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
tweet2 := tweet{User: "sandrae", Message: "Dancing all night long. Yeah."}
index1Req := NewBulkIndexRequest().Index(testIndexName).Id("1").Doc(tweet1).Routing("1")
index2Req := NewBulkIndexRequest().Index(testIndexName).Id("2").Doc(tweet2)
delete1Req := NewBulkDeleteRequest().Index(testIndexName).Id("1")
bulkRequest := client.Bulk().Index(testIndexName)
bulkRequest = bulkRequest.Add(index1Req)
bulkRequest = bulkRequest.Add(index2Req)
bulkRequest = bulkRequest.Add(delete1Req)
if bulkRequest.NumberOfActions() != 3 {
t.Errorf("expected bulkRequest.NumberOfActions %d; got %d", 3, bulkRequest.NumberOfActions())
}
bulkResponse, err := bulkRequest.Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if bulkResponse == nil {
t.Errorf("expected bulkResponse to be != nil; got nil")
}
// Document with Id="1" should not exist
exists, err := client.Exists().Index(testIndexName).Id("1").Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if exists {
t.Errorf("expected exists %v; got %v", false, exists)
}
// Document with Id="2" should exist
exists, err = client.Exists().Index(testIndexName).Id("2").Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if !exists {
t.Errorf("expected exists %v; got %v", true, exists)
}
}
func TestBulkIndexDeleteUpdate(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
//client := setupTestClientAndCreateIndexAndLog(t)
tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
tweet2 := tweet{User: "sandrae", Message: "Dancing all night long. Yeah."}
index1Req := NewBulkIndexRequest().Index(testIndexName).Id("1").Doc(tweet1)
index2Req := NewBulkIndexRequest().OpType("create").Index(testIndexName).Id("2").Doc(tweet2)
delete1Req := NewBulkDeleteRequest().Index(testIndexName).Id("1")
update2Req := NewBulkUpdateRequest().Index(testIndexName).Id("2").
ReturnSource(true).
Doc(struct {
Retweets int `json:"retweets"`
}{
Retweets: 42,
})
bulkRequest := client.Bulk()
bulkRequest = bulkRequest.Add(index1Req)
bulkRequest = bulkRequest.Add(index2Req)
bulkRequest = bulkRequest.Add(delete1Req)
bulkRequest = bulkRequest.Add(update2Req)
if bulkRequest.NumberOfActions() != 4 {
t.Errorf("expected bulkRequest.NumberOfActions %d; got %d", 4, bulkRequest.NumberOfActions())
}
expected := `{"index":{"_index":"` + testIndexName + `","_id":"1"}}
{"user":"olivere","message":"Welcome to Golang and Elasticsearch.","retweets":0,"created":"0001-01-01T00:00:00Z"}
{"create":{"_index":"` + testIndexName + `","_id":"2"}}
{"user":"sandrae","message":"Dancing all night long. Yeah.","retweets":0,"created":"0001-01-01T00:00:00Z"}
{"delete":{"_index":"` + testIndexName + `","_id":"1"}}
{"update":{"_index":"` + testIndexName + `","_id":"2"}}
{"doc":{"retweets":42},"_source":true}
`
got, err := bulkRequest.bodyAsString()
if err != nil {
t.Fatalf("expected no error, got: %v", err)
}
if got != expected {
t.Errorf("expected\n%s\ngot:\n%s", expected, got)
}
// Run the bulk request
bulkResponse, err := bulkRequest.Pretty(true).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if bulkResponse == nil {
t.Fatal("expected bulkResponse to be != nil; got nil")
}
if bulkResponse.Took == 0 {
t.Errorf("expected took to be > 0; got %d", bulkResponse.Took)
}
if bulkResponse.Errors {
t.Errorf("expected errors to be %v; got %v", false, bulkResponse.Errors)
}
if len(bulkResponse.Items) != 4 {
t.Fatalf("expected 4 result items; got %d", len(bulkResponse.Items))
}
// Indexed actions
indexed := bulkResponse.Indexed()
if indexed == nil {
t.Fatal("expected indexed to be != nil; got nil")
}
if len(indexed) != 1 {
t.Fatalf("expected len(indexed) == %d; got %d", 1, len(indexed))
}
if indexed[0].Id != "1" {
t.Errorf("expected indexed[0].Id == %s; got %s", "1", indexed[0].Id)
}
if indexed[0].Status != 201 {
t.Errorf("expected indexed[0].Status == %d; got %d", 201, indexed[0].Status)
}
// Created actions
created := bulkResponse.Created()
if created == nil {
t.Fatal("expected created to be != nil; got nil")
}
if len(created) != 1 {
t.Fatalf("expected len(created) == %d; got %d", 1, len(created))
}
if created[0].Id != "2" {
t.Errorf("expected created[0].Id == %s; got %s", "2", created[0].Id)
}
if created[0].Status != 201 {
t.Errorf("expected created[0].Status == %d; got %d", 201, created[0].Status)
}
if want, have := "created", created[0].Result; want != have {
t.Errorf("expected created[0].Result == %q; got %q", want, have)
}
// Deleted actions
deleted := bulkResponse.Deleted()
if deleted == nil {
t.Fatal("expected deleted to be != nil; got nil")
}
if len(deleted) != 1 {
t.Fatalf("expected len(deleted) == %d; got %d", 1, len(deleted))
}
if deleted[0].Id != "1" {
t.Errorf("expected deleted[0].Id == %s; got %s", "1", deleted[0].Id)
}
if deleted[0].Status != 200 {
t.Errorf("expected deleted[0].Status == %d; got %d", 200, deleted[0].Status)
}
if want, have := "deleted", deleted[0].Result; want != have {
t.Errorf("expected deleted[0].Result == %q; got %q", want, have)
}
// Updated actions
updated := bulkResponse.Updated()
if updated == nil {
t.Fatal("expected updated to be != nil; got nil")
}
if len(updated) != 1 {
t.Fatalf("expected len(updated) == %d; got %d", 1, len(updated))
}
if updated[0].Id != "2" {
t.Errorf("expected updated[0].Id == %s; got %s", "2", updated[0].Id)
}
if updated[0].Status != 200 {
t.Errorf("expected updated[0].Status == %d; got %d", 200, updated[0].Status)
}
if updated[0].Version != 2 {
t.Errorf("expected updated[0].Version == %d; got %d", 2, updated[0].Version)
}
if want, have := "updated", updated[0].Result; want != have {
t.Errorf("expected updated[0].Result == %q; got %q", want, have)
}
if updated[0].GetResult == nil {
t.Fatalf("expected updated[0].GetResult to be != nil; got nil")
}
if updated[0].GetResult.Source == nil {
t.Fatalf("expected updated[0].GetResult.Source to be != nil; got nil")
}
if want, have := true, updated[0].GetResult.Found; want != have {
t.Fatalf("expected updated[0].GetResult.Found to be != %v; got %v", want, have)
}
var doc tweet
if err := json.Unmarshal(updated[0].GetResult.Source, &doc); err != nil {
t.Fatalf("expected to unmarshal updated[0].GetResult.Source; got %v", err)
}
if want, have := 42, doc.Retweets; want != have {
t.Fatalf("expected updated tweet to have Retweets = %v; got %v", want, have)
}
// Succeeded actions
succeeded := bulkResponse.Succeeded()
if succeeded == nil {
t.Fatal("expected succeeded to be != nil; got nil")
}
if len(succeeded) != 4 {
t.Fatalf("expected len(succeeded) == %d; got %d", 4, len(succeeded))
}
// ById
id1Results := bulkResponse.ById("1")
if id1Results == nil {
t.Fatal("expected id1Results to be != nil; got nil")
}
if len(id1Results) != 2 {
t.Fatalf("expected len(id1Results) == %d; got %d", 2, len(id1Results))
}
if id1Results[0].Id != "1" {
t.Errorf("expected id1Results[0].Id == %s; got %s", "1", id1Results[0].Id)
}
if id1Results[0].Status != 201 {
t.Errorf("expected id1Results[0].Status == %d; got %d", 201, id1Results[0].Status)
}
if id1Results[0].Version != 1 {
t.Errorf("expected id1Results[0].Version == %d; got %d", 1, id1Results[0].Version)
}
if id1Results[1].Id != "1" {
t.Errorf("expected id1Results[1].Id == %s; got %s", "1", id1Results[1].Id)
}
if id1Results[1].Status != 200 {
t.Errorf("expected id1Results[1].Status == %d; got %d", 200, id1Results[1].Status)
}
if id1Results[1].Version != 2 {
t.Errorf("expected id1Results[1].Version == %d; got %d", 2, id1Results[1].Version)
}
}
func TestBulkOnReadOnlyIndex(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
//client := setupTestClientAndCreateIndexAndLog(t)
// Change index to read-only
{
_, err := client.IndexPutSettings(testIndexName).
BodyString(`{
"index": {
"blocks": {
"read_only_allow_delete": true
}
}
}`).Pretty(true).Do(context.Background())
if err != nil {
t.Fatalf("unable to set index into read-only mode: %v", err)
}
}
// Index something
tweet := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
bulk := client.Bulk().Add(
NewBulkIndexRequest().Index(testIndexName).Id("1").Doc(tweet),
)
resp, err := bulk.Pretty(true).Do(context.Background())
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("expected response to be != nil; got nil")
}
if !resp.Errors {
t.Fatal("expected response errors being set to true")
}
if len(resp.Items) != 1 {
t.Fatal("expected response with 1 item")
}
if want, have := http.StatusTooManyRequests, resp.ById("1")[0].Status; want != have {
t.Fatal("expected HTTP status code 200")
}
}
func TestFailedBulkRequests(t *testing.T) {
js := `{
"took" : 2,
"errors" : true,
"items" : [ {
"index" : {
"_index" : "elastic-test",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"status" : 201
}
}, {
"create" : {
"_index" : "elastic-test",
"_type" : "_doc",
"_id" : "2",
"_version" : 1,
"status" : 423,
"error" : {
"type":"routing_missing_exception",
"reason":"routing is required for [elastic-test2]/[comment]/[1]"
}
}
}, {
"delete" : {
"_index" : "elastic-test",
"_type" : "_doc",
"_id" : "1",
"_version" : 2,
"status" : 404,
"found" : false
}
}, {
"update" : {
"_index" : "elastic-test",
"_type" : "_doc",
"_id" : "2",
"_version" : 2,
"status" : 200
}
} ]
}`
var resp BulkResponse
err := json.Unmarshal([]byte(js), &resp)
if err != nil {
t.Fatal(err)
}
failed := resp.Failed()
if len(failed) != 2 {
t.Errorf("expected %d failed items; got: %d", 2, len(failed))
}
}
func TestBulkEstimatedSizeInBytes(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
tweet2 := tweet{User: "sandrae", Message: "Dancing all night long. Yeah."}
index1Req := NewBulkIndexRequest().Index(testIndexName).Id("1").Doc(tweet1)
index2Req := NewBulkIndexRequest().OpType("create").Index(testIndexName).Id("2").Doc(tweet2)
delete1Req := NewBulkDeleteRequest().Index(testIndexName).Id("1")
update2Req := NewBulkUpdateRequest().Index(testIndexName).Id("2").
Doc(struct {
Retweets int `json:"retweets"`
}{
Retweets: 42,
})
bulkRequest := client.Bulk()
bulkRequest = bulkRequest.Add(index1Req)
bulkRequest = bulkRequest.Add(index2Req)
bulkRequest = bulkRequest.Add(delete1Req)
bulkRequest = bulkRequest.Add(update2Req)
if bulkRequest.NumberOfActions() != 4 {
t.Errorf("expected bulkRequest.NumberOfActions %d; got %d", 4, bulkRequest.NumberOfActions())
}
// The estimated size of the bulk request in bytes must be at least
// the length of the body request.
raw, err := bulkRequest.bodyAsString()
if err != nil {
t.Fatal(err)
}
rawlen := int64(len([]byte(raw)))
if got, want := bulkRequest.EstimatedSizeInBytes(), rawlen; got < want {
t.Errorf("expected an EstimatedSizeInBytes = %d; got: %v", want, got)
}
// Reset should also reset the calculated estimated byte size
bulkRequest.Reset()
if got, want := bulkRequest.EstimatedSizeInBytes(), int64(0); got != want {
t.Errorf("expected an EstimatedSizeInBytes = %d; got: %v", want, got)
}
}
func TestBulkEstimateSizeInBytesLength(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
s := client.Bulk()
r := NewBulkDeleteRequest().Index(testIndexName).Id("1")
s = s.Add(r)
if got, want := s.estimateSizeInBytes(r), int64(1+len(r.String())); got != want {
t.Fatalf("expected %d; got: %d", want, got)
}
}
func TestBulkContentType(t *testing.T) {
var header http.Header
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
header = r.Header
fmt.Fprintln(w, `{}`)
}))
defer ts.Close()
client, err := NewSimpleClient(SetURL(ts.URL))
if err != nil {
t.Fatal(err)
}
indexReq := NewBulkIndexRequest().Index(testIndexName).Id("1").Doc(tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."})
if _, err := client.Bulk().Add(indexReq).Do(context.Background()); err != nil {
t.Fatal(err)
}
if header == nil {
t.Fatalf("expected header, got %v", header)
}
if want, have := "application/x-ndjson", header.Get("Content-Type"); want != have {
t.Fatalf("Content-Type: want %q, have %q", want, have)
}
}
// -- Benchmarks --
var benchmarkBulkEstimatedSizeInBytes int64
func BenchmarkBulkEstimatedSizeInBytesWith1Request(b *testing.B) {
client := setupTestClientAndCreateIndex(b)
s := client.Bulk()
var result int64
for n := 0; n < b.N; n++ {
s = s.Add(NewBulkIndexRequest().Index(testIndexName).Id("1").Doc(struct{ A string }{"1"}))
s = s.Add(NewBulkUpdateRequest().Index(testIndexName).Id("1").Doc(struct{ A string }{"2"}))
s = s.Add(NewBulkDeleteRequest().Index(testIndexName).Id("1"))
result = s.EstimatedSizeInBytes()
s.Reset()
}
b.ReportAllocs()
benchmarkBulkEstimatedSizeInBytes = result // ensure the compiler doesn't optimize
}
func BenchmarkBulkEstimatedSizeInBytesWith100Requests(b *testing.B) {
client := setupTestClientAndCreateIndex(b)
s := client.Bulk()
var result int64
for n := 0; n < b.N; n++ {
for i := 0; i < 100; i++ {
s = s.Add(NewBulkIndexRequest().Index(testIndexName).Id("1").Doc(struct{ A string }{"1"}))
s = s.Add(NewBulkUpdateRequest().Index(testIndexName).Id("1").Doc(struct{ A string }{"2"}))
s = s.Add(NewBulkDeleteRequest().Index(testIndexName).Id("1"))
}
result = s.EstimatedSizeInBytes()
s.Reset()
}
b.ReportAllocs()
benchmarkBulkEstimatedSizeInBytes = result // ensure the compiler doesn't optimize
}
func BenchmarkBulkAllocs(b *testing.B) {
b.Run("1000 docs with 64 byte", func(b *testing.B) { benchmarkBulkAllocs(b, 64, 1000) })
b.Run("1000 docs with 1 KiB", func(b *testing.B) { benchmarkBulkAllocs(b, 1024, 1000) })
b.Run("1000 docs with 4 KiB", func(b *testing.B) { benchmarkBulkAllocs(b, 4096, 1000) })
b.Run("1000 docs with 16 KiB", func(b *testing.B) { benchmarkBulkAllocs(b, 16*1024, 1000) })
b.Run("1000 docs with 64 KiB", func(b *testing.B) { benchmarkBulkAllocs(b, 64*1024, 1000) })
b.Run("1000 docs with 256 KiB", func(b *testing.B) { benchmarkBulkAllocs(b, 256*1024, 1000) })
b.Run("1000 docs with 1 MiB", func(b *testing.B) { benchmarkBulkAllocs(b, 1024*1024, 1000) })
}
const (
charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-"
)
func benchmarkBulkAllocs(b *testing.B, size, num int) {
buf := make([]byte, size)
for i := range buf {
buf[i] = charset[rand.Intn(len(charset))]
}
s := &BulkService{}
n := 0
for {
n++
s = s.Add(NewBulkIndexRequest().Index("test").Id("1").Doc(struct {
S string `json:"s"`
}{
S: string(buf),
}))
if n >= num {
break
}
}
for i := 0; i < b.N; i++ {
s.bodyAsString()
}
b.ReportAllocs()
}
================================================
FILE: bulk_update_request.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
//go:generate easyjson bulk_update_request.go
import (
"encoding/json"
"fmt"
"strings"
)
// BulkUpdateRequest is a request to update a document in Elasticsearch.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-bulk.html
// for details.
type BulkUpdateRequest struct {
BulkableRequest
index string
typ string
id string
routing string
parent string
script *Script
scriptedUpsert *bool
version int64 // default is MATCH_ANY
versionType string // default is "internal"
retryOnConflict *int
upsert interface{}
docAsUpsert *bool
detectNoop *bool
doc interface{}
returnSource *bool
ifSeqNo *int64
ifPrimaryTerm *int64
source []string
useEasyJSON bool
}
//easyjson:json
type bulkUpdateRequestCommand map[string]bulkUpdateRequestCommandOp
//easyjson:json
type bulkUpdateRequestCommandOp struct {
Index string `json:"_index,omitempty"`
Type string `json:"_type,omitempty"`
Id string `json:"_id,omitempty"`
Parent string `json:"parent,omitempty"`
// RetryOnConflict is "_retry_on_conflict" for 6.0 and "retry_on_conflict" for 6.1+.
RetryOnConflict *int `json:"retry_on_conflict,omitempty"`
Routing string `json:"routing,omitempty"`
Version int64 `json:"version,omitempty"`
VersionType string `json:"version_type,omitempty"`
IfSeqNo *int64 `json:"if_seq_no,omitempty"`
IfPrimaryTerm *int64 `json:"if_primary_term,omitempty"`
}
//easyjson:json
type bulkUpdateRequestCommandData struct {
DetectNoop *bool `json:"detect_noop,omitempty"`
Doc interface{} `json:"doc,omitempty"`
DocAsUpsert *bool `json:"doc_as_upsert,omitempty"`
Script interface{} `json:"script,omitempty"`
ScriptedUpsert *bool `json:"scripted_upsert,omitempty"`
Upsert interface{} `json:"upsert,omitempty"`
Source *bool `json:"_source,omitempty"`
}
// NewBulkUpdateRequest returns a new BulkUpdateRequest.
func NewBulkUpdateRequest() *BulkUpdateRequest {
return &BulkUpdateRequest{}
}
// UseEasyJSON is an experimental setting that enables serialization
// with github.com/mailru/easyjson, which should in faster serialization
// time and less allocations, but removed compatibility with encoding/json,
// usage of unsafe etc. See https://github.com/mailru/easyjson#issues-notes-and-limitations
// for details. This setting is disabled by default.
func (r *BulkUpdateRequest) UseEasyJSON(enable bool) *BulkUpdateRequest {
r.useEasyJSON = enable
return r
}
// Index specifies the Elasticsearch index to use for this update request.
// If unspecified, the index set on the BulkService will be used.
func (r *BulkUpdateRequest) Index(index string) *BulkUpdateRequest {
r.index = index
r.source = nil
return r
}
// Type specifies the Elasticsearch type to use for this update request.
// If unspecified, the type set on the BulkService will be used.
func (r *BulkUpdateRequest) Type(typ string) *BulkUpdateRequest {
r.typ = typ
r.source = nil
return r
}
// Id specifies the identifier of the document to update.
func (r *BulkUpdateRequest) Id(id string) *BulkUpdateRequest {
r.id = id
r.source = nil
return r
}
// Routing specifies a routing value for the request.
func (r *BulkUpdateRequest) Routing(routing string) *BulkUpdateRequest {
r.routing = routing
r.source = nil
return r
}
// Parent specifies the identifier of the parent document (if available).
func (r *BulkUpdateRequest) Parent(parent string) *BulkUpdateRequest {
r.parent = parent
r.source = nil
return r
}
// Script specifies an update script.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-bulk.html#bulk-update
// and https://www.elastic.co/guide/en/elasticsearch/reference/7.0/modules-scripting.html
// for details.
func (r *BulkUpdateRequest) Script(script *Script) *BulkUpdateRequest {
r.script = script
r.source = nil
return r
}
// ScripedUpsert specifies if your script will run regardless of
// whether the document exists or not.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-update.html#_literal_scripted_upsert_literal
func (r *BulkUpdateRequest) ScriptedUpsert(upsert bool) *BulkUpdateRequest {
r.scriptedUpsert = &upsert
r.source = nil
return r
}
// RetryOnConflict specifies how often to retry in case of a version conflict.
func (r *BulkUpdateRequest) RetryOnConflict(retryOnConflict int) *BulkUpdateRequest {
r.retryOnConflict = &retryOnConflict
r.source = nil
return r
}
// Version indicates the version of the document as part of an optimistic
// concurrency model.
func (r *BulkUpdateRequest) Version(version int64) *BulkUpdateRequest {
r.version = version
r.source = nil
return r
}
// VersionType can be "internal" (default), "external", "external_gte",
// or "external_gt".
func (r *BulkUpdateRequest) VersionType(versionType string) *BulkUpdateRequest {
r.versionType = versionType
r.source = nil
return r
}
// IfSeqNo indicates to only perform the index operation if the last
// operation that has changed the document has the specified sequence number.
func (r *BulkUpdateRequest) IfSeqNo(ifSeqNo int64) *BulkUpdateRequest {
r.ifSeqNo = &ifSeqNo
return r
}
// IfPrimaryTerm indicates to only perform the index operation if the
// last operation that has changed the document has the specified primary term.
func (r *BulkUpdateRequest) IfPrimaryTerm(ifPrimaryTerm int64) *BulkUpdateRequest {
r.ifPrimaryTerm = &ifPrimaryTerm
return r
}
// Doc specifies the updated document.
func (r *BulkUpdateRequest) Doc(doc interface{}) *BulkUpdateRequest {
r.doc = doc
r.source = nil
return r
}
// DocAsUpsert indicates whether the contents of Doc should be used as
// the Upsert value.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-update.html#_literal_doc_as_upsert_literal
// for details.
func (r *BulkUpdateRequest) DocAsUpsert(docAsUpsert bool) *BulkUpdateRequest {
r.docAsUpsert = &docAsUpsert
r.source = nil
return r
}
// DetectNoop specifies whether changes that don't affect the document
// should be ignored (true) or unignored (false). This is enabled by default
// in Elasticsearch.
func (r *BulkUpdateRequest) DetectNoop(detectNoop bool) *BulkUpdateRequest {
r.detectNoop = &detectNoop
r.source = nil
return r
}
// Upsert specifies the document to use for upserts. It will be used for
// create if the original document does not exist.
func (r *BulkUpdateRequest) Upsert(doc interface{}) *BulkUpdateRequest {
r.upsert = doc
r.source = nil
return r
}
// ReturnSource specifies whether Elasticsearch should return the source
// after the update. In the request, this responds to the `_source` field.
// It is false by default.
func (r *BulkUpdateRequest) ReturnSource(source bool) *BulkUpdateRequest {
r.returnSource = &source
r.source = nil
return r
}
// String returns the on-wire representation of the update request,
// concatenated as a single string.
func (r *BulkUpdateRequest) String() string {
lines, err := r.Source()
if err != nil {
return fmt.Sprintf("error: %v", err)
}
return strings.Join(lines, "\n")
}
// Source returns the on-wire representation of the update request,
// split into an action-and-meta-data line and an (optional) source line.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-bulk.html
// for details.
func (r *BulkUpdateRequest) Source() ([]string, error) {
// { "update" : { "_index" : "test", "_type" : "type1", "_id" : "1", ... } }
// { "doc" : { "field1" : "value1", ... } }
// or
// { "update" : { "_index" : "test", "_type" : "type1", "_id" : "1", ... } }
// { "script" : { ... } }
if r.source != nil {
return r.source, nil
}
lines := make([]string, 2)
// "update" ...
updateCommand := bulkUpdateRequestCommandOp{
Index: r.index,
Type: r.typ,
Id: r.id,
Routing: r.routing,
Parent: r.parent,
Version: r.version,
VersionType: r.versionType,
RetryOnConflict: r.retryOnConflict,
IfSeqNo: r.ifSeqNo,
IfPrimaryTerm: r.ifPrimaryTerm,
}
command := bulkUpdateRequestCommand{
"update": updateCommand,
}
var err error
var body []byte
if r.useEasyJSON {
// easyjson
body, err = command.MarshalJSON()
} else {
// encoding/json
body, err = json.Marshal(command)
}
if err != nil {
return nil, err
}
lines[0] = string(body)
// 2nd line: {"doc" : { ... }} or {"script": {...}}
var doc interface{}
if r.doc != nil {
// Automatically serialize strings as raw JSON
switch t := r.doc.(type) {
default:
doc = r.doc
case string:
if len(t) > 0 {
doc = json.RawMessage(t)
}
case *string:
if t != nil && len(*t) > 0 {
doc = json.RawMessage(*t)
}
}
}
data := bulkUpdateRequestCommandData{
DocAsUpsert: r.docAsUpsert,
DetectNoop: r.detectNoop,
Upsert: r.upsert,
ScriptedUpsert: r.scriptedUpsert,
Doc: doc,
Source: r.returnSource,
}
if r.script != nil {
script, err := r.script.Source()
if err != nil {
return nil, err
}
data.Script = script
}
if r.useEasyJSON {
// easyjson
body, err = data.MarshalJSON()
} else {
// encoding/json
body, err = json.Marshal(data)
}
if err != nil {
return nil, err
}
lines[1] = string(body)
r.source = lines
return lines, nil
}
================================================
FILE: bulk_update_request_easyjson.go
================================================
// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT.
package elastic
import (
json "encoding/json"
easyjson "github.com/mailru/easyjson"
jlexer "github.com/mailru/easyjson/jlexer"
jwriter "github.com/mailru/easyjson/jwriter"
)
// suppress unused package warning
var (
_ *json.RawMessage
_ *jlexer.Lexer
_ *jwriter.Writer
_ easyjson.Marshaler
)
func easyjson1ed00e60DecodeGithubComOlivereElasticV7(in *jlexer.Lexer, out *bulkUpdateRequestCommandOp) {
isTopLevel := in.IsStart()
if in.IsNull() {
if isTopLevel {
in.Consumed()
}
in.Skip()
return
}
in.Delim('{')
for !in.IsDelim('}') {
key := in.UnsafeFieldName(false)
in.WantColon()
if in.IsNull() {
in.Skip()
in.WantComma()
continue
}
switch key {
case "_index":
out.Index = string(in.String())
case "_type":
out.Type = string(in.String())
case "_id":
out.Id = string(in.String())
case "parent":
out.Parent = string(in.String())
case "retry_on_conflict":
if in.IsNull() {
in.Skip()
out.RetryOnConflict = nil
} else {
if out.RetryOnConflict == nil {
out.RetryOnConflict = new(int)
}
*out.RetryOnConflict = int(in.Int())
}
case "routing":
out.Routing = string(in.String())
case "version":
out.Version = int64(in.Int64())
case "version_type":
out.VersionType = string(in.String())
case "if_seq_no":
if in.IsNull() {
in.Skip()
out.IfSeqNo = nil
} else {
if out.IfSeqNo == nil {
out.IfSeqNo = new(int64)
}
*out.IfSeqNo = int64(in.Int64())
}
case "if_primary_term":
if in.IsNull() {
in.Skip()
out.IfPrimaryTerm = nil
} else {
if out.IfPrimaryTerm == nil {
out.IfPrimaryTerm = new(int64)
}
*out.IfPrimaryTerm = int64(in.Int64())
}
default:
in.SkipRecursive()
}
in.WantComma()
}
in.Delim('}')
if isTopLevel {
in.Consumed()
}
}
func easyjson1ed00e60EncodeGithubComOlivereElasticV7(out *jwriter.Writer, in bulkUpdateRequestCommandOp) {
out.RawByte('{')
first := true
_ = first
if in.Index != "" {
const prefix string = ",\"_index\":"
first = false
out.RawString(prefix[1:])
out.String(string(in.Index))
}
if in.Type != "" {
const prefix string = ",\"_type\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Type))
}
if in.Id != "" {
const prefix string = ",\"_id\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Id))
}
if in.Parent != "" {
const prefix string = ",\"parent\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Parent))
}
if in.RetryOnConflict != nil {
const prefix string = ",\"retry_on_conflict\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Int(int(*in.RetryOnConflict))
}
if in.Routing != "" {
const prefix string = ",\"routing\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Routing))
}
if in.Version != 0 {
const prefix string = ",\"version\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Int64(int64(in.Version))
}
if in.VersionType != "" {
const prefix string = ",\"version_type\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.VersionType))
}
if in.IfSeqNo != nil {
const prefix string = ",\"if_seq_no\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Int64(int64(*in.IfSeqNo))
}
if in.IfPrimaryTerm != nil {
const prefix string = ",\"if_primary_term\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Int64(int64(*in.IfPrimaryTerm))
}
out.RawByte('}')
}
// MarshalJSON supports json.Marshaler interface
func (v bulkUpdateRequestCommandOp) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{}
easyjson1ed00e60EncodeGithubComOlivereElasticV7(&w, v)
return w.Buffer.BuildBytes(), w.Error
}
// MarshalEasyJSON supports easyjson.Marshaler interface
func (v bulkUpdateRequestCommandOp) MarshalEasyJSON(w *jwriter.Writer) {
easyjson1ed00e60EncodeGithubComOlivereElasticV7(w, v)
}
// UnmarshalJSON supports json.Unmarshaler interface
func (v *bulkUpdateRequestCommandOp) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data}
easyjson1ed00e60DecodeGithubComOlivereElasticV7(&r, v)
return r.Error()
}
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *bulkUpdateRequestCommandOp) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson1ed00e60DecodeGithubComOlivereElasticV7(l, v)
}
func easyjson1ed00e60DecodeGithubComOlivereElasticV71(in *jlexer.Lexer, out *bulkUpdateRequestCommandData) {
isTopLevel := in.IsStart()
if in.IsNull() {
if isTopLevel {
in.Consumed()
}
in.Skip()
return
}
in.Delim('{')
for !in.IsDelim('}') {
key := in.UnsafeFieldName(false)
in.WantColon()
if in.IsNull() {
in.Skip()
in.WantComma()
continue
}
switch key {
case "detect_noop":
if in.IsNull() {
in.Skip()
out.DetectNoop = nil
} else {
if out.DetectNoop == nil {
out.DetectNoop = new(bool)
}
*out.DetectNoop = bool(in.Bool())
}
case "doc":
if m, ok := out.Doc.(easyjson.Unmarshaler); ok {
m.UnmarshalEasyJSON(in)
} else if m, ok := out.Doc.(json.Unmarshaler); ok {
_ = m.UnmarshalJSON(in.Raw())
} else {
out.Doc = in.Interface()
}
case "doc_as_upsert":
if in.IsNull() {
in.Skip()
out.DocAsUpsert = nil
} else {
if out.DocAsUpsert == nil {
out.DocAsUpsert = new(bool)
}
*out.DocAsUpsert = bool(in.Bool())
}
case "script":
if m, ok := out.Script.(easyjson.Unmarshaler); ok {
m.UnmarshalEasyJSON(in)
} else if m, ok := out.Script.(json.Unmarshaler); ok {
_ = m.UnmarshalJSON(in.Raw())
} else {
out.Script = in.Interface()
}
case "scripted_upsert":
if in.IsNull() {
in.Skip()
out.ScriptedUpsert = nil
} else {
if out.ScriptedUpsert == nil {
out.ScriptedUpsert = new(bool)
}
*out.ScriptedUpsert = bool(in.Bool())
}
case "upsert":
if m, ok := out.Upsert.(easyjson.Unmarshaler); ok {
m.UnmarshalEasyJSON(in)
} else if m, ok := out.Upsert.(json.Unmarshaler); ok {
_ = m.UnmarshalJSON(in.Raw())
} else {
out.Upsert = in.Interface()
}
case "_source":
if in.IsNull() {
in.Skip()
out.Source = nil
} else {
if out.Source == nil {
out.Source = new(bool)
}
*out.Source = bool(in.Bool())
}
default:
in.SkipRecursive()
}
in.WantComma()
}
in.Delim('}')
if isTopLevel {
in.Consumed()
}
}
func easyjson1ed00e60EncodeGithubComOlivereElasticV71(out *jwriter.Writer, in bulkUpdateRequestCommandData) {
out.RawByte('{')
first := true
_ = first
if in.DetectNoop != nil {
const prefix string = ",\"detect_noop\":"
first = false
out.RawString(prefix[1:])
out.Bool(bool(*in.DetectNoop))
}
if in.Doc != nil {
const prefix string = ",\"doc\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
if m, ok := in.Doc.(easyjson.Marshaler); ok {
m.MarshalEasyJSON(out)
} else if m, ok := in.Doc.(json.Marshaler); ok {
out.Raw(m.MarshalJSON())
} else {
out.Raw(json.Marshal(in.Doc))
}
}
if in.DocAsUpsert != nil {
const prefix string = ",\"doc_as_upsert\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Bool(bool(*in.DocAsUpsert))
}
if in.Script != nil {
const prefix string = ",\"script\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
if m, ok := in.Script.(easyjson.Marshaler); ok {
m.MarshalEasyJSON(out)
} else if m, ok := in.Script.(json.Marshaler); ok {
out.Raw(m.MarshalJSON())
} else {
out.Raw(json.Marshal(in.Script))
}
}
if in.ScriptedUpsert != nil {
const prefix string = ",\"scripted_upsert\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Bool(bool(*in.ScriptedUpsert))
}
if in.Upsert != nil {
const prefix string = ",\"upsert\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
if m, ok := in.Upsert.(easyjson.Marshaler); ok {
m.MarshalEasyJSON(out)
} else if m, ok := in.Upsert.(json.Marshaler); ok {
out.Raw(m.MarshalJSON())
} else {
out.Raw(json.Marshal(in.Upsert))
}
}
if in.Source != nil {
const prefix string = ",\"_source\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Bool(bool(*in.Source))
}
out.RawByte('}')
}
// MarshalJSON supports json.Marshaler interface
func (v bulkUpdateRequestCommandData) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{}
easyjson1ed00e60EncodeGithubComOlivereElasticV71(&w, v)
return w.Buffer.BuildBytes(), w.Error
}
// MarshalEasyJSON supports easyjson.Marshaler interface
func (v bulkUpdateRequestCommandData) MarshalEasyJSON(w *jwriter.Writer) {
easyjson1ed00e60EncodeGithubComOlivereElasticV71(w, v)
}
// UnmarshalJSON supports json.Unmarshaler interface
func (v *bulkUpdateRequestCommandData) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data}
easyjson1ed00e60DecodeGithubComOlivereElasticV71(&r, v)
return r.Error()
}
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *bulkUpdateRequestCommandData) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson1ed00e60DecodeGithubComOlivereElasticV71(l, v)
}
func easyjson1ed00e60DecodeGithubComOlivereElasticV72(in *jlexer.Lexer, out *bulkUpdateRequestCommand) {
isTopLevel := in.IsStart()
if in.IsNull() {
in.Skip()
} else {
in.Delim('{')
*out = make(bulkUpdateRequestCommand)
for !in.IsDelim('}') {
key := string(in.String())
in.WantColon()
var v1 bulkUpdateRequestCommandOp
(v1).UnmarshalEasyJSON(in)
(*out)[key] = v1
in.WantComma()
}
in.Delim('}')
}
if isTopLevel {
in.Consumed()
}
}
func easyjson1ed00e60EncodeGithubComOlivereElasticV72(out *jwriter.Writer, in bulkUpdateRequestCommand) {
if in == nil && (out.Flags&jwriter.NilMapAsEmpty) == 0 {
out.RawString(`null`)
} else {
out.RawByte('{')
v2First := true
for v2Name, v2Value := range in {
if v2First {
v2First = false
} else {
out.RawByte(',')
}
out.String(string(v2Name))
out.RawByte(':')
(v2Value).MarshalEasyJSON(out)
}
out.RawByte('}')
}
}
// MarshalJSON supports json.Marshaler interface
func (v bulkUpdateRequestCommand) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{}
easyjson1ed00e60EncodeGithubComOlivereElasticV72(&w, v)
return w.Buffer.BuildBytes(), w.Error
}
// MarshalEasyJSON supports easyjson.Marshaler interface
func (v bulkUpdateRequestCommand) MarshalEasyJSON(w *jwriter.Writer) {
easyjson1ed00e60EncodeGithubComOlivereElasticV72(w, v)
}
// UnmarshalJSON supports json.Unmarshaler interface
func (v *bulkUpdateRequestCommand) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data}
easyjson1ed00e60DecodeGithubComOlivereElasticV72(&r, v)
return r.Error()
}
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *bulkUpdateRequestCommand) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson1ed00e60DecodeGithubComOlivereElasticV72(l, v)
}
================================================
FILE: bulk_update_request_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestBulkUpdateRequestSerialization(t *testing.T) {
rawJson := json.RawMessage(`{"counter":42}`)
rawString := `{"counter":42}`
tests := []struct {
Request BulkableRequest
Expected []string
}{
// #0
{
Request: NewBulkUpdateRequest().Index("index1").Id("1").Doc(struct {
Counter int64 `json:"counter"`
}{
Counter: 42,
}),
Expected: []string{
`{"update":{"_index":"index1","_id":"1"}}`,
`{"doc":{"counter":42}}`,
},
},
// #1
{
Request: NewBulkUpdateRequest().Index("index1").Id("1").
Routing("123").
RetryOnConflict(3).
DocAsUpsert(true).
Doc(struct {
Counter int64 `json:"counter"`
}{
Counter: 42,
}),
Expected: []string{
`{"update":{"_index":"index1","_id":"1","retry_on_conflict":3,"routing":"123"}}`,
`{"doc":{"counter":42},"doc_as_upsert":true}`,
},
},
// #2
{
Request: NewBulkUpdateRequest().Index("index1").Id("1").
RetryOnConflict(3).
Script(NewScript(`ctx._source.retweets += param1`).Lang("javascript").Param("param1", 42)).
Upsert(struct {
Counter int64 `json:"counter"`
}{
Counter: 42,
}),
Expected: []string{
`{"update":{"_index":"index1","_id":"1","retry_on_conflict":3}}`,
`{"script":{"lang":"javascript","params":{"param1":42},"source":"ctx._source.retweets += param1"},"upsert":{"counter":42}}`,
},
},
// #3
{
Request: NewBulkUpdateRequest().Index("index1").Id("1").DetectNoop(true).Doc(struct {
Counter int64 `json:"counter"`
}{
Counter: 42,
}),
Expected: []string{
`{"update":{"_index":"index1","_id":"1"}}`,
`{"detect_noop":true,"doc":{"counter":42}}`,
},
},
// #4
{
Request: NewBulkUpdateRequest().Index("index1").Id("1").
RetryOnConflict(3).
ScriptedUpsert(true).
Script(NewScript(`ctx._source.retweets += param1`).Lang("javascript").Param("param1", 42)).
Upsert(struct {
Counter int64 `json:"counter"`
}{
Counter: 42,
}),
Expected: []string{
`{"update":{"_index":"index1","_id":"1","retry_on_conflict":3}}`,
`{"script":{"lang":"javascript","params":{"param1":42},"source":"ctx._source.retweets += param1"},"scripted_upsert":true,"upsert":{"counter":42}}`,
},
},
// #5
{
Request: NewBulkUpdateRequest().Index("index1").Id("4").ReturnSource(true).Doc(struct {
Counter int64 `json:"counter"`
}{
Counter: 42,
}),
Expected: []string{
`{"update":{"_index":"index1","_id":"4"}}`,
`{"doc":{"counter":42},"_source":true}`,
},
},
// #6
{
Request: NewBulkUpdateRequest().Index("index1").Id("4").
ReturnSource(true).
Doc(`{"counter":42}`),
Expected: []string{
`{"update":{"_index":"index1","_id":"4"}}`,
`{"doc":{"counter":42},"_source":true}`,
},
},
// #7
{
Request: NewBulkUpdateRequest().Index("index1").Id("4").
ReturnSource(true).
Doc(json.RawMessage(`{"counter":42}`)),
Expected: []string{
`{"update":{"_index":"index1","_id":"4"}}`,
`{"doc":{"counter":42},"_source":true}`,
},
},
// #8
{
Request: NewBulkUpdateRequest().Index("index1").Id("4").
ReturnSource(true).
Doc(&rawJson),
Expected: []string{
`{"update":{"_index":"index1","_id":"4"}}`,
`{"doc":{"counter":42},"_source":true}`,
},
},
// #9
{
Request: NewBulkUpdateRequest().Index("index1").Id("4").
ReturnSource(true).
Doc(&rawString),
Expected: []string{
`{"update":{"_index":"index1","_id":"4"}}`,
`{"doc":{"counter":42},"_source":true}`,
},
},
}
for i, test := range tests {
lines, err := test.Request.Source()
if err != nil {
t.Fatalf("#%d: expected no error, got: %v", i, err)
}
if lines == nil {
t.Fatalf("#%d: expected lines, got nil", i)
}
if len(lines) != len(test.Expected) {
t.Fatalf("#%d: expected %d lines, got %d", i, len(test.Expected), len(lines))
}
for j, line := range lines {
if line != test.Expected[j] {
t.Errorf("#%d: expected line #%d to be\n%s\nbut got:\n%s", i, j, test.Expected[j], line)
}
}
}
}
var bulkUpdateRequestSerializationResult string
func BenchmarkBulkUpdateRequestSerialization(b *testing.B) {
b.Run("stdlib", func(b *testing.B) {
r := NewBulkUpdateRequest().Index("index1").Id("1").Doc(struct {
Counter int64 `json:"counter"`
}{
Counter: 42,
})
benchmarkBulkUpdateRequestSerialization(b, r.UseEasyJSON(false))
})
b.Run("easyjson", func(b *testing.B) {
r := NewBulkUpdateRequest().Index("index1").Id("1").Doc(struct {
Counter int64 `json:"counter"`
}{
Counter: 42,
}).UseEasyJSON(false)
benchmarkBulkUpdateRequestSerialization(b, r.UseEasyJSON(true))
})
}
func benchmarkBulkUpdateRequestSerialization(b *testing.B, r *BulkUpdateRequest) {
var s string
for n := 0; n < b.N; n++ {
s = r.String()
r.source = nil // Don't let caching spoil the benchmark
}
bulkUpdateRequestSerializationResult = s // ensure the compiler doesn't optimize
b.ReportAllocs()
}
================================================
FILE: canonicalize.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"net/url"
"strings"
)
// canonicalize takes a list of URLs and returns its canonicalized form, i.e.
// remove anything but scheme, userinfo, host, path, and port.
// It also removes all trailing slashes. Invalid URLs or URLs that do not
// use protocol http or https are skipped.
//
// Example:
// http://127.0.0.1:9200/?query=1 -> http://127.0.0.1:9200
// http://127.0.0.1:9200/db1/ -> http://127.0.0.1:9200/db1
// http://127.0.0.1:9200/db1/// -> http://127.0.0.1:9200/db1
func canonicalize(rawurls ...string) []string {
var canonicalized []string
for _, rawurl := range rawurls {
u, err := url.Parse(rawurl)
if err == nil {
if u.Scheme == "http" || u.Scheme == "https" {
// Trim trailing slashes. Notice that strings.TrimSuffix will only remove the last slash,
// not all slashes from the suffix, so we'll loop over the path to remove all slashes.
for strings.HasSuffix(u.Path, "/") {
u.Path = u.Path[:len(u.Path)-1]
}
u.Fragment = ""
u.RawQuery = ""
canonicalized = append(canonicalized, u.String())
}
}
}
return canonicalized
}
================================================
FILE: canonicalize_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import "testing"
func TestCanonicalize(t *testing.T) {
tests := []struct {
Input []string
Output []string
}{
// #0
{
Input: []string{"http://127.0.0.1/"},
Output: []string{"http://127.0.0.1"},
},
// #1
{
Input: []string{"http://127.0.0.1:9200/", "gopher://golang.org/", "http://127.0.0.1:9201"},
Output: []string{"http://127.0.0.1:9200", "http://127.0.0.1:9201"},
},
// #2
{
Input: []string{"http://user:secret@127.0.0.1/path?query=1#fragment"},
Output: []string{"http://user:secret@127.0.0.1/path"},
},
// #3
{
Input: []string{"https://somewhere.on.mars:9999/path?query=1#fragment"},
Output: []string{"https://somewhere.on.mars:9999/path"},
},
// #4
{
Input: []string{"https://prod1:9999/one?query=1#fragment", "https://prod2:9998/two?query=1#fragment"},
Output: []string{"https://prod1:9999/one", "https://prod2:9998/two"},
},
// #5
{
Input: []string{"http://127.0.0.1/one/"},
Output: []string{"http://127.0.0.1/one"},
},
// #6
{
Input: []string{"http://127.0.0.1/one///"},
Output: []string{"http://127.0.0.1/one"},
},
// #7: Invalid URL
{
Input: []string{"127.0.0.1/"},
Output: []string{},
},
// #8: Invalid URL
{
Input: []string{"127.0.0.1:9200"},
Output: []string{},
},
}
for i, test := range tests {
got := canonicalize(test.Input...)
if want, have := len(test.Output), len(got); want != have {
t.Fatalf("#%d: expected %d elements; got: %d", i, want, have)
}
for i := 0; i < len(got); i++ {
if want, have := test.Output[i], got[i]; want != have {
t.Errorf("#%d: expected %q; got: %q", i, want, have)
}
}
}
}
================================================
FILE: cat_aliases.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// CatAliasesService shows information about currently configured aliases
// to indices including filter and routing infos.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/cat-aliases.html
// for details.
type CatAliasesService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
local *bool
masterTimeout string
aliases []string
columns []string
sort []string // list of columns for sort order
}
// NewCatAliasesService creates a new CatAliasesService.
func NewCatAliasesService(client *Client) *CatAliasesService {
return &CatAliasesService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *CatAliasesService) Pretty(pretty bool) *CatAliasesService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *CatAliasesService) Human(human bool) *CatAliasesService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *CatAliasesService) ErrorTrace(errorTrace bool) *CatAliasesService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *CatAliasesService) FilterPath(filterPath ...string) *CatAliasesService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *CatAliasesService) Header(name string, value string) *CatAliasesService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *CatAliasesService) Headers(headers http.Header) *CatAliasesService {
s.headers = headers
return s
}
// Alias specifies one or more aliases to which information should be returned.
func (s *CatAliasesService) Alias(alias ...string) *CatAliasesService {
s.aliases = alias
return s
}
// Local indicates to return local information, i.e. do not retrieve
// the state from master node (default: false).
func (s *CatAliasesService) Local(local bool) *CatAliasesService {
s.local = &local
return s
}
// MasterTimeout is the explicit operation timeout for connection to master node.
func (s *CatAliasesService) MasterTimeout(masterTimeout string) *CatAliasesService {
s.masterTimeout = masterTimeout
return s
}
// Columns to return in the response.
// To get a list of all possible columns to return, run the following command
// in your terminal:
//
// Example:
// curl 'http://localhost:9200/_cat/aliases?help'
//
// You can use Columns("*") to return all possible columns. That might take
// a little longer than the default set of columns.
func (s *CatAliasesService) Columns(columns ...string) *CatAliasesService {
s.columns = columns
return s
}
// Sort is a list of fields to sort by.
func (s *CatAliasesService) Sort(fields ...string) *CatAliasesService {
s.sort = fields
return s
}
// buildURL builds the URL for the operation.
func (s *CatAliasesService) buildURL() (string, url.Values, error) {
// Build URL
var (
path string
err error
)
if len(s.aliases) > 0 {
path, err = uritemplates.Expand("/_cat/aliases/{name}", map[string]string{
"name": strings.Join(s.aliases, ","),
})
} else {
path = "/_cat/aliases"
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{
"format": []string{"json"}, // always returns as JSON
}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if v := s.local; v != nil {
params.Set("local", fmt.Sprint(*v))
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
if len(s.sort) > 0 {
params.Set("s", strings.Join(s.sort, ","))
}
if len(s.columns) > 0 {
params.Set("h", strings.Join(s.columns, ","))
}
return path, params, nil
}
// Do executes the operation.
func (s *CatAliasesService) Do(ctx context.Context) (CatAliasesResponse, error) {
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
var ret CatAliasesResponse
if err := s.client.decoder.Decode(res.Body, &ret); err != nil {
return nil, err
}
return ret, nil
}
// -- Result of a get request.
// CatAliasesResponse is the outcome of CatAliasesService.Do.
type CatAliasesResponse []CatAliasesResponseRow
// CatAliasesResponseRow is a single row in a CatAliasesResponse.
// Notice that not all of these fields might be filled; that depends
// on the number of columns chose in the request (see CatAliasesService.Columns).
type CatAliasesResponseRow struct {
// Alias name.
Alias string `json:"alias"`
// Index the alias points to.
Index string `json:"index"`
// Filter, e.g. "*" or "-".
Filter string `json:"filter"`
// RoutingIndex specifies the index routing (or "-").
RoutingIndex string `json:"routing.index"`
// RoutingSearch specifies the search routing (or "-").
RoutingSearch string `json:"routing.search"`
// IsWriteIndex indicates whether the index can be written to (or "-").
IsWriteIndex string `json:"is_write_index"`
}
================================================
FILE: cat_aliases_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestCatAliases(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t, SetDecoder(&strictDecoder{})) //, SetTraceLog(log.New(os.Stdout, "", 0)))
ctx := context.Background()
// Add two aliases
_, err := client.Alias().
Add(testIndexName, testAliasName).
Action(NewAliasAddAction(testAliasName2).Index(testIndexName2)).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
defer func() {
// Remove aliases
client.Alias().
Remove(testIndexName, testAliasName).
Remove(testIndexName2, testAliasName2).
Do(context.TODO())
}()
// Check all aliases
res, err := client.CatAliases().Pretty(true).Columns("*").Do(ctx)
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("want response, have nil")
}
if want, have := 2, len(res); want != have {
t.Fatalf("want len=%d, have %d", want, have)
}
// Check a named alias
res, err = client.CatAliases().Alias(testAliasName).Pretty(true).Do(ctx)
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("want response, have nil")
}
if want, have := 1, len(res); want != have {
t.Fatalf("want len=%d, have %d", want, have)
}
}
================================================
FILE: cat_allocation.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// CatAllocationService provides a snapshot of how many shards are allocated
// to each data node and how much disk space they are using.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/cat-allocation.html
// for details.
type CatAllocationService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
bytes string // b, k, m, or g
local *bool
masterTimeout string
nodes []string
columns []string
sort []string // list of columns for sort order
}
// NewCatAllocationService creates a new CatAllocationService.
func NewCatAllocationService(client *Client) *CatAllocationService {
return &CatAllocationService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *CatAllocationService) Pretty(pretty bool) *CatAllocationService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *CatAllocationService) Human(human bool) *CatAllocationService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *CatAllocationService) ErrorTrace(errorTrace bool) *CatAllocationService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *CatAllocationService) FilterPath(filterPath ...string) *CatAllocationService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *CatAllocationService) Header(name string, value string) *CatAllocationService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *CatAllocationService) Headers(headers http.Header) *CatAllocationService {
s.headers = headers
return s
}
// NodeID specifies one or more node IDs to for information should be returned.
func (s *CatAllocationService) NodeID(nodes ...string) *CatAllocationService {
s.nodes = nodes
return s
}
// Bytes represents the unit in which to display byte values.
// Valid values are: "b", "k", "m", or "g".
func (s *CatAllocationService) Bytes(bytes string) *CatAllocationService {
s.bytes = bytes
return s
}
// Local indicates to return local information, i.e. do not retrieve
// the state from master node (default: false).
func (s *CatAllocationService) Local(local bool) *CatAllocationService {
s.local = &local
return s
}
// MasterTimeout is the explicit operation timeout for connection to master node.
func (s *CatAllocationService) MasterTimeout(masterTimeout string) *CatAllocationService {
s.masterTimeout = masterTimeout
return s
}
// Columns to return in the response.
// To get a list of all possible columns to return, run the following command
// in your terminal:
//
// Example:
// curl 'http://localhost:9200/_cat/aliases?help'
//
// You can use Columns("*") to return all possible columns. That might take
// a little longer than the default set of columns.
func (s *CatAllocationService) Columns(columns ...string) *CatAllocationService {
s.columns = columns
return s
}
// Sort is a list of fields to sort by.
func (s *CatAllocationService) Sort(fields ...string) *CatAllocationService {
s.sort = fields
return s
}
// buildURL builds the URL for the operation.
func (s *CatAllocationService) buildURL() (string, url.Values, error) {
// Build URL
var (
path string
err error
)
if len(s.nodes) > 0 {
path, err = uritemplates.Expand("/_cat/allocation/{node_id}", map[string]string{
"node_id": strings.Join(s.nodes, ","),
})
} else {
path = "/_cat/allocation"
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{
"format": []string{"json"}, // always returns as JSON
}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.bytes != "" {
params.Set("bytes", s.bytes)
}
if v := s.local; v != nil {
params.Set("local", fmt.Sprint(*v))
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
if len(s.sort) > 0 {
params.Set("s", strings.Join(s.sort, ","))
}
if len(s.columns) > 0 {
params.Set("h", strings.Join(s.columns, ","))
}
return path, params, nil
}
// Do executes the operation.
func (s *CatAllocationService) Do(ctx context.Context) (CatAllocationResponse, error) {
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
var ret CatAllocationResponse
if err := s.client.decoder.Decode(res.Body, &ret); err != nil {
return nil, err
}
return ret, nil
}
// -- Result of a get request.
// CatAllocationResponse is the outcome of CatAllocationService.Do.
type CatAllocationResponse []CatAllocationResponseRow
// CatAllocationResponseRow is a single row in a CatAllocationResponse.
// Notice that not all of these fields might be filled; that depends
// on the number of columns chose in the request (see CatAllocationService.Columns).
type CatAllocationResponseRow struct {
// Shards represents the number of shards on a node.
Shards int `json:"shards,string"`
// DiskIndices represents the disk used by ES indices, e.g. "46.1kb".
DiskIndices string `json:"disk.indices"`
// DiskUsed represents the disk used (total, not just ES), e.g. "34.5gb"
DiskUsed string `json:"disk.used"`
// DiskAvail represents the disk available, e.g. "53.2gb".
DiskAvail string `json:"disk.avail"`
// DiskTotal represents the total capacity of all volumes, e.g. "87.7gb".
DiskTotal string `json:"disk.total"`
// DiskPercent represents the percent of disk used, e.g. 39.
DiskPercent int `json:"disk.percent,string"`
// Host represents the hostname of the node.
Host string `json:"host"`
// IP represents the IP address of the node.
IP string `json:"ip"`
// Node represents the node ID.
Node string `json:"node"`
}
================================================
FILE: cat_allocation_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestCatAllocation(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t, SetDecoder(&strictDecoder{})) // , SetTraceLog(log.New(os.Stdout, "", 0)))
ctx := context.Background()
res, err := client.CatAllocation().Columns("*").Do(ctx)
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("want response, have nil")
}
if len(res) == 0 {
t.Fatalf("want response, have: %v", res)
}
if have := res[0].IP; have == "" {
t.Fatalf("IP[0]: want != %q, have %q", "", have)
}
}
================================================
FILE: cat_count.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// CatCountService provides quick access to the document count of the entire cluster,
// or individual indices.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/cat-count.html
// for details.
type CatCountService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
index []string
local *bool
masterTimeout string
columns []string
sort []string // list of columns for sort order
}
// NewCatCountService creates a new CatCountService.
func NewCatCountService(client *Client) *CatCountService {
return &CatCountService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *CatCountService) Pretty(pretty bool) *CatCountService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *CatCountService) Human(human bool) *CatCountService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *CatCountService) ErrorTrace(errorTrace bool) *CatCountService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *CatCountService) FilterPath(filterPath ...string) *CatCountService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *CatCountService) Header(name string, value string) *CatCountService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *CatCountService) Headers(headers http.Header) *CatCountService {
s.headers = headers
return s
}
// Index specifies zero or more indices for which to return counts
// (by default counts for all indices are returned).
func (s *CatCountService) Index(index ...string) *CatCountService {
s.index = index
return s
}
// Local indicates to return local information, i.e. do not retrieve
// the state from master node (default: false).
func (s *CatCountService) Local(local bool) *CatCountService {
s.local = &local
return s
}
// MasterTimeout is the explicit operation timeout for connection to master node.
func (s *CatCountService) MasterTimeout(masterTimeout string) *CatCountService {
s.masterTimeout = masterTimeout
return s
}
// Columns to return in the response.
// To get a list of all possible columns to return, run the following command
// in your terminal:
//
// Example:
// curl 'http://localhost:9200/_cat/count?help'
//
// You can use Columns("*") to return all possible columns. That might take
// a little longer than the default set of columns.
func (s *CatCountService) Columns(columns ...string) *CatCountService {
s.columns = columns
return s
}
// Sort is a list of fields to sort by.
func (s *CatCountService) Sort(fields ...string) *CatCountService {
s.sort = fields
return s
}
// buildURL builds the URL for the operation.
func (s *CatCountService) buildURL() (string, url.Values, error) {
// Build URL
var (
path string
err error
)
if len(s.index) > 0 {
path, err = uritemplates.Expand("/_cat/count/{index}", map[string]string{
"index": strings.Join(s.index, ","),
})
} else {
path = "/_cat/count"
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{
"format": []string{"json"}, // always returns as JSON
}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if v := s.local; v != nil {
params.Set("local", fmt.Sprint(*v))
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
if len(s.sort) > 0 {
params.Set("s", strings.Join(s.sort, ","))
}
if len(s.columns) > 0 {
params.Set("h", strings.Join(s.columns, ","))
}
return path, params, nil
}
// Do executes the operation.
func (s *CatCountService) Do(ctx context.Context) (CatCountResponse, error) {
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
var ret CatCountResponse
if err := s.client.decoder.Decode(res.Body, &ret); err != nil {
return nil, err
}
return ret, nil
}
// -- Result of a get request.
// CatCountResponse is the outcome of CatCountService.Do.
type CatCountResponse []CatCountResponseRow
// CatCountResponseRow specifies the data returned for one index
// of a CatCountResponse. Notice that not all of these fields might
// be filled; that depends on the number of columns chose in the
// request (see CatCountService.Columns).
type CatCountResponseRow struct {
Epoch int64 `json:"epoch,string"` // e.g. 1527077996
Timestamp string `json:"timestamp"` // e.g. "12:19:56"
Count int `json:"count,string"` // number of documents
}
================================================
FILE: cat_count_integration_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestCatCountIntegration(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t) //, SetTraceLog(log.New(os.Stdout, "", 0)))
var (
total int64
indices = []string{testIndexName, testIndexName2, testOrderIndex}
)
for _, index := range indices {
count, err := client.Count(index).Do(context.Background())
if err != nil {
t.Fatal(err)
}
total += count
}
resp, err := client.CatCount().
Index(indices...).
Columns("*").
Pretty(true).
Do(context.Background())
if err != nil {
t.Fatal(err)
}
if want, have := 1, len(resp); want != have {
t.Fatalf("expected %d response item, got %d", want, have)
}
if want, have := total, int64(resp[0].Count); want != have {
t.Fatalf("expected %d documents, got %d", want, have)
}
}
================================================
FILE: cat_count_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestCatCount(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t, SetDecoder(&strictDecoder{})) // , SetTraceLog(log.New(os.Stdout, "", 0)))
ctx := context.Background()
res, err := client.CatCount().Pretty(true).Columns("*").Do(ctx)
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("want response, have nil")
}
if len(res) == 0 {
t.Fatalf("want response, have: %v", res)
}
if have := res[0].Count; have <= 0 {
t.Fatalf("Count[0]: want > %d, have %d", 0, have)
}
}
================================================
FILE: cat_fielddata.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// CatFielddataService Returns the amount of heap memory currently used by
// the field data cache on every data node in the cluster.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.12/cat-fielddata.html
// for details.
type CatFielddataService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
fields []string // list of fields used to limit returned information
bytes string // b, k, m, or g
columns []string
sort []string // list of columns for sort order
}
// NewCatFielddataService creates a new NewCatFielddataService.
func NewCatFielddataService(client *Client) *CatFielddataService {
return &CatFielddataService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *CatFielddataService) Pretty(pretty bool) *CatFielddataService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *CatFielddataService) Human(human bool) *CatFielddataService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *CatFielddataService) ErrorTrace(errorTrace bool) *CatFielddataService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *CatFielddataService) FilterPath(filterPath ...string) *CatFielddataService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *CatFielddataService) Header(name string, value string) *CatFielddataService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *CatFielddataService) Headers(headers http.Header) *CatFielddataService {
s.headers = headers
return s
}
// Fielddata specifies one or more node IDs to for information should be returned.
func (s *CatFielddataService) Field(fields ...string) *CatFielddataService {
s.fields = fields
return s
}
// Bytes represents the unit in which to display byte values.
// Valid values are: "b", "k", "m", or "g".
func (s *CatFielddataService) Bytes(bytes string) *CatFielddataService {
s.bytes = bytes
return s
}
// Columns to return in the response.
// To get a list of all possible columns to return, run the following command
// in your terminal:
//
// Example:
// curl 'http://localhost:9200/_cat/fielddata?help'
//
// You can use Columns("*") to return all possible columns. That might take
// a little longer than the default set of columns.
func (s *CatFielddataService) Columns(columns ...string) *CatFielddataService {
s.columns = columns
return s
}
// Sort is a list of fields to sort by.
func (s *CatFielddataService) Sort(fields ...string) *CatFielddataService {
s.sort = fields
return s
}
func (s *CatFielddataService) buildURL() (string, url.Values, error) {
// Build URL
var (
path string
err error
)
if len(s.fields) > 0 {
path, err = uritemplates.Expand("/_cat/fielddata/{field}", map[string]string{
"field": strings.Join(s.fields, ","),
})
} else {
path = "/_cat/fielddata/"
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{
"format": []string{"json"}, // always returns as JSON
}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.bytes != "" {
params.Set("bytes", s.bytes)
}
if len(s.sort) > 0 {
params.Set("s", strings.Join(s.sort, ","))
}
if len(s.columns) > 0 {
params.Set("h", strings.Join(s.columns, ","))
}
return path, params, nil
}
// Do executes the operation.
func (s *CatFielddataService) Do(ctx context.Context) (CatFielddataResponse, error) {
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
var ret CatFielddataResponse
if err := s.client.decoder.Decode(res.Body, &ret); err != nil {
return nil, err
}
return ret, nil
}
// -- Result of a get request.
// CatFielddataResponse is the outcome of CatFielddataService.Do.
type CatFielddataResponse []CatFielddataResponseRow
// CatFielddataResponseRow is a single row in a CatFielddataResponse.
// Notice that not all of these fields might be filled; that depends on
// the number of columns chose in the request (see CatFielddataService.Columns).
type CatFielddataResponseRow struct {
// Id represents the id of the fielddata.
Id string `json:"id"`
// Host represents the hostname of the fielddata.
Host string `json:"host"`
// IP represents the IP address of the fielddata.
IP string `json:"ip"`
// Node represents the Node name of the fielddata.
Node string `json:"node"`
// Field represents the name of the fielddata.
Field string `json:"field"`
// Size represents the size of the fielddata, e.g. "53.2gb".
Size string `json:"size"`
}
================================================
FILE: cat_fielddata_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestCatFielddata(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t, SetDecoder(&strictDecoder{})) // , SetTraceLog(log.New(os.Stdout, "", 0)))
ctx := context.Background()
// generate fielddata by aggregation
aggRes, err := client.Search(testIndexName).
Aggregation("gene_message_fielddata", NewTermsAggregation().Field("message")).Do(ctx)
if err != nil {
t.Fatal(err)
}
if aggRes == nil {
t.Fatal("want response, have nil")
}
res, err := client.CatFielddata().Pretty(true).Columns("*").Do(ctx)
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("want response, have nil")
}
if len(res) == 0 {
t.Fatalf("want response, have: %v", res)
}
// check fielddata "message" in response
var found bool
for _, fielddata := range res {
if fielddata.Field == "message" {
found = true
break
}
}
if !found {
t.Fatal("fielddata message not found")
}
}
================================================
FILE: cat_health.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
)
// CatHealthService returns a terse representation of the same information
// as /_cluster/health.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/cat-health.html
// for details.
type CatHealthService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
local *bool
masterTimeout string
columns []string
sort []string // list of columns for sort order
disableTimestamping *bool
}
// NewCatHealthService creates a new CatHealthService.
func NewCatHealthService(client *Client) *CatHealthService {
return &CatHealthService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *CatHealthService) Pretty(pretty bool) *CatHealthService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *CatHealthService) Human(human bool) *CatHealthService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *CatHealthService) ErrorTrace(errorTrace bool) *CatHealthService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *CatHealthService) FilterPath(filterPath ...string) *CatHealthService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *CatHealthService) Header(name string, value string) *CatHealthService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *CatHealthService) Headers(headers http.Header) *CatHealthService {
s.headers = headers
return s
}
// Local indicates to return local information, i.e. do not retrieve
// the state from master node (default: false).
func (s *CatHealthService) Local(local bool) *CatHealthService {
s.local = &local
return s
}
// MasterTimeout is the explicit operation timeout for connection to master node.
func (s *CatHealthService) MasterTimeout(masterTimeout string) *CatHealthService {
s.masterTimeout = masterTimeout
return s
}
// Columns to return in the response.
// To get a list of all possible columns to return, run the following command
// in your terminal:
//
// Example:
// curl 'http://localhost:9200/_cat/indices?help'
//
// You can use Columns("*") to return all possible columns. That might take
// a little longer than the default set of columns.
func (s *CatHealthService) Columns(columns ...string) *CatHealthService {
s.columns = columns
return s
}
// Sort is a list of fields to sort by.
func (s *CatHealthService) Sort(fields ...string) *CatHealthService {
s.sort = fields
return s
}
// DisableTimestamping disables timestamping (default: true).
func (s *CatHealthService) DisableTimestamping(disable bool) *CatHealthService {
s.disableTimestamping = &disable
return s
}
// buildURL builds the URL for the operation.
func (s *CatHealthService) buildURL() (string, url.Values, error) {
// Build URL
path := "/_cat/health"
// Add query string parameters
params := url.Values{
"format": []string{"json"}, // always returns as JSON
}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if v := s.local; v != nil {
params.Set("local", fmt.Sprint(*v))
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
if len(s.sort) > 0 {
params.Set("s", strings.Join(s.sort, ","))
}
if v := s.disableTimestamping; v != nil {
params.Set("ts", fmt.Sprint(*v))
}
if len(s.columns) > 0 {
params.Set("h", strings.Join(s.columns, ","))
}
return path, params, nil
}
// Do executes the operation.
func (s *CatHealthService) Do(ctx context.Context) (CatHealthResponse, error) {
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
var ret CatHealthResponse
if err := s.client.decoder.Decode(res.Body, &ret); err != nil {
return nil, err
}
return ret, nil
}
// -- Result of a get request.
// CatHealthResponse is the outcome of CatHealthService.Do.
type CatHealthResponse []CatHealthResponseRow
// CatHealthResponseRow is a single row in a CatHealthResponse.
// Notice that not all of these fields might be filled; that depends
// on the number of columns chose in the request (see CatHealthService.Columns).
type CatHealthResponseRow struct {
Epoch int64 `json:"epoch,string"` // e.g. 1527077996
Timestamp string `json:"timestamp"` // e.g. "12:19:56"
Cluster string `json:"cluster"` // cluster name, e.g. "elasticsearch"
Status string `json:"status"` // health status, e.g. "green", "yellow", or "red"
NodeTotal int `json:"node.total,string"` // total number of nodes
NodeData int `json:"node.data,string"` // number of nodes that can store data
Shards int `json:"shards,string"` // total number of shards
Pri int `json:"pri,string"` // number of primary shards
Relo int `json:"relo,string"` // number of relocating nodes
Init int `json:"init,string"` // number of initializing nodes
Unassign int `json:"unassign,string"` // number of unassigned shards
PendingTasks int `json:"pending_tasks,string"` // number of pending tasks
MaxTaskWaitTime string `json:"max_task_wait_time"` // wait time of longest task pending, e.g. "-" or time in millis
ActiveShardsPercent string `json:"active_shards_percent"` // active number of shards in percent, e.g. "100%"
}
================================================
FILE: cat_health_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestCatHealth(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t, SetDecoder(&strictDecoder{})) // , SetTraceLog(log.New(os.Stdout, "", 0)))
ctx := context.Background()
res, err := client.CatHealth().Columns("*").Do(ctx)
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("want response, have nil")
}
if len(res) == 0 {
t.Fatalf("want response, have: %v", res)
}
if have := res[0].Cluster; have == "" {
t.Fatalf("Cluster[0]: want != %q, have %q", "", have)
}
}
================================================
FILE: cat_indices.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// CatIndicesService returns the list of indices plus some additional
// information about them.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/cat-indices.html
// for details.
type CatIndicesService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
index string
bytes string // b, k, m, or g
local *bool
masterTimeout string
columns []string
health string // green, yellow, or red
primaryOnly *bool // true for primary shards only
sort []string // list of columns for sort order
headers http.Header
}
// NewCatIndicesService creates a new CatIndicesService.
func NewCatIndicesService(client *Client) *CatIndicesService {
return &CatIndicesService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *CatIndicesService) Pretty(pretty bool) *CatIndicesService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *CatIndicesService) Human(human bool) *CatIndicesService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *CatIndicesService) ErrorTrace(errorTrace bool) *CatIndicesService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *CatIndicesService) FilterPath(filterPath ...string) *CatIndicesService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *CatIndicesService) Header(name string, value string) *CatIndicesService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *CatIndicesService) Headers(headers http.Header) *CatIndicesService {
s.headers = headers
return s
}
// Index is the name of the index to list (by default all indices are returned).
func (s *CatIndicesService) Index(index string) *CatIndicesService {
s.index = index
return s
}
// Bytes represents the unit in which to display byte values.
// Valid values are: "b", "k", "m", or "g".
func (s *CatIndicesService) Bytes(bytes string) *CatIndicesService {
s.bytes = bytes
return s
}
// Local indicates to return local information, i.e. do not retrieve
// the state from master node (default: false).
func (s *CatIndicesService) Local(local bool) *CatIndicesService {
s.local = &local
return s
}
// MasterTimeout is the explicit operation timeout for connection to master node.
func (s *CatIndicesService) MasterTimeout(masterTimeout string) *CatIndicesService {
s.masterTimeout = masterTimeout
return s
}
// Columns to return in the response.
// To get a list of all possible columns to return, run the following command
// in your terminal:
//
// Example:
// curl 'http://localhost:9200/_cat/indices?help'
//
// You can use Columns("*") to return all possible columns. That might take
// a little longer than the default set of columns.
func (s *CatIndicesService) Columns(columns ...string) *CatIndicesService {
s.columns = columns
return s
}
// Health filters indices by their health status.
// Valid values are: "green", "yellow", or "red".
func (s *CatIndicesService) Health(healthState string) *CatIndicesService {
s.health = healthState
return s
}
// PrimaryOnly when set to true returns stats only for primary shards (default: false).
func (s *CatIndicesService) PrimaryOnly(primaryOnly bool) *CatIndicesService {
s.primaryOnly = &primaryOnly
return s
}
// Sort is a list of fields to sort by.
func (s *CatIndicesService) Sort(fields ...string) *CatIndicesService {
s.sort = fields
return s
}
// buildURL builds the URL for the operation.
func (s *CatIndicesService) buildURL() (string, url.Values, error) {
// Build URL
var (
path string
err error
)
if s.index != "" {
path, err = uritemplates.Expand("/_cat/indices/{index}", map[string]string{
"index": s.index,
})
} else {
path = "/_cat/indices"
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{
"format": []string{"json"}, // always returns as JSON
}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.bytes != "" {
params.Set("bytes", s.bytes)
}
if v := s.local; v != nil {
params.Set("local", fmt.Sprint(*v))
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
if len(s.columns) > 0 {
// loop through all columns and apply alias if needed
for i, column := range s.columns {
if fullValueRaw, isAliased := catIndicesResponseRowAliasesMap[column]; isAliased {
// alias can be translated to multiple fields,
// so if translated value contains a comma, than replace the first value
// and append the others
if strings.Contains(fullValueRaw, ",") {
fullValues := strings.Split(fullValueRaw, ",")
s.columns[i] = fullValues[0]
s.columns = append(s.columns, fullValues[1:]...)
} else {
s.columns[i] = fullValueRaw
}
}
}
params.Set("h", strings.Join(s.columns, ","))
}
if s.health != "" {
params.Set("health", s.health)
}
if v := s.primaryOnly; v != nil {
params.Set("pri", fmt.Sprint(*v))
}
if len(s.sort) > 0 {
params.Set("s", strings.Join(s.sort, ","))
}
return path, params, nil
}
// Do executes the operation.
func (s *CatIndicesService) Do(ctx context.Context) (CatIndicesResponse, error) {
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
var ret CatIndicesResponse
if err := s.client.decoder.Decode(res.Body, &ret); err != nil {
return nil, err
}
return ret, nil
}
// -- Result of a get request.
// CatIndicesResponse is the outcome of CatIndicesService.Do.
type CatIndicesResponse []CatIndicesResponseRow
// CatIndicesResponseRow specifies the data returned for one index
// of a CatIndicesResponse. Notice that not all of these fields might
// be filled; that depends on the number of columns chose in the
// request (see CatIndicesService.Columns).
type CatIndicesResponseRow struct {
Health string `json:"health"` // "green", "yellow", or "red"
Status string `json:"status"` // "open" or "closed"
Index string `json:"index"` // index name
UUID string `json:"uuid"` // index uuid
Pri int `json:"pri,string"` // number of primary shards
Rep int `json:"rep,string"` // number of replica shards
DocsCount int `json:"docs.count,string"` // number of available documents
DocsDeleted int `json:"docs.deleted,string"` // number of deleted documents
CreationDate int64 `json:"creation.date,string"` // index creation date (millisecond value), e.g. 1527077221644
CreationDateString string `json:"creation.date.string"` // index creation date (as string), e.g. "2018-05-23T12:07:01.644Z"
StoreSize string `json:"store.size"` // store size of primaries & replicas, e.g. "4.6kb"
PriStoreSize string `json:"pri.store.size"` // store size of primaries, e.g. "230b"
CompletionSize string `json:"completion.size"` // size of completion on primaries & replicas
PriCompletionSize string `json:"pri.completion.size"` // size of completion on primaries
FielddataMemorySize string `json:"fielddata.memory_size"` // used fielddata cache on primaries & replicas
PriFielddataMemorySize string `json:"pri.fielddata.memory_size"` // used fielddata cache on primaries
FielddataEvictions int `json:"fielddata.evictions,string"` // fielddata evictions on primaries & replicas
PriFielddataEvictions int `json:"pri.fielddata.evictions,string"` // fielddata evictions on primaries
QueryCacheMemorySize string `json:"query_cache.memory_size"` // used query cache on primaries & replicas
PriQueryCacheMemorySize string `json:"pri.query_cache.memory_size"` // used query cache on primaries
QueryCacheEvictions int `json:"query_cache.evictions,string"` // query cache evictions on primaries & replicas
PriQueryCacheEvictions int `json:"pri.query_cache.evictions,string"` // query cache evictions on primaries
RequestCacheMemorySize string `json:"request_cache.memory_size"` // used request cache on primaries & replicas
PriRequestCacheMemorySize string `json:"pri.request_cache.memory_size"` // used request cache on primaries
RequestCacheEvictions int `json:"request_cache.evictions,string"` // request cache evictions on primaries & replicas
PriRequestCacheEvictions int `json:"pri.request_cache.evictions,string"` // request cache evictions on primaries
RequestCacheHitCount int `json:"request_cache.hit_count,string"` // request cache hit count on primaries & replicas
PriRequestCacheHitCount int `json:"pri.request_cache.hit_count,string"` // request cache hit count on primaries
RequestCacheMissCount int `json:"request_cache.miss_count,string"` // request cache miss count on primaries & replicas
PriRequestCacheMissCount int `json:"pri.request_cache.miss_count,string"` // request cache miss count on primaries
FlushTotal int `json:"flush.total,string"` // number of flushes on primaries & replicas
PriFlushTotal int `json:"pri.flush.total,string"` // number of flushes on primaries
FlushTotalTime string `json:"flush.total_time"` // time spent in flush on primaries & replicas
PriFlushTotalTime string `json:"pri.flush.total_time"` // time spent in flush on primaries
GetCurrent int `json:"get.current,string"` // number of current get ops on primaries & replicas
PriGetCurrent int `json:"pri.get.current,string"` // number of current get ops on primaries
GetTime string `json:"get.time"` // time spent in get on primaries & replicas
PriGetTime string `json:"pri.get.time"` // time spent in get on primaries
GetTotal int `json:"get.total,string"` // number of get ops on primaries & replicas
PriGetTotal int `json:"pri.get.total,string"` // number of get ops on primaries
GetExistsTime string `json:"get.exists_time"` // time spent in successful gets on primaries & replicas
PriGetExistsTime string `json:"pri.get.exists_time"` // time spent in successful gets on primaries
GetExistsTotal int `json:"get.exists_total,string"` // number of successful gets on primaries & replicas
PriGetExistsTotal int `json:"pri.get.exists_total,string"` // number of successful gets on primaries
GetMissingTime string `json:"get.missing_time"` // time spent in failed gets on primaries & replicas
PriGetMissingTime string `json:"pri.get.missing_time"` // time spent in failed gets on primaries
GetMissingTotal int `json:"get.missing_total,string"` // number of failed gets on primaries & replicas
PriGetMissingTotal int `json:"pri.get.missing_total,string"` // number of failed gets on primaries
IndexingDeleteCurrent int `json:"indexing.delete_current,string"` // number of current deletions on primaries & replicas
PriIndexingDeleteCurrent int `json:"pri.indexing.delete_current,string"` // number of current deletions on primaries
IndexingDeleteTime string `json:"indexing.delete_time"` // time spent in deletions on primaries & replicas
PriIndexingDeleteTime string `json:"pri.indexing.delete_time"` // time spent in deletions on primaries
IndexingDeleteTotal int `json:"indexing.delete_total,string"` // number of delete ops on primaries & replicas
PriIndexingDeleteTotal int `json:"pri.indexing.delete_total,string"` // number of delete ops on primaries
IndexingIndexCurrent int `json:"indexing.index_current,string"` // number of current indexing on primaries & replicas
PriIndexingIndexCurrent int `json:"pri.indexing.index_current,string"` // number of current indexing on primaries
IndexingIndexTime string `json:"indexing.index_time"` // time spent in indexing on primaries & replicas
PriIndexingIndexTime string `json:"pri.indexing.index_time"` // time spent in indexing on primaries
IndexingIndexTotal int `json:"indexing.index_total,string"` // number of index ops on primaries & replicas
PriIndexingIndexTotal int `json:"pri.indexing.index_total,string"` // number of index ops on primaries
IndexingIndexFailed int `json:"indexing.index_failed,string"` // number of failed indexing ops on primaries & replicas
PriIndexingIndexFailed int `json:"pri.indexing.index_failed,string"` // number of failed indexing ops on primaries
MergesCurrent int `json:"merges.current,string"` // number of current merges on primaries & replicas
PriMergesCurrent int `json:"pri.merges.current,string"` // number of current merges on primaries
MergesCurrentDocs int `json:"merges.current_docs,string"` // number of current merging docs on primaries & replicas
PriMergesCurrentDocs int `json:"pri.merges.current_docs,string"` // number of current merging docs on primaries
MergesCurrentSize string `json:"merges.current_size"` // size of current merges on primaries & replicas
PriMergesCurrentSize string `json:"pri.merges.current_size"` // size of current merges on primaries
MergesTotal int `json:"merges.total,string"` // number of completed merge ops on primaries & replicas
PriMergesTotal int `json:"pri.merges.total,string"` // number of completed merge ops on primaries
MergesTotalDocs int `json:"merges.total_docs,string"` // docs merged on primaries & replicas
PriMergesTotalDocs int `json:"pri.merges.total_docs,string"` // docs merged on primaries
MergesTotalSize string `json:"merges.total_size"` // size merged on primaries & replicas
PriMergesTotalSize string `json:"pri.merges.total_size"` // size merged on primaries
MergesTotalTime string `json:"merges.total_time"` // time spent in merges on primaries & replicas
PriMergesTotalTime string `json:"pri.merges.total_time"` // time spent in merges on primaries
RefreshTotal int `json:"refresh.total,string"` // total refreshes on primaries & replicas
PriRefreshTotal int `json:"pri.refresh.total,string"` // total refreshes on primaries
RefreshExternalTotal int `json:"refresh.external_total,string"` // total external refreshes on primaries & replicas
PriRefreshExternalTotal int `json:"pri.refresh.external_total,string"` // total external refreshes on primaries
RefreshTime string `json:"refresh.time"` // time spent in refreshes on primaries & replicas
PriRefreshTime string `json:"pri.refresh.time"` // time spent in refreshes on primaries
RefreshExternalTime string `json:"refresh.external_time"` // external time spent in refreshes on primaries & replicas
PriRefreshExternalTime string `json:"pri.refresh.external_time"` // external time spent in refreshes on primaries
RefreshListeners int `json:"refresh.listeners,string"` // number of pending refresh listeners on primaries & replicas
PriRefreshListeners int `json:"pri.refresh.listeners,string"` // number of pending refresh listeners on primaries
SearchFetchCurrent int `json:"search.fetch_current,string"` // current fetch phase ops on primaries & replicas
PriSearchFetchCurrent int `json:"pri.search.fetch_current,string"` // current fetch phase ops on primaries
SearchFetchTime string `json:"search.fetch_time"` // time spent in fetch phase on primaries & replicas
PriSearchFetchTime string `json:"pri.search.fetch_time"` // time spent in fetch phase on primaries
SearchFetchTotal int `json:"search.fetch_total,string"` // total fetch ops on primaries & replicas
PriSearchFetchTotal int `json:"pri.search.fetch_total,string"` // total fetch ops on primaries
SearchOpenContexts int `json:"search.open_contexts,string"` // open search contexts on primaries & replicas
PriSearchOpenContexts int `json:"pri.search.open_contexts,string"` // open search contexts on primaries
SearchQueryCurrent int `json:"search.query_current,string"` // current query phase ops on primaries & replicas
PriSearchQueryCurrent int `json:"pri.search.query_current,string"` // current query phase ops on primaries
SearchQueryTime string `json:"search.query_time"` // time spent in query phase on primaries & replicas, e.g. "0s"
PriSearchQueryTime string `json:"pri.search.query_time"` // time spent in query phase on primaries, e.g. "0s"
SearchQueryTotal int `json:"search.query_total,string"` // total query phase ops on primaries & replicas
PriSearchQueryTotal int `json:"pri.search.query_total,string"` // total query phase ops on primaries
SearchScrollCurrent int `json:"search.scroll_current,string"` // open scroll contexts on primaries & replicas
PriSearchScrollCurrent int `json:"pri.search.scroll_current,string"` // open scroll contexts on primaries
SearchScrollTime string `json:"search.scroll_time"` // time scroll contexts held open on primaries & replicas, e.g. "0s"
PriSearchScrollTime string `json:"pri.search.scroll_time"` // time scroll contexts held open on primaries, e.g. "0s"
SearchScrollTotal int `json:"search.scroll_total,string"` // completed scroll contexts on primaries & replicas
PriSearchScrollTotal int `json:"pri.search.scroll_total,string"` // completed scroll contexts on primaries
SearchThrottled bool `json:"search.throttled,string"` // indicates if the index is search throttled
SegmentsCount int `json:"segments.count,string"` // number of segments on primaries & replicas
PriSegmentsCount int `json:"pri.segments.count,string"` // number of segments on primaries
SegmentsMemory string `json:"segments.memory"` // memory used by segments on primaries & replicas, e.g. "1.3kb"
PriSegmentsMemory string `json:"pri.segments.memory"` // memory used by segments on primaries, e.g. "1.3kb"
SegmentsIndexWriterMemory string `json:"segments.index_writer_memory"` // memory used by index writer on primaries & replicas, e.g. "0b"
PriSegmentsIndexWriterMemory string `json:"pri.segments.index_writer_memory"` // memory used by index writer on primaries, e.g. "0b"
SegmentsVersionMapMemory string `json:"segments.version_map_memory"` // memory used by version map on primaries & replicas, e.g. "0b"
PriSegmentsVersionMapMemory string `json:"pri.segments.version_map_memory"` // memory used by version map on primaries, e.g. "0b"
SegmentsFixedBitsetMemory string `json:"segments.fixed_bitset_memory"` // memory used by fixed bit sets for nested object field types and type filters for types referred in _parent fields on primaries & replicas, e.g. "0b"
PriSegmentsFixedBitsetMemory string `json:"pri.segments.fixed_bitset_memory"` // memory used by fixed bit sets for nested object field types and type filters for types referred in _parent fields on primaries, e.g. "0b"
WarmerCurrent int `json:"warmer.current,string"` // current warmer ops on primaries & replicas
PriWarmerCurrent int `json:"pri.warmer.current,string"` // current warmer ops on primaries
WarmerTotal int `json:"warmer.total,string"` // total warmer ops on primaries & replicas
PriWarmerTotal int `json:"pri.warmer.total,string"` // total warmer ops on primaries
WarmerTotalTime string `json:"warmer.total_time"` // time spent in warmers on primaries & replicas, e.g. "47s"
PriWarmerTotalTime string `json:"pri.warmer.total_time"` // time spent in warmers on primaries, e.g. "47s"
SuggestCurrent int `json:"suggest.current,string"` // number of current suggest ops on primaries & replicas
PriSuggestCurrent int `json:"pri.suggest.current,string"` // number of current suggest ops on primaries
SuggestTime string `json:"suggest.time"` // time spend in suggest on primaries & replicas, "31s"
PriSuggestTime string `json:"pri.suggest.time"` // time spend in suggest on primaries, e.g. "31s"
SuggestTotal int `json:"suggest.total,string"` // number of suggest ops on primaries & replicas
PriSuggestTotal int `json:"pri.suggest.total,string"` // number of suggest ops on primaries
MemoryTotal string `json:"memory.total"` // total user memory on primaries & replicas, e.g. "1.5kb"
PriMemoryTotal string `json:"pri.memory.total"` // total user memory on primaries, e.g. "1.5kb"
}
// catIndicesResponseRowAliasesMap holds the global map for columns aliases
// the map is used by CatIndicesService.buildURL
// for backwards compatibility some fields are able to have the same aliases
// that means that one alias can be translated to different columns (from different elastic versions)
// example for understanding: rto -> RefreshTotal, RefreshExternalTotal
var catIndicesResponseRowAliasesMap = map[string]string{
"qce": "query_cache.evictions",
"searchFetchTime": "search.fetch_time",
"memoryTotal": "memory.total",
"requestCacheEvictions": "request_cache.evictions",
"ftt": "flush.total_time",
"iic": "indexing.index_current",
"mtt": "merges.total_time",
"scti": "search.scroll_time",
"searchScrollTime": "search.scroll_time",
"segmentsCount": "segments.count",
"getTotal": "get.total",
"sfti": "search.fetch_time",
"searchScrollCurrent": "search.scroll_current",
"svmm": "segments.version_map_memory",
"warmerTotalTime": "warmer.total_time",
"r": "rep",
"indexingIndexTime": "indexing.index_time",
"refreshTotal": "refresh.total,refresh.external_total",
"scc": "search.scroll_current",
"suggestTime": "suggest.time",
"idc": "indexing.delete_current",
"rti": "refresh.time,refresh.external_time",
"sfto": "search.fetch_total",
"completionSize": "completion.size",
"mt": "merges.total",
"segmentsVersionMapMemory": "segments.version_map_memory",
"rto": "refresh.total,refresh.external_total",
"id": "uuid",
"dd": "docs.deleted",
"docsDeleted": "docs.deleted",
"fielddataMemory": "fielddata.memory_size",
"getTime": "get.time",
"getExistsTime": "get.exists_time",
"mtd": "merges.total_docs",
"rli": "refresh.listeners",
"h": "health",
"cds": "creation.date.string",
"rcmc": "request_cache.miss_count",
"iif": "indexing.index_failed",
"warmerCurrent": "warmer.current",
"gti": "get.time",
"indexingIndexFailed": "indexing.index_failed",
"mts": "merges.total_size",
"sqti": "search.query_time",
"segmentsIndexWriterMemory": "segments.index_writer_memory",
"iiti": "indexing.index_time",
"iito": "indexing.index_total",
"cd": "creation.date",
"gc": "get.current",
"searchFetchTotal": "search.fetch_total",
"sqc": "search.query_current",
"segmentsMemory": "segments.memory",
"dc": "docs.count",
"qcm": "query_cache.memory_size",
"queryCacheMemory": "query_cache.memory_size",
"mergesTotalDocs": "merges.total_docs",
"searchOpenContexts": "search.open_contexts",
"shards.primary": "pri",
"cs": "completion.size",
"mergesTotalTIme": "merges.total_time",
"wtt": "warmer.total_time",
"mergesCurrentSize": "merges.current_size",
"mergesTotal": "merges.total",
"refreshTime": "refresh.time,refresh.external_time",
"wc": "warmer.current",
"p": "pri",
"idti": "indexing.delete_time",
"searchQueryCurrent": "search.query_current",
"warmerTotal": "warmer.total",
"suggestTotal": "suggest.total",
"tm": "memory.total",
"ss": "store.size",
"ft": "flush.total",
"getExistsTotal": "get.exists_total",
"scto": "search.scroll_total",
"s": "status",
"queryCacheEvictions": "query_cache.evictions",
"rce": "request_cache.evictions",
"geto": "get.exists_total",
"refreshListeners": "refresh.listeners",
"suto": "suggest.total",
"storeSize": "store.size",
"gmti": "get.missing_time",
"indexingIdexCurrent": "indexing.index_current",
"searchFetchCurrent": "search.fetch_current",
"idx": "index",
"fm": "fielddata.memory_size",
"geti": "get.exists_time",
"indexingDeleteCurrent": "indexing.delete_current",
"mergesCurrentDocs": "merges.current_docs",
"sth": "search.throttled",
"flushTotal": "flush.total",
"sfc": "search.fetch_current",
"wto": "warmer.total",
"suti": "suggest.time",
"shardsReplica": "rep",
"mergesCurrent": "merges.current",
"mcs": "merges.current_size",
"so": "search.open_contexts",
"i": "index",
"siwm": "segments.index_writer_memory",
"sfbm": "segments.fixed_bitset_memory",
"fe": "fielddata.evictions",
"requestCacheMissCount": "request_cache.miss_count",
"idto": "indexing.delete_total",
"mergesTotalSize": "merges.total_size",
"suc": "suggest.current",
"suggestCurrent": "suggest.current",
"flushTotalTime": "flush.total_time",
"getMissingTotal": "get.missing_total",
"sqto": "search.query_total",
"searchScrollTotal": "search.scroll_total",
"fixedBitsetMemory": "segments.fixed_bitset_memory",
"getMissingTime": "get.missing_time",
"indexingDeleteTotal": "indexing.delete_total",
"mcd": "merges.current_docs",
"docsCount": "docs.count",
"gto": "get.total",
"mc": "merges.current",
"fielddataEvictions": "fielddata.evictions",
"rcm": "request_cache.memory_size",
"requestCacheHitCount": "request_cache.hit_count",
"gmto": "get.missing_total",
"searchQueryTime": "search.query_time",
"shards.replica": "rep",
"requestCacheMemory": "request_cache.memory_size",
"rchc": "request_cache.hit_count",
"getCurrent": "get.current",
"indexingIndexTotal": "indexing.index_total",
"sc": "segments.count,segments.memory",
"shardsPrimary": "pri",
"indexingDeleteTime": "indexing.delete_time",
"searchQueryTotal": "search.query_total",
}
================================================
FILE: cat_indices_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestCatIndices(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t, SetDecoder(&strictDecoder{})) // , SetTraceLog(log.New(os.Stdout, "", 0)))
ctx := context.Background()
res, err := client.CatIndices().Columns("*").Do(ctx)
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("want response, have nil")
}
if len(res) == 0 {
t.Fatalf("want response, have: %v", res)
}
if have := res[0].Index; have == "" {
t.Fatalf("Index[0]: want != %q, have %q", "", have)
}
}
// TestCatIndicesResponseRowAliasesMap tests if catIndicesResponseRowAliasesMap is declared
func TestCatIndicesResponseRowAliasesMap(t *testing.T) {
if catIndicesResponseRowAliasesMap == nil {
t.Fatal("want catIndicesResponseRowAliasesMap to be not nil")
}
if len(catIndicesResponseRowAliasesMap) == 0 {
t.Fatal("want catIndicesResponseRowAliasesMap to be not empty")
}
}
// TestCatIndicesWithAliases makes a simple test (if ?h=h will be the same as ?h=health)
func TestCatIndicesWithAliases(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t, SetDecoder(&strictDecoder{})) // , SetTraceLog(log.New(os.Stdout, "", 0)))
ctx := context.Background()
res, err := client.CatIndices().Columns("h").Do(ctx)
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("want response, have nil")
}
if len(res) == 0 {
t.Fatalf("want response, have: %v", res)
}
if have := res[0].Health; have == "" {
t.Fatalf("Index[0]: want != %q, have %q", "", have)
}
}
// TestCatIndicesWithAliases makes a test with a double-alias
// asking `?h=rti` will fill one of the refresh.external_time/refresh.time fields (depending on elasticsearch version)
func TestCatIndicesWithAliases_Double(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t, SetDecoder(&strictDecoder{})) // , SetTraceLog(log.New(os.Stdout, "", 0)))
ctx := context.Background()
res, err := client.CatIndices().Columns("rti").Do(ctx)
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("want response, have nil")
}
if len(res) == 0 {
t.Fatalf("want response, have: %v", res)
}
refreshTime := res[0].RefreshTime
refreshExternalTime := res[0].RefreshExternalTime
if refreshTime == "" && refreshExternalTime == "" {
t.Fatalf("Index[0]: want one of [refreshTime or refreshExternalTime] be not empty")
}
}
================================================
FILE: cat_master.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
)
// CatMasterService shows information about the master node,
// including the ID, bound IP address, and name.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.12/cat-master.html
// for details.
type CatMasterService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
local *bool
masterTimeout string
columns []string
sort []string // list of columns for sort order
}
// NewCatMasterService creates a new CatMasterService
func NewCatMasterService(client *Client) *CatMasterService {
return &CatMasterService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *CatMasterService) Pretty(pretty bool) *CatMasterService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *CatMasterService) Human(human bool) *CatMasterService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *CatMasterService) ErrorTrace(errorTrace bool) *CatMasterService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *CatMasterService) FilterPath(filterPath ...string) *CatMasterService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *CatMasterService) Header(name string, value string) *CatMasterService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *CatMasterService) Headers(headers http.Header) *CatMasterService {
s.headers = headers
return s
}
// Local indicates to return local information, i.e. do not retrieve
// the state from master node (default: false).
func (s *CatMasterService) Local(local bool) *CatMasterService {
s.local = &local
return s
}
// MasterTimeout is the explicit operation timeout for connection to master node.
func (s *CatMasterService) MasterTimeout(masterTimeout string) *CatMasterService {
s.masterTimeout = masterTimeout
return s
}
// Columns to return in the response.
// To get a list of all possible columns to return, run the following command
// in your terminal:
//
// Example:
// curl 'http://localhost:9200/_cat/master?help'
//
// You can use Columns("*") to return all possible columns. That might take
// a little longer than the default set of columns.
func (s *CatMasterService) Columns(columns ...string) *CatMasterService {
s.columns = columns
return s
}
// Sort is a list of fields to sort by.
func (s *CatMasterService) Sort(fields ...string) *CatMasterService {
s.sort = fields
return s
}
// buildURL builds the URL for the operation.
func (s *CatMasterService) buildURL() (string, url.Values, error) {
// Build URL
path := "/_cat/master"
// Add query string parameters
params := url.Values{
"format": []string{"json"}, // always returns as JSON
}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if v := s.local; v != nil {
params.Set("local", fmt.Sprint(*v))
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
if len(s.sort) > 0 {
params.Set("s", strings.Join(s.sort, ","))
}
if len(s.columns) > 0 {
params.Set("h", strings.Join(s.columns, ","))
}
return path, params, nil
}
// Do executes the operation.
func (s *CatMasterService) Do(ctx context.Context) (CatMasterResponse, error) {
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
var ret CatMasterResponse
if err := s.client.decoder.Decode(res.Body, &ret); err != nil {
return nil, err
}
return ret, nil
}
// -- Result of a get request.
// CatMasterResponse is the outcome of CatMasterService.Do.
type CatMasterResponse []CatMasterResponseRow
// CatMasterResponseRow is a single row in a CatMasterResponse.
// Notice that not all of these fields might be filled; that depends
// on the number of columns chose in the request (see CatMasterService.Columns).
type CatMasterResponseRow struct {
ID string `json:"id"`
Host string `json:"host"`
IP string `json:"ip"`
Node string `json:"node"`
}
================================================
FILE: cat_master_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestCatMaster(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t, SetDecoder(&strictDecoder{})) //, SetTraceLog(log.New(os.Stdout, "", 0)))
ctx := context.Background()
res, err := client.CatMaster().Columns("*").Do(ctx)
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("want response, have nil")
}
if len(res) == 0 {
t.Fatalf("want response, have: %v", res)
}
if have := res[0].IP; have == "" {
t.Fatalf("IP[0]: want != %q, have %q", "", have)
}
}
================================================
FILE: cat_shards.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// CatShardsService returns the list of shards plus some additional
// information about them.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.6/cat-shards.html
// for details.
type CatShardsService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
index []string
bytes string // b, k, kb, m, mb, g, gb, t, tb, p, or pb
local *bool
masterTimeout string
columns []string
time string // d, h, m, s, ms, micros, or nanos
sort []string // list of columns for sort order
headers http.Header
}
// NewCatShardsService creates a new CatShardsService.
func NewCatShardsService(client *Client) *CatShardsService {
return &CatShardsService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *CatShardsService) Pretty(pretty bool) *CatShardsService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *CatShardsService) Human(human bool) *CatShardsService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *CatShardsService) ErrorTrace(errorTrace bool) *CatShardsService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *CatShardsService) FilterPath(filterPath ...string) *CatShardsService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *CatShardsService) Header(name string, value string) *CatShardsService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *CatShardsService) Headers(headers http.Header) *CatShardsService {
s.headers = headers
return s
}
// Index is the name of the index to list (by default all indices are returned).
func (s *CatShardsService) Index(index ...string) *CatShardsService {
s.index = index
return s
}
// Bytes represents the unit in which to display byte values.
// Valid values are: "b", "k", "kb", "m", "mb", "g", "gb", "t", "tb", "p" or "pb".
func (s *CatShardsService) Bytes(bytes string) *CatShardsService {
s.bytes = bytes
return s
}
// Local indicates to return local information, i.e. do not retrieve
// the state from master node (default: false).
func (s *CatShardsService) Local(local bool) *CatShardsService {
s.local = &local
return s
}
// MasterTimeout is the explicit operation timeout for connection to master node.
func (s *CatShardsService) MasterTimeout(masterTimeout string) *CatShardsService {
s.masterTimeout = masterTimeout
return s
}
// Columns to return in the response.
//
// To get a list of all possible columns to return, run the following command
// in your terminal:
//
// Example:
// curl 'http://localhost:9200/_cat/shards?help'
//
// You can use Columns("*") to return all possible columns. That might take
// a little longer than the default set of columns.
func (s *CatShardsService) Columns(columns ...string) *CatShardsService {
s.columns = columns
return s
}
// Sort is a list of fields to sort by.
func (s *CatShardsService) Sort(fields ...string) *CatShardsService {
s.sort = fields
return s
}
// Time specifies the way that time values are formatted with.
func (s *CatShardsService) Time(time string) *CatShardsService {
s.time = time
return s
}
// buildURL builds the URL for the operation.
func (s *CatShardsService) buildURL() (string, url.Values, error) {
// Build URL
var (
path string
err error
)
if len(s.index) > 0 {
path, err = uritemplates.Expand("/_cat/shards/{index}", map[string]string{
"index": strings.Join(s.index, ","),
})
} else {
path = "/_cat/shards"
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{
"format": []string{"json"}, // always returns as JSON
}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.bytes != "" {
params.Set("bytes", s.bytes)
}
if s.time != "" {
params.Set("time", s.time)
}
if v := s.local; v != nil {
params.Set("local", fmt.Sprint(*v))
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
if len(s.columns) > 0 {
// loop through all columns and apply alias if needed
for i, column := range s.columns {
if fullValueRaw, isAliased := catShardsResponseRowAliasesMap[column]; isAliased {
// alias can be translated to multiple fields,
// so if translated value contains a comma, than replace the first value
// and append the others
if strings.Contains(fullValueRaw, ",") {
fullValues := strings.Split(fullValueRaw, ",")
s.columns[i] = fullValues[0]
s.columns = append(s.columns, fullValues[1:]...)
} else {
s.columns[i] = fullValueRaw
}
}
}
params.Set("h", strings.Join(s.columns, ","))
}
if len(s.sort) > 0 {
params.Set("s", strings.Join(s.sort, ","))
}
return path, params, nil
}
// Do executes the operation.
func (s *CatShardsService) Do(ctx context.Context) (CatShardsResponse, error) {
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
var ret CatShardsResponse
if err := s.client.decoder.Decode(res.Body, &ret); err != nil {
return nil, err
}
return ret, nil
}
// -- Result of a get request.
// CatShardsResponse is the outcome of CatShardsService.Do.
type CatShardsResponse []CatShardsResponseRow
// CatShardsResponseRow specifies the data returned for one index
// of a CatShardsResponse. Notice that not all of these fields might
// be filled; that depends on the number of columns chose in the
// request (see CatShardsService.Columns).
type CatShardsResponseRow struct {
Index string `json:"index"` // index name
UUID string `json:"uuid"` // index uuid
Shard int `json:"shard,string"` // shard number, e.g. 1
Prirep string `json:"prirep"` // "r" for replica, "p" for primary
State string `json:"state"` // STARTED, INITIALIZING, RELOCATING, or UNASSIGNED
Docs int64 `json:"docs,string"` // number of documents, e.g. 142847
Store string `json:"store"` // size, e.g. "40mb"
IP string `json:"ip"` // IP address
ID string `json:"id"`
Node string `json:"node"` // Node name
SyncID string `json:"sync_id"`
UnassignedReason string `json:"unassigned.reason"`
UnassignedAt string `json:"unassigned.at"`
UnassignedFor string `json:"unassigned.for"`
UnassignedDetails string `json:"unassigned.details"`
RecoverysourceType string `json:"recoverysource.type"`
CompletionSize string `json:"completion.size"` // size of completion on primaries & replicas
FielddataMemorySize string `json:"fielddata.memory_size"` // used fielddata cache on primaries & replicas
FielddataEvictions int `json:"fielddata.evictions,string"` // fielddata evictions on primaries & replicas
QueryCacheMemorySize string `json:"query_cache.memory_size"` // used query cache on primaries & replicas
QueryCacheEvictions int `json:"query_cache.evictions,string"` // query cache evictions on primaries & replicas
FlushTotal int `json:"flush.total,string"` // number of flushes on primaries & replicas
FlushTotalTime string `json:"flush.total_time"` // time spent in flush on primaries & replicas
GetCurrent int `json:"get.current,string"` // number of current get ops on primaries & replicas
GetTime string `json:"get.time"` // time spent in get on primaries & replicas
GetTotal int `json:"get.total,string"` // number of get ops on primaries & replicas
GetExistsTime string `json:"get.exists_time"` // time spent in successful gets on primaries & replicas
GetExistsTotal int `json:"get.exists_total,string"` // number of successful gets on primaries & replicas
GetMissingTime string `json:"get.missing_time"` // time spent in failed gets on primaries & replicas
GetMissingTotal int `json:"get.missing_total,string"` // number of failed gets on primaries & replicas
IndexingDeleteCurrent int `json:"indexing.delete_current,string"` // number of current deletions on primaries & replicas
IndexingDeleteTime string `json:"indexing.delete_time"` // time spent in deletions on primaries & replicas
IndexingDeleteTotal int `json:"indexing.delete_total,string"` // number of delete ops on primaries & replicas
IndexingIndexCurrent int `json:"indexing.index_current,string"` // number of current indexing on primaries & replicas
IndexingIndexTime string `json:"indexing.index_time"` // time spent in indexing on primaries & replicas
IndexingIndexTotal int `json:"indexing.index_total,string"` // number of index ops on primaries & replicas
IndexingIndexFailed int `json:"indexing.index_failed,string"` // number of failed indexing ops on primaries & replicas
MergesCurrent int `json:"merges.current,string"` // number of current merges on primaries & replicas
MergesCurrentDocs int `json:"merges.current_docs,string"` // number of current merging docs on primaries & replicas
MergesCurrentSize string `json:"merges.current_size"` // size of current merges on primaries & replicas
MergesTotal int `json:"merges.total,string"` // number of completed merge ops on primaries & replicas
MergesTotalDocs int `json:"merges.total_docs,string"` // docs merged on primaries & replicas
MergesTotalSize string `json:"merges.total_size"` // size merged on primaries & replicas
MergesTotalTime string `json:"merges.total_time"` // time spent in merges on primaries & replicas
RefreshTotal int `json:"refresh.total,string"` // total refreshes on primaries & replicas
RefreshExternalTotal int `json:"refresh.external_total,string"` // total external refreshes on primaries & replicas
RefreshTime string `json:"refresh.time"` // time spent in refreshes on primaries & replicas
RefreshExternalTime string `json:"refresh.external_time"` // external time spent in refreshes on primaries & replicas
RefreshListeners int `json:"refresh.listeners,string"` // number of pending refresh listeners on primaries & replicas
SearchFetchCurrent int `json:"search.fetch_current,string"` // current fetch phase ops on primaries & replicas
SearchFetchTime string `json:"search.fetch_time"` // time spent in fetch phase on primaries & replicas
SearchFetchTotal int `json:"search.fetch_total,string"` // total fetch ops on primaries & replicas
SearchOpenContexts int `json:"search.open_contexts,string"` // open search contexts on primaries & replicas
SearchQueryCurrent int `json:"search.query_current,string"` // current query phase ops on primaries & replicas
SearchQueryTime string `json:"search.query_time"` // time spent in query phase on primaries & replicas, e.g. "0s"
SearchQueryTotal int `json:"search.query_total,string"` // total query phase ops on primaries & replicas
SearchScrollCurrent int `json:"search.scroll_current,string"` // open scroll contexts on primaries & replicas
SearchScrollTime string `json:"search.scroll_time"` // time scroll contexts held open on primaries & replicas, e.g. "0s"
SearchScrollTotal int `json:"search.scroll_total,string"` // completed scroll contexts on primaries & replicas
SearchThrottled bool `json:"search.throttled,string"` // indicates if the index is search throttled
SegmentsCount int `json:"segments.count,string"` // number of segments on primaries & replicas
SegmentsMemory string `json:"segments.memory"` // memory used by segments on primaries & replicas, e.g. "1.3kb"
SegmentsIndexWriterMemory string `json:"segments.index_writer_memory"` // memory used by index writer on primaries & replicas, e.g. "0b"
SegmentsVersionMapMemory string `json:"segments.version_map_memory"` // memory used by version map on primaries & replicas, e.g. "0b"
SegmentsFixedBitsetMemory string `json:"segments.fixed_bitset_memory"` // memory used by fixed bit sets for nested object field types and type filters for types referred in _parent fields on primaries & replicas, e.g. "0b"
SeqNoMax int `json:"seq_no.max,string"`
SeqNoLocalCheckpoint int `json:"seq_no.local_checkpoint,string"`
SeqNoGlobalCheckpoint int `json:"seq_no.global_checkpoint,string"`
WarmerCurrent int `json:"warmer.current,string"` // current warmer ops on primaries & replicas
WarmerTotal int `json:"warmer.total,string"` // total warmer ops on primaries & replicas
WarmerTotalTime string `json:"warmer.total_time"` // time spent in warmers on primaries & replicas, e.g. "47s"
PathData string `json:"path.data"`
PathState string `json:"path.state"`
}
// catShardsResponseRowAliasesMap holds the global map for columns aliases
// the map is used by CatShardsService.buildURL.
// For backwards compatibility some fields are able to have the same aliases
// that means that one alias can be translated to different columns (from different elastic versions)
// example for understanding: rto -> RefreshTotal, RefreshExternalTotal
var catShardsResponseRowAliasesMap = map[string]string{
"sync_id": "sync_id",
"ur": "unassigned.reason",
"ua": "unassigned.at",
"uf": "unassigned.for",
"ud": "unassigned.details",
"rs": "recoverysource.type",
"cs": "completion.size",
"fm": "fielddata.memory_size",
"fe": "fielddata.evictions",
"qcm": "query_cache.memory_size",
"qce": "query_cache.evictions",
"ft": "flush.total",
"ftt": "flush.total_time",
"gc": "get.current",
"gti": "get.time",
"gto": "get.total",
"geti": "get.exists_time",
"geto": "get.exists_total",
"gmti": "get.missing_time",
"gmto": "get.missing_total",
"idc": "indexing.delete_current",
"idti": "indexing.delete_time",
"idto": "indexing.delete_total",
"iic": "indexing.index_current",
"iiti": "indexing.index_time",
"iito": "indexing.index_total",
"iif": "indexing.index_failed",
"mc": "merges.current",
"mcd": "merges.current_docs",
"mcs": "merges.current_size",
"mt": "merges.total",
"mtd": "merges.total_docs",
"mts": "merges.total_size",
"mtt": "merges.total_time",
"rto": "refresh.total",
"rti": "refresh.time",
// "rto": "refresh.external_total",
// "rti": "refresh.external_time",
"rli": "refresh.listeners",
"sfc": "search.fetch_current",
"sfti": "search.fetch_time",
"sfto": "search.fetch_total",
"so": "search.open_contexts",
"sqc": "search.query_current",
"sqti": "search.query_time",
"sqto": "search.query_total",
"scc": "search.scroll_current",
"scti": "search.scroll_time",
"scto": "search.scroll_total",
"sc": "segments.count",
"sm": "segments.memory",
"siwm": "segments.index_writer_memory",
"svmm": "segments.version_map_memory",
"sfbm": "segments.fixed_bitset_memory",
"sqm": "seq_no.max",
"sql": "seq_no.local_checkpoint",
"sqg": "seq_no.global_checkpoint",
"wc": "warmer.current",
"wto": "warmer.total",
"wtt": "warmer.total_time",
}
================================================
FILE: cat_shards_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestCatShards(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t, SetDecoder(&strictDecoder{})) //, SetTraceLog(log.New(os.Stdout, "", 0)))
ctx := context.Background()
res, err := client.CatShards().Columns("*").Pretty(true).Do(ctx)
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("want response, have nil")
}
if len(res) == 0 {
t.Fatalf("want response, have: %v", res)
}
if have := res[0].Index; have == "" {
t.Fatalf("Index[0]: want != %q, have %q", "", have)
}
}
// TestCatShardsResponseRowAliasesMap tests if catIndicesResponseRowAliasesMap is declared
func TestCatShardsResponseRowAliasesMap(t *testing.T) {
if catIndicesResponseRowAliasesMap == nil {
t.Fatal("want catIndicesResponseRowAliasesMap to be not nil")
}
if len(catIndicesResponseRowAliasesMap) == 0 {
t.Fatal("want catIndicesResponseRowAliasesMap to be not empty")
}
}
// TestCatShardsWithSpecificColumns makes a simple test with specific column names.
func TestCatShardsWithSpecificColumns(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t, SetDecoder(&strictDecoder{})) // , SetTraceLog(log.New(os.Stdout, "", 0)))
ctx := context.Background()
res, err := client.CatShards().Columns("index", "shard").Do(ctx)
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("want response, have nil")
}
if len(res) == 0 {
t.Fatalf("want response, have: %v", res)
}
if have := res[0].Index; have == "" {
t.Fatalf("Index[0]: want != %q, have %q", "", have)
}
}
================================================
FILE: cat_snapshots.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// CatSnapshotsService returns the list of snapshots.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.13/cat-snapshots.html
// for details.
type CatSnapshotsService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
repository string // snapshot repository used to limit the request
masterTimeout string
columns []string
sort []string // list of columns for sort order
}
// NewCatSnapshotsService creates a new NewCatSnapshotsService.
func NewCatSnapshotsService(client *Client) *CatSnapshotsService {
return &CatSnapshotsService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *CatSnapshotsService) Pretty(pretty bool) *CatSnapshotsService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *CatSnapshotsService) Human(human bool) *CatSnapshotsService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *CatSnapshotsService) ErrorTrace(errorTrace bool) *CatSnapshotsService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *CatSnapshotsService) FilterPath(filterPath ...string) *CatSnapshotsService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *CatSnapshotsService) Header(name string, value string) *CatSnapshotsService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *CatSnapshotsService) Headers(headers http.Header) *CatSnapshotsService {
s.headers = headers
return s
}
// Repository specifies the napshot repository used to limit the request.
func (s *CatSnapshotsService) Repository(repository string) *CatSnapshotsService {
s.repository = repository
return s
}
// MasterTimeout is the explicit operation timeout for connection to master node.
func (s *CatSnapshotsService) MasterTimeout(masterTimeout string) *CatSnapshotsService {
s.masterTimeout = masterTimeout
return s
}
// Columns to return in the response.
// To get a list of all possible columns to return, run the following command
// in your terminal:
//
// Example:
// curl 'http://localhost:9200/_cat/snapshots/?help'
//
// You can use Columns("*") to return all possible columns. That might take
// a little longer than the default set of columns.
func (s *CatSnapshotsService) Columns(columns ...string) *CatSnapshotsService {
s.columns = columns
return s
}
// Sort is a list of fields to sort by.
func (s *CatSnapshotsService) Sort(fields ...string) *CatSnapshotsService {
s.sort = fields
return s
}
// buildURL builds the URL for the operation.
func (s *CatSnapshotsService) buildURL() (string, url.Values, error) {
// Build URL
var (
path string
err error
)
if s.repository != "" {
path, err = uritemplates.Expand("/_cat/snapshots/{repository}", map[string]string{
"repository": s.repository,
})
} else {
path = "/_cat/snapshots"
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{
"format": []string{"json"}, // always returns as JSON
}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
// TODO
if len(s.columns) > 0 {
// loop through all columns and apply alias if needed
for i, column := range s.columns {
if fullValueRaw, isAliased := catSnapshotsResponseRowAliasesMap[column]; isAliased {
// alias can be translated to multiple fields,
// so if translated value contains a comma, than replace the first value
// and append the others
if strings.Contains(fullValueRaw, ",") {
fullValues := strings.Split(fullValueRaw, ",")
s.columns[i] = fullValues[0]
s.columns = append(s.columns, fullValues[1:]...)
} else {
s.columns[i] = fullValueRaw
}
}
}
params.Set("h", strings.Join(s.columns, ","))
}
if len(s.sort) > 0 {
params.Set("s", strings.Join(s.sort, ","))
}
return path, params, nil
}
// Do executes the operation.
func (s *CatSnapshotsService) Do(ctx context.Context) (CatSnapshotsResponse, error) {
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
var ret CatSnapshotsResponse
if err := s.client.decoder.Decode(res.Body, &ret); err != nil {
return nil, err
}
return ret, nil
}
// -- Result of a get request.
// CatSnapshotsResponse is the outcome of CatSnapshotsService.Do.
type CatSnapshotsResponse []CatSnapshotsResponseRow
// CatSnapshotssResponseRow specifies the data returned for one index
// of a CatSnapshotsResponse. Notice that not all of these fields might
// be filled; that depends on the number of columns chose in the
// request (see CatSnapshotsService.Columns).
type CatSnapshotsResponseRow struct {
ID string `json:"id"` // ID of the snapshot, such as "snap1".
Repository string `json:"repository"` // Name of the repository, such as "repo1".
Status string `json:"status"` // One of "FAILED", "INCOMPATIBLE", "IN_PROGRESS", "PARTIAL" or "SUCCESS".
StartEpoch string `json:"start_epoch"` // Unix epoch time at which the snapshot process started.
StartTime string `json:"start_time"` // HH:MM:SS time at which the snapshot process started.
EndEpoch string `json:"end_epoch"` // Unix epoch time at which the snapshot process ended.
EndTime string `json:"end_time"` // HH:MM:SS time at which the snapshot process ended.
Duration string `json:"duration"` // Time it took the snapshot process to complete in time units.
Indices string `json:"indices"` // Number of indices in the snapshot.
SuccessfulShards string `json:"successful_shards"` // Number of successful shards in the snapshot.
FailedShards string `json:"failed_shards"` // Number of failed shards in the snapshot.
TotalShards string `json:"total_shards"` // Total number of shards in the snapshot.
Reason string `json:"reason"` // Reason for any snapshot failures.
}
// catSnapshotsResponseRowAliasesMap holds the global map for columns aliases
// the map is used by CatSnapshotsService.buildURL.
// For backwards compatibility some fields are able to have the same aliases
// that means that one alias can be translated to different columns (from different elastic versions)
// example for understanding: rto -> RefreshTotal, RefreshExternalTotal
var catSnapshotsResponseRowAliasesMap = map[string]string{
"snapshot": "id",
"re": "repository",
"s": "status",
"ste": "start_epoch",
"sti": "start_time",
"ete": "end_epoch",
"eti": "end_time",
"dur": "duration",
"i": "indices",
"ss": "successful_shards",
"fs": "failed_shards",
"ts": "total_shards",
"`r": "reason",
}
================================================
FILE: cat_snapshots_integration_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
"time"
)
func TestCatSnapshotsIntegration(t *testing.T) {
if isCI() {
t.Skip("this test requires local directories")
}
client := setupTestClientAndCreateIndexAndAddDocs(t, SetDecoder(&strictDecoder{})) // , SetTraceLog(log.New(os.Stdout, "", 0)))
{
// Create a repository for this test
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
_, err := client.SnapshotCreateRepository("my_backup").
Type("fs").
Settings(map[string]interface{}{
// Notice the path is configured as path.repo in docker-compose.yml
"location": "/usr/share/elasticsearch/backup",
}).
Do(ctx)
if err != nil {
t.Fatal(err)
}
// Make a snapshot
ctx, cancel = context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
_, err = client.SnapshotCreate("my_backup", "snapshot_1").
WaitForCompletion(true).
Do(ctx)
if err != nil {
t.Fatal(err)
}
defer func() {
// Remove snapshot
_, _ = client.SnapshotDelete("my_backup", "snapshot_1").Do(context.Background())
// Remove repository
_, _ = client.SnapshotDeleteRepository("my_backup").Do(context.Background())
}()
}
// List snapshots of repository
ctx := context.Background()
res, err := client.CatSnapshots().Repository("my_backup").Columns("*").Do(ctx)
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("want response, have nil")
}
if want, have := 1, len(res); want != have {
t.Fatalf("want %d snapshot, have %d", want, have)
}
if want, have := "snapshot_1", res[0].ID; want != have {
t.Fatalf("want ID=%q, have %q", want, have)
}
}
================================================
FILE: cat_snapshots_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"testing"
)
func TestCatSnapshots(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t, SetDecoder(&strictDecoder{})) // , SetTraceLog(log.New(os.Stdout, "", 0)))
urls, params, err := client.CatSnapshots().Repository("my_repo").Columns("*").buildURL()
if err != nil {
t.Fatal(err)
}
if want, have := "/_cat/snapshots/my_repo", urls; want != have {
t.Fatalf("want URL=%q, have %q", want, have)
}
if want, have := "format=json&h=%2A", params.Encode(); want != have {
t.Fatalf("want Params=%q, have %q", want, have)
}
}
================================================
FILE: clear_scroll.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
)
// ClearScrollService clears one or more scroll contexts by their ids.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-request-scroll.html#_clear_scroll_api
// for details.
type ClearScrollService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
scrollId []string
}
// NewClearScrollService creates a new ClearScrollService.
func NewClearScrollService(client *Client) *ClearScrollService {
return &ClearScrollService{
client: client,
scrollId: make([]string, 0),
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *ClearScrollService) Pretty(pretty bool) *ClearScrollService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *ClearScrollService) Human(human bool) *ClearScrollService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *ClearScrollService) ErrorTrace(errorTrace bool) *ClearScrollService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *ClearScrollService) FilterPath(filterPath ...string) *ClearScrollService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *ClearScrollService) Header(name string, value string) *ClearScrollService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *ClearScrollService) Headers(headers http.Header) *ClearScrollService {
s.headers = headers
return s
}
// ScrollId is a list of scroll IDs to clear.
// Use _all to clear all search contexts.
func (s *ClearScrollService) ScrollId(scrollIds ...string) *ClearScrollService {
s.scrollId = append(s.scrollId, scrollIds...)
return s
}
// buildURL builds the URL for the operation.
func (s *ClearScrollService) buildURL() (string, url.Values, error) {
// Build URL
path := "/_search/scroll/"
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *ClearScrollService) Validate() error {
var invalid []string
if len(s.scrollId) == 0 {
invalid = append(invalid, "ScrollId")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *ClearScrollService) Do(ctx context.Context) (*ClearScrollResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Setup HTTP request body
body := map[string][]string{
"scroll_id": s.scrollId,
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "DELETE",
Path: path,
Params: params,
Body: body,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(ClearScrollResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// ClearScrollResponse is the response of ClearScrollService.Do.
type ClearScrollResponse struct {
Succeeded bool `json:"succeeded,omitempty"`
NumFreed int `json:"num_freed,omitempty"`
}
================================================
FILE: clear_scroll_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
_ "net/http"
"testing"
)
func TestClearScroll(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
// client := setupTestClientAndCreateIndex(t, SetTraceLog(log.New(os.Stdout, "", log.LstdFlags)))
tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
tweet2 := tweet{User: "olivere", Message: "Another unrelated topic."}
tweet3 := tweet{User: "sandrae", Message: "Cycling is fun."}
// Add all documents
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("3").BodyJson(&tweet3).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Match all should return all documents
res, err := client.Scroll(testIndexName).Size(1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected results != nil; got nil")
}
if res.ScrollId == "" {
t.Fatalf("expected scrollId in results; got %q", res.ScrollId)
}
// Search should succeed
_, err = client.Scroll(testIndexName).Size(1).ScrollId(res.ScrollId).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Clear scroll id
clearScrollRes, err := client.ClearScroll().ScrollId(res.ScrollId).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if clearScrollRes == nil {
t.Fatal("expected results != nil; got nil")
}
// Search result should fail
_, err = client.Scroll(testIndexName).Size(1).ScrollId(res.ScrollId).Do(context.TODO())
if err == nil {
t.Fatalf("expected scroll to fail")
}
}
func TestClearScrollValidate(t *testing.T) {
client := setupTestClient(t)
// No scroll id -> fail with error
res, err := NewClearScrollService(client).Do(context.TODO())
if err == nil {
t.Fatalf("expected ClearScroll to fail without scroll ids")
}
if res != nil {
t.Fatalf("expected result to be nil; got: %v", res)
}
}
================================================
FILE: client.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"bytes"
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"net/http/httputil"
"net/url"
"os"
"runtime"
"strings"
"sync"
"time"
"github.com/pkg/errors"
"github.com/olivere/elastic/v7/config"
)
const (
// Version is the current version of Elastic.
Version = "7.0.32"
// DefaultURL is the default endpoint of Elasticsearch on the local machine.
// It is used e.g. when initializing a new Client without a specific URL.
DefaultURL = "http://127.0.0.1:9200"
// DefaultScheme is the default protocol scheme to use when sniffing
// the Elasticsearch cluster.
DefaultScheme = "http"
// DefaultHealthcheckEnabled specifies if healthchecks are enabled by default.
DefaultHealthcheckEnabled = true
// DefaultHealthcheckTimeoutStartup is the time the healthcheck waits
// for a response from Elasticsearch on startup, i.e. when creating a
// client. After the client is started, a shorter timeout is commonly used
// (its default is specified in DefaultHealthcheckTimeout).
DefaultHealthcheckTimeoutStartup = 5 * time.Second
// DefaultHealthcheckTimeout specifies the time a running client waits for
// a response from Elasticsearch. Notice that the healthcheck timeout
// when a client is created is larger by default (see DefaultHealthcheckTimeoutStartup).
DefaultHealthcheckTimeout = 1 * time.Second
// DefaultHealthcheckInterval is the default interval between
// two health checks of the nodes in the cluster.
DefaultHealthcheckInterval = 60 * time.Second
// DefaultSnifferEnabled specifies if the sniffer is enabled by default.
DefaultSnifferEnabled = true
// DefaultSnifferInterval is the interval between two sniffing procedures,
// i.e. the lookup of all nodes in the cluster and their addition/removal
// from the list of actual connections.
DefaultSnifferInterval = 15 * time.Minute
// DefaultSnifferTimeoutStartup is the default timeout for the sniffing
// process that is initiated while creating a new client. For subsequent
// sniffing processes, DefaultSnifferTimeout is used (by default).
DefaultSnifferTimeoutStartup = 5 * time.Second
// DefaultSnifferTimeout is the default timeout after which the
// sniffing process times out. Notice that for the initial sniffing
// process, DefaultSnifferTimeoutStartup is used.
DefaultSnifferTimeout = 2 * time.Second
// DefaultSendGetBodyAs is the HTTP method to use when elastic is sending
// a GET request with a body.
DefaultSendGetBodyAs = "GET"
// DefaultGzipEnabled specifies if gzip compression is enabled by default.
DefaultGzipEnabled = false
// off is used to disable timeouts.
off = -1 * time.Second
)
var (
// nilByte is used in JSON marshal/unmarshal
nilByte = []byte("null")
// ErrNoClient is raised when no Elasticsearch node is available.
ErrNoClient = errors.New("no Elasticsearch node available")
// ErrRetry is raised when a request cannot be executed after the configured
// number of retries.
ErrRetry = errors.New("cannot connect after several retries")
// ErrTimeout is raised when a request timed out, e.g. when WaitForStatus
// didn't return in time.
ErrTimeout = errors.New("timeout")
// noRetries is a retrier that does not retry.
noRetries = NewStopRetrier()
// noDeprecationLog is a no-op for logging deprecations.
noDeprecationLog = func(*http.Request, *http.Response) {}
)
// Doer is an interface to perform HTTP requests.
// It can be used for mocking.
type Doer interface {
Do(*http.Request) (*http.Response, error)
}
// ClientOptionFunc is a function that configures a Client.
// It is used in NewClient.
type ClientOptionFunc func(*Client) error
// Client is an Elasticsearch client. Create one by calling NewClient.
type Client struct {
c Doer // e.g. a net/*http.Client to use for requests
connsMu sync.RWMutex // connsMu guards the next block
conns []*conn // all connections
cindex int // index into conns
mu sync.RWMutex // guards the next block
urls []string // set of URLs passed initially to the client
running bool // true if the client's background processes are running
errorlog Logger // error log for critical messages
infolog Logger // information log for e.g. response times
tracelog Logger // trace log for debugging
deprecationlog func(*http.Request, *http.Response)
scheme string // http or https
healthcheckEnabled bool // healthchecks enabled or disabled
healthcheckTimeoutStartup time.Duration // time the healthcheck waits for a response from Elasticsearch on startup
healthcheckTimeout time.Duration // time the healthcheck waits for a response from Elasticsearch
healthcheckInterval time.Duration // interval between healthchecks
healthcheckStop chan bool // notify healthchecker to stop, and notify back
snifferEnabled bool // sniffer enabled or disabled
snifferTimeoutStartup time.Duration // time the sniffer waits for a response from nodes info API on startup
snifferTimeout time.Duration // time the sniffer waits for a response from nodes info API
snifferInterval time.Duration // interval between sniffing
snifferCallback SnifferCallback // callback to modify the sniffing decision
snifferStop chan bool // notify sniffer to stop, and notify back
decoder Decoder // used to decode data sent from Elasticsearch
basicAuthUsername string // username for HTTP Basic Auth
basicAuthPassword string // password for HTTP Basic Auth
sendGetBodyAs string // override for when sending a GET with a body
gzipEnabled bool // gzip compression enabled or disabled (default)
requiredPlugins []string // list of required plugins
retrier Retrier // strategy for retries
retryStatusCodes []int // HTTP status codes where to retry automatically (with retrier)
headers http.Header // a list of default headers to add to each request
}
// NewClient creates a new client to work with Elasticsearch.
//
// NewClient, by default, is meant to be long-lived and shared across
// your application. If you need a short-lived client, e.g. for request-scope,
// consider using NewSimpleClient instead.
//
// The caller can configure the new client by passing configuration options
// to the func.
//
// Example:
//
// client, err := elastic.NewClient(
// elastic.SetURL("http://127.0.0.1:9200", "http://127.0.0.1:9201"),
// elastic.SetBasicAuth("user", "secret"))
//
// If no URL is configured, Elastic uses DefaultURL by default.
//
// If the sniffer is enabled (the default), the new client then sniffes
// the cluster via the Nodes Info API
// (see https://www.elastic.co/guide/en/elasticsearch/reference/7.0/cluster-nodes-info.html#cluster-nodes-info).
// It uses the URLs specified by the caller. The caller is responsible
// to only pass a list of URLs of nodes that belong to the same cluster.
// This sniffing process is run on startup and periodically.
// Use SnifferInterval to set the interval between two sniffs (default is
// 15 minutes). In other words: By default, the client will find new nodes
// in the cluster and remove those that are no longer available every
// 15 minutes. Disable the sniffer by passing SetSniff(false) to NewClient.
//
// The list of nodes found in the sniffing process will be used to make
// connections to the REST API of Elasticsearch. These nodes are also
// periodically checked in a shorter time frame. This process is called
// a health check. By default, a health check is done every 60 seconds.
// You can set a shorter or longer interval by SetHealthcheckInterval.
// Disabling health checks is not recommended, but can be done by
// SetHealthcheck(false).
//
// Connections are automatically marked as dead or healthy while
// making requests to Elasticsearch. When a request fails, Elastic will
// call into the Retry strategy which can be specified with SetRetry.
// The Retry strategy is also responsible for handling backoff i.e. the time
// to wait before starting the next request. There are various standard
// backoff implementations, e.g. ExponentialBackoff or SimpleBackoff.
// Retries are disabled by default.
//
// If no HttpClient is configured, then http.DefaultClient is used.
// You can use your own http.Client with some http.Transport for
// advanced scenarios.
//
// An error is also returned when some configuration option is invalid or
// the new client cannot sniff the cluster (if enabled).
func NewClient(options ...ClientOptionFunc) (*Client, error) {
return DialContext(context.Background(), options...)
}
// NewClientFromConfig initializes a client from a configuration.
func NewClientFromConfig(cfg *config.Config) (*Client, error) {
options, err := configToOptions(cfg)
if err != nil {
return nil, err
}
return DialContext(context.Background(), options...)
}
// NewSimpleClient creates a new short-lived Client that can be used in
// use cases where you need e.g. one client per request.
//
// While NewClient by default sets up e.g. periodic health checks
// and sniffing for new nodes in separate goroutines, NewSimpleClient does
// not and is meant as a simple replacement where you don't need all the
// heavy lifting of NewClient.
//
// NewSimpleClient does the following by default: First, all health checks
// are disabled, including timeouts and periodic checks. Second, sniffing
// is disabled, including timeouts and periodic checks. The number of retries
// is set to 1. NewSimpleClient also does not start any goroutines.
//
// Notice that you can still override settings by passing additional options,
// just like with NewClient.
func NewSimpleClient(options ...ClientOptionFunc) (*Client, error) {
c := &Client{
c: http.DefaultClient,
conns: make([]*conn, 0),
cindex: -1,
scheme: DefaultScheme,
decoder: &DefaultDecoder{},
healthcheckEnabled: false,
healthcheckTimeoutStartup: off,
healthcheckTimeout: off,
healthcheckInterval: off,
healthcheckStop: make(chan bool),
snifferEnabled: false,
snifferTimeoutStartup: off,
snifferTimeout: off,
snifferInterval: off,
snifferCallback: nopSnifferCallback,
snifferStop: make(chan bool),
sendGetBodyAs: DefaultSendGetBodyAs,
gzipEnabled: DefaultGzipEnabled,
retrier: noRetries, // no retries by default
retryStatusCodes: nil, // no automatic retries for specific HTTP status codes
deprecationlog: noDeprecationLog,
}
// Run the options on it
for _, option := range options {
if err := option(c); err != nil {
return nil, err
}
}
// Use a default URL and normalize them
if len(c.urls) == 0 {
c.urls = []string{DefaultURL}
}
c.urls = canonicalize(c.urls...)
// If the URLs have auth info, use them here as an alternative to SetBasicAuth
if c.basicAuthUsername == "" && c.basicAuthPassword == "" {
for _, urlStr := range c.urls {
u, err := url.Parse(urlStr)
if err == nil && u.User != nil {
c.basicAuthUsername = u.User.Username()
c.basicAuthPassword, _ = u.User.Password()
break
}
}
}
for _, url := range c.urls {
c.conns = append(c.conns, newConn(url, url))
}
// Ensure that we have at least one connection available
if err := c.mustActiveConn(); err != nil {
return nil, err
}
// Check the required plugins
for _, plugin := range c.requiredPlugins {
found, err := c.HasPlugin(plugin)
if err != nil {
return nil, err
}
if !found {
return nil, fmt.Errorf("elastic: plugin %s not found", plugin)
}
}
c.mu.Lock()
c.running = true
c.mu.Unlock()
return c, nil
}
// Dial will call DialContext with a background context.
func Dial(options ...ClientOptionFunc) (*Client, error) {
return DialContext(context.Background(), options...)
}
// DialContext will connect to Elasticsearch, just like NewClient does.
//
// The context is honoured in terms of e.g. cancellation.
func DialContext(ctx context.Context, options ...ClientOptionFunc) (*Client, error) {
// Set up the client
c := &Client{
c: http.DefaultClient,
conns: make([]*conn, 0),
cindex: -1,
scheme: DefaultScheme,
decoder: &DefaultDecoder{},
healthcheckEnabled: DefaultHealthcheckEnabled,
healthcheckTimeoutStartup: DefaultHealthcheckTimeoutStartup,
healthcheckTimeout: DefaultHealthcheckTimeout,
healthcheckInterval: DefaultHealthcheckInterval,
healthcheckStop: make(chan bool),
snifferEnabled: DefaultSnifferEnabled,
snifferTimeoutStartup: DefaultSnifferTimeoutStartup,
snifferTimeout: DefaultSnifferTimeout,
snifferInterval: DefaultSnifferInterval,
snifferCallback: nopSnifferCallback,
snifferStop: make(chan bool),
sendGetBodyAs: DefaultSendGetBodyAs,
gzipEnabled: DefaultGzipEnabled,
retrier: noRetries, // no retries by default
retryStatusCodes: nil, // no automatic retries for specific HTTP status codes
deprecationlog: noDeprecationLog,
}
// Run the options on it
for _, option := range options {
if err := option(c); err != nil {
return nil, err
}
}
// Use a default URL and normalize them
if len(c.urls) == 0 {
c.urls = []string{DefaultURL}
}
c.urls = canonicalize(c.urls...)
// If the URLs have auth info, use them here as an alternative to SetBasicAuth
if c.basicAuthUsername == "" && c.basicAuthPassword == "" {
for _, urlStr := range c.urls {
u, err := url.Parse(urlStr)
if err == nil && u.User != nil {
c.basicAuthUsername = u.User.Username()
c.basicAuthPassword, _ = u.User.Password()
break
}
}
}
// Check if we can make a request to any of the specified URLs
if c.healthcheckEnabled {
if err := c.startupHealthcheck(ctx, c.healthcheckTimeoutStartup); err != nil {
return nil, err
}
}
if c.snifferEnabled {
// Sniff the cluster initially
if err := c.sniff(ctx, c.snifferTimeoutStartup); err != nil {
return nil, err
}
} else {
// Do not sniff the cluster initially. Use the provided URLs instead.
for _, url := range c.urls {
c.conns = append(c.conns, newConn(url, url))
}
}
if c.healthcheckEnabled {
// Perform an initial health check
c.healthcheck(ctx, c.healthcheckTimeoutStartup, true)
}
// Ensure that we have at least one connection available
if err := c.mustActiveConn(); err != nil {
return nil, err
}
// Check the required plugins
for _, plugin := range c.requiredPlugins {
found, err := c.HasPlugin(plugin)
if err != nil {
return nil, err
}
if !found {
return nil, fmt.Errorf("elastic: plugin %s not found", plugin)
}
}
if c.snifferEnabled {
go c.sniffer() // periodically update cluster information
}
if c.healthcheckEnabled {
go c.healthchecker() // start goroutine periodically ping all nodes of the cluster
}
c.mu.Lock()
c.running = true
c.mu.Unlock()
return c, nil
}
// DialWithConfig will use the configuration settings parsed from config package
// to connect to Elasticsearch.
//
// The context is honoured in terms of e.g. cancellation.
func DialWithConfig(ctx context.Context, cfg *config.Config) (*Client, error) {
options, err := configToOptions(cfg)
if err != nil {
return nil, err
}
return DialContext(ctx, options...)
}
func configToOptions(cfg *config.Config) ([]ClientOptionFunc, error) {
var options []ClientOptionFunc
if cfg != nil {
if cfg.URL != "" {
options = append(options, SetURL(cfg.URL))
}
if cfg.Errorlog != "" {
f, err := os.OpenFile(cfg.Errorlog, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return nil, errors.Wrap(err, "unable to initialize error log")
}
l := log.New(f, "", 0)
options = append(options, SetErrorLog(l))
}
if cfg.Tracelog != "" {
f, err := os.OpenFile(cfg.Tracelog, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return nil, errors.Wrap(err, "unable to initialize trace log")
}
l := log.New(f, "", 0)
options = append(options, SetTraceLog(l))
}
if cfg.Infolog != "" {
f, err := os.OpenFile(cfg.Infolog, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return nil, errors.Wrap(err, "unable to initialize info log")
}
l := log.New(f, "", 0)
options = append(options, SetInfoLog(l))
}
if cfg.Username != "" || cfg.Password != "" {
options = append(options, SetBasicAuth(cfg.Username, cfg.Password))
}
if cfg.Sniff != nil {
options = append(options, SetSniff(*cfg.Sniff))
}
if cfg.Healthcheck != nil {
options = append(options, SetHealthcheck(*cfg.Healthcheck))
}
}
return options, nil
}
// SetHttpClient can be used to specify the http.Client to use when making
// HTTP requests to Elasticsearch.
func SetHttpClient(httpClient Doer) ClientOptionFunc {
return func(c *Client) error {
if httpClient != nil {
c.c = httpClient
} else {
c.c = http.DefaultClient
}
return nil
}
}
// SetBasicAuth can be used to specify the HTTP Basic Auth credentials to
// use when making HTTP requests to Elasticsearch.
func SetBasicAuth(username, password string) ClientOptionFunc {
return func(c *Client) error {
c.basicAuthUsername = username
c.basicAuthPassword = password
return nil
}
}
// SetURL defines the URL endpoints of the Elasticsearch nodes. Notice that
// when sniffing is enabled, these URLs are used to initially sniff the
// cluster on startup.
func SetURL(urls ...string) ClientOptionFunc {
return func(c *Client) error {
switch len(urls) {
case 0:
c.urls = []string{DefaultURL}
default:
c.urls = urls
}
// Check URLs
for _, urlStr := range c.urls {
if _, err := url.Parse(urlStr); err != nil {
return err
}
}
return nil
}
}
// SetScheme sets the HTTP scheme to look for when sniffing (http or https).
// This is http by default.
func SetScheme(scheme string) ClientOptionFunc {
return func(c *Client) error {
c.scheme = scheme
return nil
}
}
// SetSniff enables or disables the sniffer (enabled by default).
func SetSniff(enabled bool) ClientOptionFunc {
return func(c *Client) error {
c.snifferEnabled = enabled
return nil
}
}
// SetSnifferTimeoutStartup sets the timeout for the sniffer that is used
// when creating a new client. The default is 5 seconds. Notice that the
// timeout being used for subsequent sniffing processes is set with
// SetSnifferTimeout.
func SetSnifferTimeoutStartup(timeout time.Duration) ClientOptionFunc {
return func(c *Client) error {
c.snifferTimeoutStartup = timeout
return nil
}
}
// SetSnifferTimeout sets the timeout for the sniffer that finds the
// nodes in a cluster. The default is 2 seconds. Notice that the timeout
// used when creating a new client on startup is usually greater and can
// be set with SetSnifferTimeoutStartup.
func SetSnifferTimeout(timeout time.Duration) ClientOptionFunc {
return func(c *Client) error {
c.snifferTimeout = timeout
return nil
}
}
// SetSnifferInterval sets the interval between two sniffing processes.
// The default interval is 15 minutes.
func SetSnifferInterval(interval time.Duration) ClientOptionFunc {
return func(c *Client) error {
c.snifferInterval = interval
return nil
}
}
// SnifferCallback defines the protocol for sniffing decisions.
type SnifferCallback func(*NodesInfoNode) bool
// nopSnifferCallback is the default sniffer callback: It accepts
// all nodes the sniffer finds.
var nopSnifferCallback = func(*NodesInfoNode) bool { return true }
// SetSnifferCallback allows the caller to modify sniffer decisions.
// When setting the callback, the given SnifferCallback is called for
// each (healthy) node found during the sniffing process.
// If the callback returns false, the node is ignored: No requests
// are routed to it.
func SetSnifferCallback(f SnifferCallback) ClientOptionFunc {
return func(c *Client) error {
if f != nil {
c.snifferCallback = f
}
return nil
}
}
// SetHealthcheck enables or disables healthchecks (enabled by default).
func SetHealthcheck(enabled bool) ClientOptionFunc {
return func(c *Client) error {
c.healthcheckEnabled = enabled
return nil
}
}
// SetHealthcheckTimeoutStartup sets the timeout for the initial health check.
// The default timeout is 5 seconds (see DefaultHealthcheckTimeoutStartup).
// Notice that timeouts for subsequent health checks can be modified with
// SetHealthcheckTimeout.
func SetHealthcheckTimeoutStartup(timeout time.Duration) ClientOptionFunc {
return func(c *Client) error {
c.healthcheckTimeoutStartup = timeout
return nil
}
}
// SetHealthcheckTimeout sets the timeout for periodic health checks.
// The default timeout is 1 second (see DefaultHealthcheckTimeout).
// Notice that a different (usually larger) timeout is used for the initial
// healthcheck, which is initiated while creating a new client.
// The startup timeout can be modified with SetHealthcheckTimeoutStartup.
func SetHealthcheckTimeout(timeout time.Duration) ClientOptionFunc {
return func(c *Client) error {
c.healthcheckTimeout = timeout
return nil
}
}
// SetHealthcheckInterval sets the interval between two health checks.
// The default interval is 60 seconds.
func SetHealthcheckInterval(interval time.Duration) ClientOptionFunc {
return func(c *Client) error {
c.healthcheckInterval = interval
return nil
}
}
// SetMaxRetries sets the maximum number of retries before giving up when
// performing a HTTP request to Elasticsearch.
//
// Deprecated: Replace with a Retry implementation.
func SetMaxRetries(maxRetries int) ClientOptionFunc {
return func(c *Client) error {
if maxRetries < 0 {
return errors.New("MaxRetries must be greater than or equal to 0")
} else if maxRetries == 0 {
c.retrier = noRetries
} else {
// Create a Retrier that will wait for 100ms (+/- jitter) between requests.
// This resembles the old behavior with maxRetries.
ticks := make([]int, maxRetries)
for i := 0; i < len(ticks); i++ {
ticks[i] = 100
}
backoff := NewSimpleBackoff(ticks...)
c.retrier = NewBackoffRetrier(backoff)
}
return nil
}
}
// SetGzip enables or disables gzip compression (disabled by default).
func SetGzip(enabled bool) ClientOptionFunc {
return func(c *Client) error {
c.gzipEnabled = enabled
return nil
}
}
// SetDecoder sets the Decoder to use when decoding data from Elasticsearch.
// DefaultDecoder is used by default.
func SetDecoder(decoder Decoder) ClientOptionFunc {
return func(c *Client) error {
if decoder != nil {
c.decoder = decoder
} else {
c.decoder = &DefaultDecoder{}
}
return nil
}
}
// SetRequiredPlugins can be used to indicate that some plugins are required
// before a Client will be created.
func SetRequiredPlugins(plugins ...string) ClientOptionFunc {
return func(c *Client) error {
if c.requiredPlugins == nil {
c.requiredPlugins = make([]string, 0)
}
c.requiredPlugins = append(c.requiredPlugins, plugins...)
return nil
}
}
// SetErrorLog sets the logger for critical messages like nodes joining
// or leaving the cluster or failing requests. It is nil by default.
func SetErrorLog(logger Logger) ClientOptionFunc {
return func(c *Client) error {
c.errorlog = logger
return nil
}
}
// SetInfoLog sets the logger for informational messages, e.g. requests
// and their response times. It is nil by default.
func SetInfoLog(logger Logger) ClientOptionFunc {
return func(c *Client) error {
c.infolog = logger
return nil
}
}
// SetTraceLog specifies the log.Logger to use for output of HTTP requests
// and responses which is helpful during debugging. It is nil by default.
func SetTraceLog(logger Logger) ClientOptionFunc {
return func(c *Client) error {
c.tracelog = logger
return nil
}
}
// SetSendGetBodyAs specifies the HTTP method to use when sending a GET request
// with a body. It is GET by default.
func SetSendGetBodyAs(httpMethod string) ClientOptionFunc {
return func(c *Client) error {
c.sendGetBodyAs = httpMethod
return nil
}
}
// SetRetrier specifies the retry strategy that handles errors during
// HTTP request/response with Elasticsearch.
func SetRetrier(retrier Retrier) ClientOptionFunc {
return func(c *Client) error {
if retrier == nil {
retrier = noRetries // no retries by default
}
c.retrier = retrier
return nil
}
}
// SetRetryStatusCodes specifies the HTTP status codes where the client
// will retry automatically. Notice that retries call the specified retrier,
// so calling SetRetryStatusCodes without setting a Retrier won't do anything
// for retries.
func SetRetryStatusCodes(statusCodes ...int) ClientOptionFunc {
return func(c *Client) error {
c.retryStatusCodes = statusCodes
return nil
}
}
// SetHeaders adds a list of default HTTP headers that will be added to
// each requests executed by PerformRequest.
func SetHeaders(headers http.Header) ClientOptionFunc {
return func(c *Client) error {
c.headers = headers
return nil
}
}
// String returns a string representation of the client status.
func (c *Client) String() string {
c.connsMu.Lock()
conns := c.conns
c.connsMu.Unlock()
var buf bytes.Buffer
for i, conn := range conns {
if i > 0 {
buf.WriteString(", ")
}
buf.WriteString(conn.String())
}
return buf.String()
}
// IsRunning returns true if the background processes of the client are
// running, false otherwise.
func (c *Client) IsRunning() bool {
c.mu.RLock()
defer c.mu.RUnlock()
return c.running
}
// Start starts the background processes like sniffing the cluster and
// periodic health checks. You don't need to run Start when creating a
// client with NewClient; the background processes are run by default.
//
// If the background processes are already running, this is a no-op.
func (c *Client) Start() {
c.mu.RLock()
if c.running {
c.mu.RUnlock()
return
}
c.mu.RUnlock()
if c.snifferEnabled {
go c.sniffer()
}
if c.healthcheckEnabled {
go c.healthchecker()
}
c.mu.Lock()
c.running = true
c.mu.Unlock()
c.infof("elastic: client started")
}
// Stop stops the background processes that the client is running,
// i.e. sniffing the cluster periodically and running health checks
// on the nodes.
//
// If the background processes are not running, this is a no-op.
func (c *Client) Stop() {
c.mu.RLock()
if !c.running {
c.mu.RUnlock()
return
}
c.mu.RUnlock()
if c.healthcheckEnabled {
c.healthcheckStop <- true
<-c.healthcheckStop
}
if c.snifferEnabled {
c.snifferStop <- true
<-c.snifferStop
}
c.mu.Lock()
c.running = false
c.mu.Unlock()
c.infof("elastic: client stopped")
}
// errorf logs to the error log.
func (c *Client) errorf(format string, args ...interface{}) {
if c.errorlog != nil {
c.errorlog.Printf(format, args...)
}
}
// infof logs informational messages.
func (c *Client) infof(format string, args ...interface{}) {
if c.infolog != nil {
c.infolog.Printf(format, args...)
}
}
// tracef logs to the trace log.
func (c *Client) tracef(format string, args ...interface{}) {
if c.tracelog != nil {
c.tracelog.Printf(format, args...)
}
}
// dumpRequest dumps the given HTTP request to the trace log.
func (c *Client) dumpRequest(r *http.Request) {
if c.tracelog != nil {
out, err := httputil.DumpRequestOut(r, true)
if err == nil {
c.tracef("%s\n", string(out))
}
}
}
// dumpResponse dumps the given HTTP response to the trace log.
func (c *Client) dumpResponse(resp *http.Response) {
if c.tracelog != nil {
out, err := httputil.DumpResponse(resp, true)
if err == nil {
c.tracef("%s\n", string(out))
}
}
}
// sniffer periodically runs sniff.
func (c *Client) sniffer() {
c.mu.RLock()
timeout := c.snifferTimeout
interval := c.snifferInterval
c.mu.RUnlock()
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-c.snifferStop:
// we are asked to stop, so we signal back that we're stopping now
c.snifferStop <- true
return
case <-ticker.C:
c.sniff(context.Background(), timeout)
}
}
}
// sniff uses the Node Info API to return the list of nodes in the cluster.
// It uses the list of URLs passed on startup plus the list of URLs found
// by the preceding sniffing process (if sniffing is enabled).
//
// If sniffing is disabled, this is a no-op.
func (c *Client) sniff(parentCtx context.Context, timeout time.Duration) error {
c.mu.RLock()
if !c.snifferEnabled {
c.mu.RUnlock()
return nil
}
// Use all available URLs provided to sniff the cluster.
var urls []string
urlsMap := make(map[string]bool)
// Add all URLs provided on startup
for _, url := range c.urls {
urlsMap[url] = true
urls = append(urls, url)
}
c.mu.RUnlock()
// Add all URLs found by sniffing
c.connsMu.RLock()
for _, conn := range c.conns {
if !conn.IsDead() {
url := conn.URL()
if _, found := urlsMap[url]; !found {
urls = append(urls, url)
}
}
}
c.connsMu.RUnlock()
if len(urls) == 0 {
return errors.Wrap(ErrNoClient, "no URLs found")
}
// Start sniffing on all found URLs
ch := make(chan []*conn, len(urls))
ctx, cancel := context.WithTimeout(parentCtx, timeout)
defer cancel()
for _, url := range urls {
go func(url string) { ch <- c.sniffNode(ctx, url) }(url)
}
// Wait for the results to come back, or the process times out.
for {
select {
case conns := <-ch:
if len(conns) > 0 {
c.updateConns(conns)
return nil
}
case <-ctx.Done():
if err := ctx.Err(); err != nil {
switch {
case IsContextErr(err):
return err
}
return errors.Wrapf(ErrNoClient, "sniff timeout: %v", err)
}
// We get here if no cluster responds in time
return errors.Wrap(ErrNoClient, "sniff timeout")
}
}
}
// sniffNode sniffs a single node. This method is run as a goroutine
// in sniff. If successful, it returns the list of node URLs extracted
// from the result of calling Nodes Info API. Otherwise, an empty array
// is returned.
func (c *Client) sniffNode(ctx context.Context, url string) []*conn {
var nodes []*conn
// Call the Nodes Info API at /_nodes/http
req, err := NewRequest("GET", url+"/_nodes/http")
if err != nil {
return nodes
}
c.mu.RLock()
if c.basicAuthUsername != "" || c.basicAuthPassword != "" {
req.SetBasicAuth(c.basicAuthUsername, c.basicAuthPassword)
}
c.mu.RUnlock()
if req.Header.Get("User-Agent") == "" {
req.Header.Add("User-Agent", "elastic/"+Version+" ("+runtime.GOOS+"-"+runtime.GOARCH+")")
}
res, err := c.c.Do((*http.Request)(req).WithContext(ctx))
if err != nil {
return nodes
}
defer res.Body.Close()
var info NodesInfoResponse
if err := json.NewDecoder(res.Body).Decode(&info); err == nil {
if len(info.Nodes) > 0 {
for nodeID, node := range info.Nodes {
if c.snifferCallback(node) {
if node.HTTP != nil && len(node.HTTP.PublishAddress) > 0 {
url := c.extractHostname(c.scheme, node.HTTP.PublishAddress)
if url != "" {
nodes = append(nodes, newConn(nodeID, url))
}
}
}
}
}
}
return nodes
}
// extractHostname returns the URL from the http.publish_address setting.
func (c *Client) extractHostname(scheme, address string) string {
var (
host string
port string
addrs = strings.Split(address, "/")
ports = strings.Split(address, ":")
)
if len(addrs) > 1 {
host = addrs[0]
} else {
host = strings.Split(addrs[0], ":")[0]
}
port = ports[len(ports)-1]
return fmt.Sprintf("%s://%s:%s", scheme, host, port)
}
// updateConns updates the clients' connections with new information
// gather by a sniff operation.
func (c *Client) updateConns(conns []*conn) {
c.connsMu.Lock()
// Build up new connections:
// If we find an existing connection, use that (including no. of failures etc.).
// If we find a new connection, add it.
var newConns []*conn
for _, conn := range conns {
var found bool
for _, oldConn := range c.conns {
// Notice that e.g. in a Kubernetes cluster the NodeID might be
// stable while the URL has changed.
if oldConn.NodeID() == conn.NodeID() && oldConn.URL() == conn.URL() {
// Take over the old connection
newConns = append(newConns, oldConn)
found = true
break
}
}
if !found {
// New connection didn't exist, so add it to our list of new conns.
c.infof("elastic: %s joined the cluster", conn.URL())
newConns = append(newConns, conn)
}
}
c.conns = newConns
c.cindex = -1
c.connsMu.Unlock()
}
// healthchecker periodically runs healthcheck.
func (c *Client) healthchecker() {
c.mu.RLock()
timeout := c.healthcheckTimeout
interval := c.healthcheckInterval
c.mu.RUnlock()
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-c.healthcheckStop:
// we are asked to stop, so we signal back that we're stopping now
c.healthcheckStop <- true
return
case <-ticker.C:
c.healthcheck(context.Background(), timeout, false)
}
}
}
// healthcheck does a health check on all nodes in the cluster. Depending on
// the node state, it marks connections as dead, sets them alive etc.
// If healthchecks are disabled and force is false, this is a no-op.
// The timeout specifies how long to wait for a response from Elasticsearch.
func (c *Client) healthcheck(parentCtx context.Context, timeout time.Duration, force bool) {
c.mu.RLock()
if !c.healthcheckEnabled && !force {
c.mu.RUnlock()
return
}
headers := c.headers
basicAuth := c.basicAuthUsername != "" || c.basicAuthPassword != ""
basicAuthUsername := c.basicAuthUsername
basicAuthPassword := c.basicAuthPassword
c.mu.RUnlock()
c.connsMu.RLock()
conns := c.conns
c.connsMu.RUnlock()
for _, conn := range conns {
// Run the HEAD request against ES with a timeout
ctx, cancel := context.WithTimeout(parentCtx, timeout)
defer cancel()
// Goroutine executes the HTTP request, returns an error and sets status
var status int
errc := make(chan error, 1)
go func(url string) {
req, err := NewRequest("HEAD", url)
if err != nil {
errc <- err
return
}
if basicAuth {
req.SetBasicAuth(basicAuthUsername, basicAuthPassword)
}
if len(headers) > 0 {
for key, values := range headers {
for _, v := range values {
req.Header.Add(key, v)
}
}
}
if req.Header.Get("User-Agent") == "" {
req.Header.Add("User-Agent", "elastic/"+Version+" ("+runtime.GOOS+"-"+runtime.GOARCH+")")
}
res, err := c.c.Do((*http.Request)(req).WithContext(ctx))
if res != nil {
status = res.StatusCode
if res.Body != nil {
res.Body.Close()
}
}
errc <- err
}(conn.URL())
// Wait for the Goroutine (or its timeout)
select {
case <-ctx.Done(): // timeout
c.errorf("elastic: %s is dead", conn.URL())
conn.MarkAsDead()
case err := <-errc:
if err != nil {
c.errorf("elastic: %s is dead", conn.URL())
conn.MarkAsDead()
break
}
if status >= 200 && status < 300 {
conn.MarkAsAlive()
} else {
conn.MarkAsDead()
c.errorf("elastic: %s is dead [status=%d]", conn.URL(), status)
}
}
}
}
// startupHealthcheck is used at startup to check if the server is available
// at all.
func (c *Client) startupHealthcheck(parentCtx context.Context, timeout time.Duration) error {
c.mu.Lock()
urls := c.urls
headers := c.headers
basicAuth := c.basicAuthUsername != "" || c.basicAuthPassword != ""
basicAuthUsername := c.basicAuthUsername
basicAuthPassword := c.basicAuthPassword
c.mu.Unlock()
// If we don't get a connection after "timeout", we bail.
var lastErr error
start := time.Now()
done := false
for !done {
for _, url := range urls {
req, err := http.NewRequest("HEAD", url, nil)
if err != nil {
return err
}
if basicAuth {
req.SetBasicAuth(basicAuthUsername, basicAuthPassword)
}
if len(headers) > 0 {
for key, values := range headers {
for _, v := range values {
req.Header.Add(key, v)
}
}
}
ctx, cancel := context.WithTimeout(parentCtx, timeout)
defer cancel()
req = req.WithContext(ctx)
res, err := c.c.Do(req)
if err != nil {
lastErr = err
} else if res.StatusCode >= 200 && res.StatusCode < 300 {
return nil
} else if res.StatusCode == http.StatusUnauthorized {
lastErr = &Error{Status: res.StatusCode}
}
}
select {
case <-parentCtx.Done():
lastErr = parentCtx.Err()
done = true
case <-time.After(1 * time.Second):
if time.Since(start) > timeout {
done = true
}
}
}
if lastErr != nil {
if IsContextErr(lastErr) || IsUnauthorized(lastErr) {
return lastErr
}
return errors.Wrapf(ErrNoClient, "health check timeout: %v", lastErr)
}
return errors.Wrap(ErrNoClient, "health check timeout")
}
// next returns the next available connection, or ErrNoClient.
func (c *Client) next() (*conn, error) {
// We do round-robin here.
// TODO(oe) This should be a pluggable strategy, like the Selector in the official clients.
c.connsMu.Lock()
defer c.connsMu.Unlock()
i := 0
numConns := len(c.conns)
for {
i++
if i > numConns {
break // we visited all conns: they all seem to be dead
}
c.cindex++
if c.cindex >= numConns {
c.cindex = 0
}
conn := c.conns[c.cindex]
if !conn.IsDead() {
return conn, nil
}
}
// We have a deadlock here: All nodes are marked as dead.
// If sniffing is disabled, connections will never be marked alive again.
// So we are marking them as alive--if sniffing is disabled.
// They'll then be picked up in the next call to PerformRequest.
if !c.snifferEnabled {
c.errorf("elastic: all %d nodes marked as dead; resurrecting them to prevent deadlock", len(c.conns))
for _, conn := range c.conns {
conn.MarkAsAlive()
}
}
// We tried hard, but there is no node available
return nil, errors.Wrap(ErrNoClient, "no available connection")
}
// mustActiveConn returns nil if there is an active connection,
// otherwise ErrNoClient is returned.
func (c *Client) mustActiveConn() error {
c.connsMu.Lock()
defer c.connsMu.Unlock()
for _, c := range c.conns {
if !c.IsDead() {
return nil
}
}
return errors.Wrap(ErrNoClient, "no active connection found")
}
// -- PerformRequest --
// PerformRequestOptions must be passed into PerformRequest.
type PerformRequestOptions struct {
Method string
Path string
Params url.Values
Body interface{}
ContentType string
IgnoreErrors []int
Retrier Retrier
RetryStatusCodes []int
Headers http.Header
MaxResponseSize int64
Stream bool
}
// PerformRequest does a HTTP request to Elasticsearch.
// It returns a response (which might be nil) and an error on failure.
//
// Optionally, a list of HTTP error codes to ignore can be passed.
// This is necessary for services that expect e.g. HTTP status 404 as a
// valid outcome (Exists, IndicesExists, IndicesTypeExists).
//
// If Stream is set, the returned BodyReader field must be closed, even
// if PerformRequest returns an error.
func (c *Client) PerformRequest(ctx context.Context, opt PerformRequestOptions) (*Response, error) {
start := time.Now().UTC()
c.mu.RLock()
timeout := c.healthcheckTimeout
basicAuth := c.basicAuthUsername != "" || c.basicAuthPassword != ""
basicAuthUsername := c.basicAuthUsername
basicAuthPassword := c.basicAuthPassword
sendGetBodyAs := c.sendGetBodyAs
gzipEnabled := c.gzipEnabled
healthcheckEnabled := c.healthcheckEnabled
retrier := c.retrier
if opt.Retrier != nil {
retrier = opt.Retrier
}
retryStatusCodes := c.retryStatusCodes
if opt.RetryStatusCodes != nil {
retryStatusCodes = opt.RetryStatusCodes
}
defaultHeaders := c.headers
c.mu.RUnlock()
// retry returns true if statusCode indicates the request is to be retried
retry := func(statusCode int) bool {
for _, code := range retryStatusCodes {
if code == statusCode {
return true
}
}
return false
}
var err error
var conn *conn
var req *Request
var resp *Response
var retried bool
var n int
// Change method if sendGetBodyAs is specified.
if opt.Method == "GET" && opt.Body != nil && sendGetBodyAs != "GET" {
opt.Method = sendGetBodyAs
}
for {
pathWithParams := opt.Path
if len(opt.Params) > 0 {
pathWithParams += "?" + opt.Params.Encode()
}
// Get a connection
conn, err = c.next()
if errors.Cause(err) == ErrNoClient {
n++
if !retried {
// Force a healtcheck as all connections seem to be dead.
c.healthcheck(ctx, timeout, false)
if healthcheckEnabled {
retried = true
continue
}
}
wait, ok, rerr := retrier.Retry(ctx, n, nil, nil, err)
if rerr != nil {
return nil, rerr
}
if !ok {
return nil, err
}
retried = true
time.Sleep(wait)
continue // try again
}
if err != nil {
c.errorf("elastic: cannot get connection from pool")
return nil, err
}
req, err = NewRequest(opt.Method, conn.URL()+pathWithParams)
if err != nil {
c.errorf("elastic: cannot create request for %s %s: %v", strings.ToUpper(opt.Method), conn.URL()+pathWithParams, err)
return nil, err
}
if basicAuth {
req.SetBasicAuth(basicAuthUsername, basicAuthPassword)
}
if opt.ContentType != "" {
req.Header.Set("Content-Type", opt.ContentType)
}
for key, value := range opt.Headers {
for _, v := range value {
req.Header.Add(key, v)
}
}
if len(defaultHeaders) > 0 {
for key, value := range defaultHeaders {
for _, v := range value {
req.Header.Add(key, v)
}
}
}
if req.Header.Get("User-Agent") == "" {
req.Header.Set("User-Agent", "elastic/"+Version+" ("+runtime.GOOS+"-"+runtime.GOARCH+")")
}
// Set body
if opt.Body != nil {
err = req.SetBody(opt.Body, gzipEnabled)
if err != nil {
c.errorf("elastic: couldn't set body %+v for request: %v", opt.Body, err)
return nil, err
}
}
// Tracing
c.dumpRequest((*http.Request)(req))
// Get response
res, err := c.c.Do((*http.Request)(req).WithContext(ctx))
if IsContextErr(err) {
// Proceed, but don't mark the node as dead
return nil, err
}
if err != nil {
n++
wait, ok, rerr := retrier.Retry(ctx, n, (*http.Request)(req), res, err)
if rerr != nil {
c.errorf("elastic: %s is dead", conn.URL())
conn.MarkAsDead()
return nil, rerr
}
if !ok {
c.errorf("elastic: %s is dead", conn.URL())
conn.MarkAsDead()
return nil, err
}
retried = true
time.Sleep(wait)
continue // try again
}
if retry(res.StatusCode) {
n++
wait, ok, rerr := retrier.Retry(ctx, n, (*http.Request)(req), res, err)
if rerr != nil {
c.errorf("elastic: %s is dead", conn.URL())
conn.MarkAsDead()
return nil, rerr
}
if ok {
// retry
retried = true
time.Sleep(wait)
continue // try again
}
}
if !opt.Stream {
defer res.Body.Close()
}
// Tracing
c.dumpResponse(res)
// Log deprecation warnings as errors
if len(res.Header["Warning"]) > 0 {
c.deprecationlog((*http.Request)(req), res)
for _, warning := range res.Header["Warning"] {
c.errorf("Deprecation warning: %s", warning)
}
}
// Check for errors
if err := checkResponse((*http.Request)(req), res, opt.IgnoreErrors...); err != nil {
// No retry if request succeeded
// We still try to return a response.
resp, _ = c.newResponse(res, opt.MaxResponseSize, opt.Stream)
return resp, err
}
// We successfully made a request with this connection
conn.MarkAsHealthy()
resp, err = c.newResponse(res, opt.MaxResponseSize, opt.Stream)
if err != nil {
return nil, err
}
break
}
duration := time.Now().UTC().Sub(start)
c.infof("%s %s [status:%d, request:%.3fs]",
strings.ToUpper(opt.Method),
req.URL.Redacted(),
resp.StatusCode,
float64(int64(duration/time.Millisecond))/1000)
return resp, nil
}
// -- Document APIs --
// Index a document.
func (c *Client) Index() *IndexService {
return NewIndexService(c)
}
// Get a document.
func (c *Client) Get() *GetService {
return NewGetService(c)
}
// MultiGet retrieves multiple documents in one roundtrip.
func (c *Client) MultiGet() *MgetService {
return NewMgetService(c)
}
// Mget retrieves multiple documents in one roundtrip.
func (c *Client) Mget() *MgetService {
return NewMgetService(c)
}
// Delete a document.
func (c *Client) Delete() *DeleteService {
return NewDeleteService(c)
}
// DeleteByQuery deletes documents as found by a query.
func (c *Client) DeleteByQuery(indices ...string) *DeleteByQueryService {
return NewDeleteByQueryService(c).Index(indices...)
}
// Update a document.
func (c *Client) Update() *UpdateService {
return NewUpdateService(c)
}
// UpdateByQuery performs an update on a set of documents.
func (c *Client) UpdateByQuery(indices ...string) *UpdateByQueryService {
return NewUpdateByQueryService(c).Index(indices...)
}
// Bulk is the entry point to mass insert/update/delete documents.
func (c *Client) Bulk() *BulkService {
return NewBulkService(c)
}
// BulkProcessor allows setting up a concurrent processor of bulk requests.
func (c *Client) BulkProcessor() *BulkProcessorService {
return NewBulkProcessorService(c)
}
// Reindex copies data from a source index into a destination index.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-reindex.html
// for details on the Reindex API.
func (c *Client) Reindex() *ReindexService {
return NewReindexService(c)
}
// TermVectors returns information and statistics on terms in the fields
// of a particular document.
func (c *Client) TermVectors(index string) *TermvectorsService {
builder := NewTermvectorsService(c)
builder = builder.Index(index)
return builder
}
// MultiTermVectors returns information and statistics on terms in the fields
// of multiple documents.
func (c *Client) MultiTermVectors() *MultiTermvectorService {
return NewMultiTermvectorService(c)
}
// -- Search APIs --
// Search is the entry point for searches.
func (c *Client) Search(indices ...string) *SearchService {
return NewSearchService(c).Index(indices...)
}
// MultiSearch is the entry point for multi searches.
func (c *Client) MultiSearch() *MultiSearchService {
return NewMultiSearchService(c)
}
// Count documents.
func (c *Client) Count(indices ...string) *CountService {
return NewCountService(c).Index(indices...)
}
// Explain computes a score explanation for a query and a specific document.
func (c *Client) Explain(index, typ, id string) *ExplainService {
return NewExplainService(c).Index(index).Type(typ).Id(id)
}
// TODO Search Template
// TODO Search Exists API
// Validate allows a user to validate a potentially expensive query without executing it.
func (c *Client) Validate(indices ...string) *ValidateService {
return NewValidateService(c).Index(indices...)
}
// SearchShards returns statistical information about nodes and shards.
func (c *Client) SearchShards(indices ...string) *SearchShardsService {
return NewSearchShardsService(c).Index(indices...)
}
// FieldCaps returns statistical information about fields in indices.
func (c *Client) FieldCaps(indices ...string) *FieldCapsService {
return NewFieldCapsService(c).Index(indices...)
}
// Exists checks if a document exists.
func (c *Client) Exists() *ExistsService {
return NewExistsService(c)
}
// Scroll through documents. Use this to efficiently scroll through results
// while returning the results to a client.
func (c *Client) Scroll(indices ...string) *ScrollService {
return NewScrollService(c).Index(indices...)
}
// ClearScroll can be used to clear search contexts manually.
func (c *Client) ClearScroll(scrollIds ...string) *ClearScrollService {
return NewClearScrollService(c).ScrollId(scrollIds...)
}
// OpenPointInTime opens a new Point in Time.
func (c *Client) OpenPointInTime(indices ...string) *OpenPointInTimeService {
return NewOpenPointInTimeService(c).Index(indices...)
}
// ClosePointInTime closes an existing Point in Time.
func (c *Client) ClosePointInTime(id string) *ClosePointInTimeService {
return NewClosePointInTimeService(c).ID(id)
}
// -- Indices APIs --
// CreateIndex returns a service to create a new index.
func (c *Client) CreateIndex(name string) *IndicesCreateService {
return NewIndicesCreateService(c).Index(name)
}
// DeleteIndex returns a service to delete an index.
func (c *Client) DeleteIndex(indices ...string) *IndicesDeleteService {
return NewIndicesDeleteService(c).Index(indices)
}
// IndexExists allows to check if an index exists.
func (c *Client) IndexExists(indices ...string) *IndicesExistsService {
return NewIndicesExistsService(c).Index(indices)
}
// ShrinkIndex returns a service to shrink one index into another.
func (c *Client) ShrinkIndex(source, target string) *IndicesShrinkService {
return NewIndicesShrinkService(c).Source(source).Target(target)
}
// RolloverIndex rolls an alias over to a new index when the existing index
// is considered to be too large or too old.
func (c *Client) RolloverIndex(alias string) *IndicesRolloverService {
return NewIndicesRolloverService(c).Alias(alias)
}
// IndexStats provides statistics on different operations happining
// in one or more indices.
func (c *Client) IndexStats(indices ...string) *IndicesStatsService {
return NewIndicesStatsService(c).Index(indices...)
}
// OpenIndex opens an index.
func (c *Client) OpenIndex(name string) *IndicesOpenService {
return NewIndicesOpenService(c).Index(name)
}
// CloseIndex closes an index.
func (c *Client) CloseIndex(name string) *IndicesCloseService {
return NewIndicesCloseService(c).Index(name)
}
// FreezeIndex freezes an index.
//
// Deprecated: Frozen indices are deprecated because they provide no benefit
// given improvements in heap memory utilization.
func (c *Client) FreezeIndex(name string) *IndicesFreezeService {
return NewIndicesFreezeService(c).Index(name)
}
// UnfreezeIndex unfreezes an index.
//
// Deprecated: Frozen indices are deprecated because they provide no benefit
// given improvements in heap memory utilization.
func (c *Client) UnfreezeIndex(name string) *IndicesUnfreezeService {
return NewIndicesUnfreezeService(c).Index(name)
}
// IndexGet retrieves information about one or more indices.
// IndexGet is only available for Elasticsearch 1.4 or later.
func (c *Client) IndexGet(indices ...string) *IndicesGetService {
return NewIndicesGetService(c).Index(indices...)
}
// IndexGetSettings retrieves settings of all, one or more indices.
func (c *Client) IndexGetSettings(indices ...string) *IndicesGetSettingsService {
return NewIndicesGetSettingsService(c).Index(indices...)
}
// IndexPutSettings sets settings for all, one or more indices.
func (c *Client) IndexPutSettings(indices ...string) *IndicesPutSettingsService {
return NewIndicesPutSettingsService(c).Index(indices...)
}
// IndexSegments retrieves low level segment information for all, one or more indices.
func (c *Client) IndexSegments(indices ...string) *IndicesSegmentsService {
return NewIndicesSegmentsService(c).Index(indices...)
}
// IndexAnalyze performs the analysis process on a text and returns the
// token breakdown of the text.
func (c *Client) IndexAnalyze() *IndicesAnalyzeService {
return NewIndicesAnalyzeService(c)
}
// Forcemerge optimizes one or more indices.
// It replaces the deprecated Optimize API.
func (c *Client) Forcemerge(indices ...string) *IndicesForcemergeService {
return NewIndicesForcemergeService(c).Index(indices...)
}
// Refresh asks Elasticsearch to refresh one or more indices.
func (c *Client) Refresh(indices ...string) *RefreshService {
return NewRefreshService(c).Index(indices...)
}
// Flush asks Elasticsearch to free memory from the index and
// flush data to disk.
func (c *Client) Flush(indices ...string) *IndicesFlushService {
return NewIndicesFlushService(c).Index(indices...)
}
// SyncedFlush performs a synced flush.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/indices-synced-flush.html
// for more details on synched flushes and how they differ from a normal
// Flush.
func (c *Client) SyncedFlush(indices ...string) *IndicesSyncedFlushService {
return NewIndicesSyncedFlushService(c).Index(indices...)
}
// ClearCache clears caches for one or more indices.
func (c *Client) ClearCache(indices ...string) *IndicesClearCacheService {
return NewIndicesClearCacheService(c).Index(indices...)
}
// Alias enables the caller to add and/or remove aliases.
func (c *Client) Alias() *AliasService {
return NewAliasService(c)
}
// Aliases returns aliases by index name(s).
func (c *Client) Aliases() *AliasesService {
return NewAliasesService(c)
}
// -- Legacy templates --
// IndexGetTemplate gets an index template (v1/legacy version before 7.8).
//
// This service implements the legacy version of index templates as described
// in https://www.elastic.co/guide/en/elasticsearch/reference/7.9/indices-templates-v1.html.
//
// See e.g. IndexPutIndexTemplate and IndexPutComponentTemplate for the new version(s).
//
// Deprecated: Legacy index templates are deprecated in favor of composable templates.
func (c *Client) IndexGetTemplate(names ...string) *IndicesGetTemplateService {
return NewIndicesGetTemplateService(c).Name(names...)
}
// IndexTemplateExists gets check if an index template exists (v1/legacy version before 7.8).
//
// This service implements the legacy version of index templates as described
// in https://www.elastic.co/guide/en/elasticsearch/reference/7.9/indices-templates-v1.html.
//
// See e.g. IndexPutIndexTemplate and IndexPutComponentTemplate for the new version(s).
//
// Deprecated: Legacy index templates are deprecated in favor of composable templates.
func (c *Client) IndexTemplateExists(name string) *IndicesExistsTemplateService {
return NewIndicesExistsTemplateService(c).Name(name)
}
// IndexPutTemplate creates or updates an index template (v1/legacy version before 7.8).
//
// This service implements the legacy version of index templates as described
// in https://www.elastic.co/guide/en/elasticsearch/reference/7.9/indices-templates-v1.html.
//
// See e.g. IndexPutIndexTemplate and IndexPutComponentTemplate for the new version(s).
//
// Deprecated: Legacy index templates are deprecated in favor of composable templates.
func (c *Client) IndexPutTemplate(name string) *IndicesPutTemplateService {
return NewIndicesPutTemplateService(c).Name(name)
}
// IndexDeleteTemplate deletes an index template (v1/legacy version before 7.8).
//
// This service implements the legacy version of index templates as described
// in https://www.elastic.co/guide/en/elasticsearch/reference/7.9/indices-templates-v1.html.
//
// See e.g. IndexPutIndexTemplate and IndexPutComponentTemplate for the new version(s).
//
// Deprecated: Legacy index templates are deprecated in favor of composable templates.
func (c *Client) IndexDeleteTemplate(name string) *IndicesDeleteTemplateService {
return NewIndicesDeleteTemplateService(c).Name(name)
}
// -- Index templates --
// IndexPutIndexTemplate creates or updates an index template (new version after 7.8).
//
// This service implements the new version of index templates as described
// on https://www.elastic.co/guide/en/elasticsearch/reference/7.9/indices-put-template.html.
//
// See e.g. IndexPutTemplate for the v1/legacy version.
func (c *Client) IndexPutIndexTemplate(name string) *IndicesPutIndexTemplateService {
return NewIndicesPutIndexTemplateService(c).Name(name)
}
// IndexGetIndexTemplate returns an index template (new version after 7.8).
//
// This service implements the new version of index templates as described
// on https://www.elastic.co/guide/en/elasticsearch/reference/7.9/indices-get-template.html.
//
// See e.g. IndexPutTemplate for the v1/legacy version.
func (c *Client) IndexGetIndexTemplate(name string) *IndicesGetIndexTemplateService {
return NewIndicesGetIndexTemplateService(c).Name(name)
}
// IndexDeleteIndexTemplate deletes an index template (new version after 7.8).
//
// This service implements the new version of index templates as described
// on https://www.elastic.co/guide/en/elasticsearch/reference/7.9/indices-delete-template.html.
//
// See e.g. IndexPutTemplate for the v1/legacy version.
func (c *Client) IndexDeleteIndexTemplate(name string) *IndicesDeleteIndexTemplateService {
return NewIndicesDeleteIndexTemplateService(c).Name(name)
}
// -- Component templates --
// IndexPutComponentTemplate creates or updates a component template (available since 7.8).
//
// This service implements the component templates as described
// on https://www.elastic.co/guide/en/elasticsearch/reference/7.10/indices-component-template.html.
func (c *Client) IndexPutComponentTemplate(name string) *IndicesPutComponentTemplateService {
return NewIndicesPutComponentTemplateService(c).Name(name)
}
// IndexGetComponentTemplate returns a component template (available since 7.8).
//
// This service implements the component templates as described
// on https://www.elastic.co/guide/en/elasticsearch/reference/7.10/getting-component-templates.html.
func (c *Client) IndexGetComponentTemplate(name string) *IndicesGetComponentTemplateService {
return NewIndicesGetComponentTemplateService(c).Name(name)
}
// IndexDeleteComponentTemplate deletes a component template (available since 7.8).
//
// This service implements the component templates as described
// on https://www.elastic.co/guide/en/elasticsearch/reference/7.10/indices-delete-component-template.html.
func (c *Client) IndexDeleteComponentTemplate(name string) *IndicesDeleteComponentTemplateService {
return NewIndicesDeleteComponentTemplateService(c).Name(name)
}
// GetMapping gets a mapping.
func (c *Client) GetMapping() *IndicesGetMappingService {
return NewIndicesGetMappingService(c)
}
// PutMapping registers a mapping.
func (c *Client) PutMapping() *IndicesPutMappingService {
return NewIndicesPutMappingService(c)
}
// GetFieldMapping gets mapping for fields.
func (c *Client) GetFieldMapping() *IndicesGetFieldMappingService {
return NewIndicesGetFieldMappingService(c)
}
// -- cat APIs --
// TODO cat nodes
// TODO cat pending tasks
// TODO cat plugins
// TODO cat recovery
// TODO cat thread pool
// TODO cat shards
// TODO cat segments
// CatMaster returns information about the master node
func (c *Client) CatMaster() *CatMasterService {
return NewCatMasterService(c)
}
// CatFielddata returns information about the amount of heap memory currently used by the field data cache.
func (c *Client) CatFielddata() *CatFielddataService {
return NewCatFielddataService(c)
}
// CatAliases returns information about aliases.
func (c *Client) CatAliases() *CatAliasesService {
return NewCatAliasesService(c)
}
// CatAllocation returns information about the allocation across nodes.
func (c *Client) CatAllocation() *CatAllocationService {
return NewCatAllocationService(c)
}
// CatCount returns document counts for indices.
func (c *Client) CatCount() *CatCountService {
return NewCatCountService(c)
}
// CatHealth returns information about cluster health.
func (c *Client) CatHealth() *CatHealthService {
return NewCatHealthService(c)
}
// CatIndices returns information about indices.
func (c *Client) CatIndices() *CatIndicesService {
return NewCatIndicesService(c)
}
// CatShards returns information about shards.
func (c *Client) CatShards() *CatShardsService {
return NewCatShardsService(c)
}
// CatSnapshots returns information about snapshots.
func (c *Client) CatSnapshots() *CatSnapshotsService {
return NewCatSnapshotsService(c)
}
// -- Ingest APIs --
// IngestPutPipeline adds pipelines and updates existing pipelines in
// the cluster.
func (c *Client) IngestPutPipeline(id string) *IngestPutPipelineService {
return NewIngestPutPipelineService(c).Id(id)
}
// IngestGetPipeline returns pipelines based on ID.
func (c *Client) IngestGetPipeline(ids ...string) *IngestGetPipelineService {
return NewIngestGetPipelineService(c).Id(ids...)
}
// IngestDeletePipeline deletes a pipeline by ID.
func (c *Client) IngestDeletePipeline(id string) *IngestDeletePipelineService {
return NewIngestDeletePipelineService(c).Id(id)
}
// IngestSimulatePipeline executes a specific pipeline against the set of
// documents provided in the body of the request.
func (c *Client) IngestSimulatePipeline() *IngestSimulatePipelineService {
return NewIngestSimulatePipelineService(c)
}
// -- Cluster APIs --
// ClusterHealth retrieves the health of the cluster.
func (c *Client) ClusterHealth() *ClusterHealthService {
return NewClusterHealthService(c)
}
// ClusterReroute allows for manual changes to the allocation of
// individual shards in the cluster.
func (c *Client) ClusterReroute() *ClusterRerouteService {
return NewClusterRerouteService(c)
}
// ClusterState retrieves the state of the cluster.
func (c *Client) ClusterState() *ClusterStateService {
return NewClusterStateService(c)
}
// ClusterStats retrieves cluster statistics.
func (c *Client) ClusterStats() *ClusterStatsService {
return NewClusterStatsService(c)
}
// NodesInfo retrieves one or more or all of the cluster nodes information.
func (c *Client) NodesInfo() *NodesInfoService {
return NewNodesInfoService(c)
}
// NodesStats retrieves one or more or all of the cluster nodes statistics.
func (c *Client) NodesStats() *NodesStatsService {
return NewNodesStatsService(c)
}
// TasksCancel cancels tasks running on the specified nodes.
func (c *Client) TasksCancel() *TasksCancelService {
return NewTasksCancelService(c)
}
// TasksList retrieves the list of tasks running on the specified nodes.
func (c *Client) TasksList() *TasksListService {
return NewTasksListService(c)
}
// TasksGetTask retrieves a task running on the cluster.
func (c *Client) TasksGetTask() *TasksGetTaskService {
return NewTasksGetTaskService(c)
}
// TODO Pending cluster tasks
// TODO Cluster Reroute
// TODO Cluster Update Settings
// TODO Nodes Stats
// TODO Nodes hot_threads
// -- Snapshot and Restore --
// SnapshotStatus returns information about the status of a snapshot.
func (c *Client) SnapshotStatus() *SnapshotStatusService {
return NewSnapshotStatusService(c)
}
// SnapshotCreate creates a snapshot.
func (c *Client) SnapshotCreate(repository string, snapshot string) *SnapshotCreateService {
return NewSnapshotCreateService(c).Repository(repository).Snapshot(snapshot)
}
// SnapshotCreateRepository creates or updates a snapshot repository.
func (c *Client) SnapshotCreateRepository(repository string) *SnapshotCreateRepositoryService {
return NewSnapshotCreateRepositoryService(c).Repository(repository)
}
// SnapshotDelete deletes a snapshot in a snapshot repository.
func (c *Client) SnapshotDelete(repository string, snapshot string) *SnapshotDeleteService {
return NewSnapshotDeleteService(c).Repository(repository).Snapshot(snapshot)
}
// SnapshotDeleteRepository deletes a snapshot repository.
func (c *Client) SnapshotDeleteRepository(repositories ...string) *SnapshotDeleteRepositoryService {
return NewSnapshotDeleteRepositoryService(c).Repository(repositories...)
}
// SnapshotGetRepository gets a snapshot repository.
func (c *Client) SnapshotGetRepository(repositories ...string) *SnapshotGetRepositoryService {
return NewSnapshotGetRepositoryService(c).Repository(repositories...)
}
// SnapshotGet lists snapshot for a repository.
func (c *Client) SnapshotGet(repository string) *SnapshotGetService {
return NewSnapshotGetService(c).Repository(repository)
}
// SnapshotVerifyRepository verifies a snapshot repository.
func (c *Client) SnapshotVerifyRepository(repository string) *SnapshotVerifyRepositoryService {
return NewSnapshotVerifyRepositoryService(c).Repository(repository)
}
// SnapshotRestore restores the specified indices from a given snapshot
func (c *Client) SnapshotRestore(repository string, snapshot string) *SnapshotRestoreService {
return NewSnapshotRestoreService(c).Repository(repository).Snapshot(snapshot)
}
// -- Scripting APIs --
// GetScript reads a stored script in Elasticsearch.
// Use PutScript for storing a script.
func (c *Client) GetScript() *GetScriptService {
return NewGetScriptService(c)
}
// PutScript allows saving a stored script in Elasticsearch.
func (c *Client) PutScript() *PutScriptService {
return NewPutScriptService(c)
}
// DeleteScript allows removing a stored script from Elasticsearch.
func (c *Client) DeleteScript() *DeleteScriptService {
return NewDeleteScriptService(c)
}
// -- X-Pack General --
// XPackInfo gets information on the xpack plugins enabled on the cluster
func (c *Client) XPackInfo() *XPackInfoService {
return NewXPackInfoService(c)
}
// -- X-Pack Async Search --
// XPackAsyncSearchSubmit starts an asynchronous search.
func (c *Client) XPackAsyncSearchSubmit() *XPackAsyncSearchSubmit {
return NewXPackAsyncSearchSubmit(c)
}
// XPackAsyncSearchGet retrieves the outcome of an asynchronous search.
func (c *Client) XPackAsyncSearchGet() *XPackAsyncSearchGet {
return NewXPackAsyncSearchGet(c)
}
// XPackAsyncSearchDelete deletes an asynchronous search.
func (c *Client) XPackAsyncSearchDelete() *XPackAsyncSearchDelete {
return NewXPackAsyncSearchDelete(c)
}
// -- X-Pack Index Lifecycle Management --
// XPackIlmPutLifecycle adds or modifies an ilm policy.
func (c *Client) XPackIlmPutLifecycle() *XPackIlmPutLifecycleService {
return NewXPackIlmPutLifecycleService(c)
}
// XPackIlmGettLifecycle gets an ilm policy.
func (c *Client) XPackIlmGetLifecycle() *XPackIlmGetLifecycleService {
return NewXPackIlmGetLifecycleService(c)
}
// XPackIlmDeleteLifecycle deletes an ilm policy.
func (c *Client) XPackIlmDeleteLifecycle() *XPackIlmDeleteLifecycleService {
return NewXPackIlmDeleteLifecycleService(c)
}
// -- X-Pack Security --
// XPackSecurityGetRoleMapping gets a role mapping.
func (c *Client) XPackSecurityGetRoleMapping(roleMappingName string) *XPackSecurityGetRoleMappingService {
return NewXPackSecurityGetRoleMappingService(c).Name(roleMappingName)
}
// XPackSecurityPutRoleMapping adds a role mapping.
func (c *Client) XPackSecurityPutRoleMapping(roleMappingName string) *XPackSecurityPutRoleMappingService {
return NewXPackSecurityPutRoleMappingService(c).Name(roleMappingName)
}
// XPackSecurityDeleteRoleMapping deletes a role mapping.
func (c *Client) XPackSecurityDeleteRoleMapping(roleMappingName string) *XPackSecurityDeleteRoleMappingService {
return NewXPackSecurityDeleteRoleMappingService(c).Name(roleMappingName)
}
// XPackSecurityGetRole gets a role.
func (c *Client) XPackSecurityGetRole(roleName string) *XPackSecurityGetRoleService {
return NewXPackSecurityGetRoleService(c).Name(roleName)
}
// XPackSecurityPutRole adds a role.
func (c *Client) XPackSecurityPutRole(roleName string) *XPackSecurityPutRoleService {
return NewXPackSecurityPutRoleService(c).Name(roleName)
}
// XPackSecurityDeleteRole deletes a role.
func (c *Client) XPackSecurityDeleteRole(roleName string) *XPackSecurityDeleteRoleService {
return NewXPackSecurityDeleteRoleService(c).Name(roleName)
}
// TODO: Clear role cache API
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/security-api-clear-role-cache.html
// XPackSecurityChangePassword changes the password of users in the native realm.
func (c *Client) XPackSecurityChangePassword(username string) *XPackSecurityChangePasswordService {
return NewXPackSecurityChangePasswordService(c).Username(username)
}
// XPackSecurityGetUser gets details about one or more users.
func (c *Client) XPackSecurityGetUser(usernames ...string) *XPackSecurityGetUserService {
return NewXPackSecurityGetUserService(c).Usernames(usernames...)
}
// XPackSecurityPutUser adds or updates a user.
func (c *Client) XPackSecurityPutUser(username string) *XPackSecurityPutUserService {
return NewXPackSecurityPutUserService(c).Username(username)
}
// XPackSecurityEnableUser enables a user.
func (c *Client) XPackSecurityEnableUser(username string) *XPackSecurityEnableUserService {
return NewXPackSecurityEnableUserService(c).Username(username)
}
// XPackSecurityDisableUser disables a user.
func (c *Client) XPackSecurityDisableUser(username string) *XPackSecurityDisableUserService {
return NewXPackSecurityDisableUserService(c).Username(username)
}
// XPackSecurityDeleteUser deletes a user.
func (c *Client) XPackSecurityDeleteUser(username string) *XPackSecurityDeleteUserService {
return NewXPackSecurityDeleteUserService(c).Username(username)
}
// -- X-Pack Rollup --
// XPackRollupPut creates or updates a rollup job.
func (c *Client) XPackRollupPut(jobId string) *XPackRollupPutService {
return NewXPackRollupPutService(c).JobId(jobId)
}
// XPackRollupGet gets a rollup job.
func (c *Client) XPackRollupGet(jobId string) *XPackRollupGetService {
return NewXPackRollupGetService(c).JobId(jobId)
}
// XPackRollupDelete deletes a rollup job.
func (c *Client) XPackRollupDelete(jobId string) *XPackRollupDeleteService {
return NewXPackRollupDeleteService(c).JobId(jobId)
}
// XPackRollupStart starts a rollup job.
func (c *Client) XPackRollupStart(jobId string) *XPackRollupStartService {
return NewXPackRollupStartService(c).JobId(jobId)
}
// XPackRollupStop stops a rollup job.
func (c *Client) XPackRollupStop(jobId string) *XPackRollupStopService {
return NewXPackRollupStopService(c).JobId(jobId)
}
// -- X-Pack Watcher --
// XPackWatchPut adds a watch.
func (c *Client) XPackWatchPut(watchId string) *XPackWatcherPutWatchService {
return NewXPackWatcherPutWatchService(c).Id(watchId)
}
// XPackWatchGet gets a watch.
func (c *Client) XPackWatchGet(watchId string) *XPackWatcherGetWatchService {
return NewXPackWatcherGetWatchService(c).Id(watchId)
}
// XPackWatchDelete deletes a watch.
func (c *Client) XPackWatchDelete(watchId string) *XPackWatcherDeleteWatchService {
return NewXPackWatcherDeleteWatchService(c).Id(watchId)
}
// XPackWatchExecute executes a watch.
func (c *Client) XPackWatchExecute() *XPackWatcherExecuteWatchService {
return NewXPackWatcherExecuteWatchService(c)
}
// XPackWatchAck acknowledging a watch.
func (c *Client) XPackWatchAck(watchId string) *XPackWatcherAckWatchService {
return NewXPackWatcherAckWatchService(c).WatchId(watchId)
}
// XPackWatchActivate activates a watch.
func (c *Client) XPackWatchActivate(watchId string) *XPackWatcherActivateWatchService {
return NewXPackWatcherActivateWatchService(c).WatchId(watchId)
}
// XPackWatchDeactivate deactivates a watch.
func (c *Client) XPackWatchDeactivate(watchId string) *XPackWatcherDeactivateWatchService {
return NewXPackWatcherDeactivateWatchService(c).WatchId(watchId)
}
// XPackWatchStats returns the current Watcher metrics.
func (c *Client) XPackWatchStats() *XPackWatcherStatsService {
return NewXPackWatcherStatsService(c)
}
// XPackWatchStart starts a watch.
func (c *Client) XPackWatchStart() *XPackWatcherStartService {
return NewXPackWatcherStartService(c)
}
// XPackWatchStop stops a watch.
func (c *Client) XPackWatchStop() *XPackWatcherStopService {
return NewXPackWatcherStopService(c)
}
// -- Helpers and shortcuts --
// ElasticsearchVersion returns the version number of Elasticsearch
// running on the given URL.
func (c *Client) ElasticsearchVersion(url string) (string, error) {
res, _, err := c.Ping(url).Do(context.Background())
if err != nil {
return "", err
}
return res.Version.Number, nil
}
// IndexNames returns the names of all indices in the cluster.
func (c *Client) IndexNames() ([]string, error) {
res, err := c.IndexGetSettings().Index("_all").Do(context.Background())
if err != nil {
return nil, err
}
var names []string
for name := range res {
names = append(names, name)
}
return names, nil
}
// Ping checks if a given node in a cluster exists and (optionally)
// returns some basic information about the Elasticsearch server,
// e.g. the Elasticsearch version number.
//
// Notice that you need to specify a URL here explicitly.
func (c *Client) Ping(url string) *PingService {
return NewPingService(c).URL(url)
}
// WaitForStatus waits for the cluster to have the given status.
// This is a shortcut method for the ClusterHealth service.
//
// WaitForStatus waits for the specified timeout, e.g. "10s".
// If the cluster will have the given state within the timeout, nil is returned.
// If the request timed out, ErrTimeout is returned.
func (c *Client) WaitForStatus(status string, timeout string) error {
health, err := c.ClusterHealth().WaitForStatus(status).Timeout(timeout).Do(context.Background())
if err != nil {
return err
}
if health.TimedOut {
return ErrTimeout
}
return nil
}
// WaitForGreenStatus waits for the cluster to have the "green" status.
// See WaitForStatus for more details.
func (c *Client) WaitForGreenStatus(timeout string) error {
return c.WaitForStatus("green", timeout)
}
// WaitForYellowStatus waits for the cluster to have the "yellow" status.
// See WaitForStatus for more details.
func (c *Client) WaitForYellowStatus(timeout string) error {
return c.WaitForStatus("yellow", timeout)
}
================================================
FILE: client_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"log"
"net"
"net/http"
"net/http/httptest"
"net/url"
"reflect"
"regexp"
"runtime"
"strings"
"sync"
"testing"
"time"
"github.com/fortytw2/leaktest"
"github.com/olivere/elastic/v7/config"
)
func findConn(s string, slice ...*conn) (int, bool) {
for i, t := range slice {
if s == t.URL() {
return i, true
}
}
return -1, false
}
// -- NewClient --
func TestClientDefaults(t *testing.T) {
client, err := NewClient()
if err != nil {
t.Fatal(err)
}
if client.healthcheckEnabled != true {
t.Errorf("expected health checks to be enabled, got: %v", client.healthcheckEnabled)
}
if client.healthcheckTimeoutStartup != DefaultHealthcheckTimeoutStartup {
t.Errorf("expected health checks timeout on startup = %v, got: %v", DefaultHealthcheckTimeoutStartup, client.healthcheckTimeoutStartup)
}
if client.healthcheckTimeout != DefaultHealthcheckTimeout {
t.Errorf("expected health checks timeout = %v, got: %v", DefaultHealthcheckTimeout, client.healthcheckTimeout)
}
if client.healthcheckInterval != DefaultHealthcheckInterval {
t.Errorf("expected health checks interval = %v, got: %v", DefaultHealthcheckInterval, client.healthcheckInterval)
}
if client.snifferEnabled != true {
t.Errorf("expected sniffing to be enabled, got: %v", client.snifferEnabled)
}
if client.snifferTimeoutStartup != DefaultSnifferTimeoutStartup {
t.Errorf("expected sniffer timeout on startup = %v, got: %v", DefaultSnifferTimeoutStartup, client.snifferTimeoutStartup)
}
if client.snifferTimeout != DefaultSnifferTimeout {
t.Errorf("expected sniffer timeout = %v, got: %v", DefaultSnifferTimeout, client.snifferTimeout)
}
if client.snifferInterval != DefaultSnifferInterval {
t.Errorf("expected sniffer interval = %v, got: %v", DefaultSnifferInterval, client.snifferInterval)
}
if client.basicAuthUsername != "" {
t.Errorf("expected no basic auth username; got: %q", client.basicAuthUsername)
}
if client.basicAuthPassword != "" {
t.Errorf("expected no basic auth password; got: %q", client.basicAuthUsername)
}
if client.sendGetBodyAs != "GET" {
t.Errorf("expected sendGetBodyAs to be GET; got: %q", client.sendGetBodyAs)
}
}
func TestClientWithoutURL(t *testing.T) {
client, err := NewClient()
if err != nil {
t.Fatal(err)
}
// Two things should happen here:
// 1. The client starts sniffing the cluster on DefaultURL
// 2. The sniffing process should find (at least) one node in the cluster, i.e. the DefaultURL
if len(client.conns) == 0 {
t.Fatalf("expected at least 1 node in the cluster, got: %d (%v)", len(client.conns), client.conns)
}
if !isCI() {
if _, found := findConn(DefaultURL, client.conns...); !found {
t.Errorf("expected to find node with default URL of %s in %v", DefaultURL, client.conns)
}
}
}
func TestClientWithSingleURL(t *testing.T) {
client, err := NewClient(SetURL("http://127.0.0.1:9200"))
if err != nil {
t.Fatal(err)
}
// Two things should happen here:
// 1. The client starts sniffing the cluster on DefaultURL
// 2. The sniffing process should find (at least) one node in the cluster, i.e. the DefaultURL
if len(client.conns) == 0 {
t.Fatalf("expected at least 1 node in the cluster, got: %d (%v)", len(client.conns), client.conns)
}
if !isCI() {
if _, found := findConn(DefaultURL, client.conns...); !found {
t.Errorf("expected to find node with default URL of %s in %v", DefaultURL, client.conns)
}
}
}
func TestClientWithMultipleURLs(t *testing.T) {
client, err := NewClient(SetURL("http://127.0.0.1:9200", "http://127.0.0.1:9201"))
if err != nil {
t.Fatal(err)
}
// The client should sniff both URLs, but only 127.0.0.1:9200 should return nodes.
if len(client.conns) != 1 {
t.Fatalf("expected exactly 1 node in the local cluster, got: %d (%v)", len(client.conns), client.conns)
}
if !isCI() {
if client.conns[0].URL() != DefaultURL {
t.Errorf("expected to find node with default URL of %s in %v", DefaultURL, client.conns)
}
}
}
func TestClientWithInvalidURLs(t *testing.T) {
client, err := NewClient(SetURL(" http://foo.com", "http://[fe80::%31%25en0]:8080/"))
if err == nil {
t.Fatal("expected error, got nil")
}
if want, have := `first path segment in URL cannot contain colon`, err.Error(); !strings.Contains(have, want) {
t.Fatalf("expected error to contain %q, have %q", want, have)
}
if client != nil {
t.Fatal("expected client == nil")
}
}
func TestClientWithBasicAuth(t *testing.T) {
client, err := NewClient(SetBasicAuth("user", "secret"))
if err != nil {
t.Fatal(err)
}
if got, want := client.basicAuthUsername, "user"; got != want {
t.Errorf("expected basic auth username %q; got: %q", want, got)
}
if got, want := client.basicAuthPassword, "secret"; got != want {
t.Errorf("expected basic auth password %q; got: %q", want, got)
}
}
func TestClientWithBasicAuthInUserInfo(t *testing.T) {
client, err := NewClient(SetURL("http://user1:secret1@localhost:9200", "http://user2:secret2@localhost:9200"))
if err != nil {
t.Fatal(err)
}
if got, want := client.basicAuthUsername, "user1"; got != want {
t.Errorf("expected basic auth username %q; got: %q", want, got)
}
if got, want := client.basicAuthPassword, "secret1"; got != want {
t.Errorf("expected basic auth password %q; got: %q", want, got)
}
}
func TestClientWithBasicAuthDuringHealthcheck(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "HEAD" || r.URL.String() != "/" {
t.Fatalf("expected HEAD / request, got %s %s", r.Method, r.URL)
http.Error(w, fmt.Sprintf("expected HEAD / request, got %s %s", r.Method, r.URL), http.StatusBadRequest)
return
}
username, password, ok := r.BasicAuth()
if !ok {
t.Fatal("expected HEAD basic auth")
http.Error(w, "expected HTTP basic auth", http.StatusBadRequest)
return
}
if username != "user" && password != "secret" {
t.Fatalf("invalid HTTP basic auth username %q and password %q", username, password)
http.Error(w, fmt.Sprintf("invalid HTTP basic auth username %q and password %q", username, password), http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusOK)
}))
defer ts.Close()
client, err := NewClient(SetBasicAuth("user", "secret"), SetURL(ts.URL), SetSniff(false))
if err != nil {
t.Fatal(err)
}
if client == nil {
t.Fatal("expected a client")
}
}
func TestClientWithXpackSecurity(t *testing.T) {
// Connect to ES Platinum with X-Pack Security enabled and L: elastic, P: elastic
client, err := NewClient(SetURL("http://elastic:elastic@127.0.0.1:9210"))
if err != nil {
t.Fatal(err)
}
if got, want := client.basicAuthUsername, "elastic"; got != want {
t.Errorf("expected basic auth username %q; got: %q", want, got)
}
if got, want := client.basicAuthPassword, "elastic"; got != want {
t.Errorf("expected basic auth password %q; got: %q", want, got)
}
}
func TestClientWithXpackSecurityUnauthorized(t *testing.T) {
client, err := NewClient(SetURL("http://no-such-user:invalid-password@127.0.0.1:9210"))
if client != nil {
t.Fatal("expected no client")
}
if !IsUnauthorized(err) {
t.Fatalf("expected IsUnauthorized to be true; got err=%+v", err)
}
if !IsStatusCode(err, http.StatusUnauthorized) {
t.Fatalf("expected IsUnauthorized to be true; got err=%+v", err)
}
}
func TestClientWithoutBasicAuthButAuthEnabledInElasticDuringHealthcheck(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "HEAD" || r.URL.String() != "/" {
t.Fatalf("expected HEAD / request, got %s %s", r.Method, r.URL)
http.Error(w, fmt.Sprintf("expected HEAD / request, got %s %s", r.Method, r.URL), http.StatusBadRequest)
return
}
_, _, ok := r.BasicAuth()
if ok {
t.Fatal("unexpected HEAD basic auth")
http.Error(w, "unexpected HTTP basic auth", http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusUnauthorized)
}))
defer ts.Close()
_, err := NewClient(SetURL(ts.URL), SetSniff(false))
if err == nil {
t.Fatal("expected unauthorized error")
}
if !IsUnauthorized(err) {
t.Fatalf("expected IsUnauthorized = %v, got err=%+v", true, err)
}
}
func TestClientFromConfig(t *testing.T) {
cfg, err := config.Parse("http://127.0.0.1:9200")
if err != nil {
t.Fatal(err)
}
client, err := NewClientFromConfig(cfg)
if err != nil {
t.Fatal(err)
}
// Two things should happen here:
// 1. The client starts sniffing the cluster on DefaultURL
// 2. The sniffing process should find (at least) one node in the cluster, i.e. the DefaultURL
if len(client.conns) == 0 {
t.Fatalf("expected at least 1 node in the cluster, got: %d (%v)", len(client.conns), client.conns)
}
if !isCI() {
if _, found := findConn(DefaultURL, client.conns...); !found {
t.Errorf("expected to find node with default URL of %s in %v", DefaultURL, client.conns)
}
}
}
func TestClientDialFromConfig(t *testing.T) {
cfg, err := config.Parse("http://127.0.0.1:9200")
if err != nil {
t.Fatal(err)
}
client, err := DialWithConfig(context.Background(), cfg)
if err != nil {
t.Fatal(err)
}
// Two things should happen here:
// 1. The client starts sniffing the cluster on DefaultURL
// 2. The sniffing process should find (at least) one node in the cluster, i.e. the DefaultURL
if len(client.conns) == 0 {
t.Fatalf("expected at least 1 node in the cluster, got: %d (%v)", len(client.conns), client.conns)
}
if !isCI() {
if _, found := findConn(DefaultURL, client.conns...); !found {
t.Errorf("expected to find node with default URL of %s in %v", DefaultURL, client.conns)
}
}
}
func TestClientDialContext(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
client, err := DialContext(ctx, SetURL("http://localhost:9200"))
if err != nil {
t.Fatalf("expected successful connection, got %v", err)
}
client.Stop()
}
func TestClientDialContextTimeoutFromHealthcheck(t *testing.T) {
start := time.Now().UTC()
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
_, err := DialContext(ctx, SetURL("http://localhost:9299"), SetHealthcheckTimeoutStartup(5*time.Second))
if !IsContextErr(err) {
t.Fatal(err)
}
if time.Since(start) < 3*time.Second {
t.Fatalf("early timeout")
}
if time.Since(start) >= 5*time.Second {
t.Fatalf("timeout probably due to healthcheck, not context cancellation")
}
}
func TestClientDialContextTimeoutFromSniffer(t *testing.T) {
start := time.Now().UTC()
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
_, err := DialContext(ctx, SetURL("http://localhost:9299"), SetHealthcheck(false))
if !IsContextErr(err) {
t.Fatal(err)
}
if time.Since(start) < 3*time.Second {
t.Fatalf("early timeout")
}
if time.Since(start) >= 5*time.Second {
t.Fatalf("timeout probably not caused by context cancellation")
}
}
func TestClientSniffSuccess(t *testing.T) {
client, err := NewClient(SetURL("http://127.0.0.1:19200", "http://127.0.0.1:9200"))
if err != nil {
t.Fatal(err)
}
// The client should sniff both URLs, but only 127.0.0.1:9200 should return nodes.
if len(client.conns) != 1 {
t.Fatalf("expected exactly 1 node in the local cluster, got: %d (%v)", len(client.conns), client.conns)
}
}
func TestClientSniffFailure(t *testing.T) {
_, err := NewClient(SetURL("http://127.0.0.1:19200", "http://127.0.0.1:19201"))
if err == nil {
t.Fatalf("expected cluster to fail with no nodes found")
}
}
func TestClientSnifferCallback(t *testing.T) {
var calls int
cb := func(node *NodesInfoNode) bool {
calls++
return false
}
_, err := NewClient(
SetURL("http://127.0.0.1:19200", "http://127.0.0.1:9200"),
SetSnifferCallback(cb))
if err == nil {
t.Fatalf("expected cluster to fail with no nodes found")
}
if calls != 1 {
t.Fatalf("expected 1 call to the sniffer callback, got %d", calls)
}
}
func TestClientSniffDisabled(t *testing.T) {
client, err := NewClient(SetSniff(false), SetURL("http://127.0.0.1:9200", "http://127.0.0.1:9201"))
if err != nil {
t.Fatal(err)
}
// The client should not sniff, so it should have two connections.
if len(client.conns) != 2 {
t.Fatalf("expected 2 nodes, got: %d (%v)", len(client.conns), client.conns)
}
// Make two requests, so that both connections are being used
for i := 0; i < len(client.conns); i++ {
client.Flush().Do(context.TODO())
}
// The first connection (127.0.0.1:9200) should now be okay.
if i, found := findConn("http://127.0.0.1:9200", client.conns...); !found {
t.Fatalf("expected connection to %q to be found", "http://127.0.0.1:9200")
} else {
if conn := client.conns[i]; conn.IsDead() {
t.Fatal("expected connection to be alive, but it is dead")
}
}
// The second connection (127.0.0.1:9201) should now be marked as dead.
if i, found := findConn("http://127.0.0.1:9201", client.conns...); !found {
t.Fatalf("expected connection to %q to be found", "http://127.0.0.1:9201")
} else {
if conn := client.conns[i]; !conn.IsDead() {
t.Fatal("expected connection to be dead, but it is alive")
}
}
}
func TestClientWillMarkConnectionsAsAliveWhenAllAreDead(t *testing.T) {
client, err := NewClient(SetURL("http://127.0.0.1:9201"),
SetSniff(false), SetHealthcheck(false), SetMaxRetries(0))
if err != nil {
t.Fatal(err)
}
// We should have a connection.
if len(client.conns) != 1 {
t.Fatalf("expected 1 node, got: %d (%v)", len(client.conns), client.conns)
}
// Make a request, so that the connections is marked as dead.
client.Flush().Do(context.TODO())
// The connection should now be marked as dead.
if i, found := findConn("http://127.0.0.1:9201", client.conns...); !found {
t.Fatalf("expected connection to %q to be found", "http://127.0.0.1:9201")
} else {
if conn := client.conns[i]; !conn.IsDead() {
t.Fatalf("expected connection to be dead, got: %v", conn)
}
}
// Now send another request and the connection should be marked as alive again.
client.Flush().Do(context.TODO())
if i, found := findConn("http://127.0.0.1:9201", client.conns...); !found {
t.Fatalf("expected connection to %q to be found", "http://127.0.0.1:9201")
} else {
if conn := client.conns[i]; conn.IsDead() {
t.Fatalf("expected connection to be alive, got: %v", conn)
}
}
}
func TestClientWithRequiredPlugins(t *testing.T) {
_, err := NewClient(SetRequiredPlugins("no-such-plugin"))
if err == nil {
t.Fatal("expected error when creating client")
}
if got, want := err.Error(), "elastic: plugin no-such-plugin not found"; got != want {
t.Fatalf("expected error %q; got: %q", want, got)
}
}
func TestClientHealthcheckStartupTimeout(t *testing.T) {
start := time.Now()
_, err := NewClient(SetURL("http://localhost:9299"), SetHealthcheckTimeoutStartup(5*time.Second))
duration := time.Since(start)
if !IsConnErr(err) {
t.Fatal(err)
}
if !strings.Contains(err.Error(), "connection refused") {
t.Fatalf("expected error to contain %q, have %q", "connection refused", err.Error())
}
if duration < 5*time.Second {
t.Fatalf("expected a timeout in more than 5 seconds; got: %v", duration)
}
}
func TestClientHealthcheckTimeoutLeak(t *testing.T) {
// This test test checks if healthcheck requests are canceled
// after timeout.
// It contains couple of hacks which won't be needed once we
// stop supporting Go1.7.
// On Go1.7 it uses server side effects to monitor if connection
// was closed,
// and on Go 1.8+ we're additionally honestly monitoring routine
// leaks via leaktest.
mux := http.NewServeMux()
var reqDoneMu sync.Mutex
var reqDone bool
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
cn, ok := w.(http.CloseNotifier)
if !ok {
t.Fatalf("Writer is not CloseNotifier, but %v", reflect.TypeOf(w).Name())
}
<-cn.CloseNotify()
reqDoneMu.Lock()
reqDone = true
reqDoneMu.Unlock()
})
lis, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("Couldn't setup listener: %v", err)
}
addr := lis.Addr().String()
srv := &http.Server{
Handler: mux,
}
go srv.Serve(lis)
cli := &Client{
c: &http.Client{},
conns: []*conn{
{
url: "http://" + addr + "/",
},
},
}
type closer interface {
Shutdown(context.Context) error
}
// pre-Go1.8 Server can't Shutdown
cl, isServerCloseable := (interface{}(srv)).(closer)
// Since Go1.7 can't Shutdown() - there will be leak from server
// Monitor leaks on Go 1.8+
if isServerCloseable {
defer leaktest.CheckTimeout(t, time.Second*10)()
}
cli.healthcheck(context.Background(), time.Millisecond*500, true)
if isServerCloseable {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
cl.Shutdown(ctx)
}
<-time.After(time.Second)
reqDoneMu.Lock()
if !reqDone {
reqDoneMu.Unlock()
t.Fatal("Request wasn't canceled or stopped")
}
reqDoneMu.Unlock()
}
func TestClientSniffUpdatingNodeURL(t *testing.T) {
var (
nodeID = "3DWDurZJQvWyWIOFnEB7VA"
nodeURL string
n int
)
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
n++
w.Header().Set("Content-Type", "application/json")
if r.URL.Path != "/_nodes/http" {
w.WriteHeader(http.StatusInternalServerError)
return
}
u, err := url.Parse(nodeURL)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
fmt.Fprintf(w, `{
"cluster_name": "elasticsearch",
"nodes": {
%q: {
"name": "elasticsearch",
"http": {
"publish_address": %q
}
}
}
}`, nodeID, u.Host)
fmt.Fprintln(w)
})
ts := httptest.NewServer(h)
defer ts.Close()
nodeURL = ts.URL
client, err := NewSimpleClient(SetURL(ts.URL), SetSniff(true))
if err != nil {
t.Fatal(err)
}
if want, have := 0, n; want != have {
t.Fatalf("expected %d calls to handler; got %d", want, have)
}
if want, have := 1, len(client.conns); want != have {
t.Fatalf("expected %d connections; got %d", want, have)
}
if want, have := nodeURL, client.conns[0].URL(); want != have {
t.Fatalf("expected URL=%q; got %q", want, have)
}
err = client.sniff(context.Background(), 2*time.Second)
if err != nil {
t.Fatal(err)
}
if want, have := 1, n; want != have {
t.Fatalf("expected %d calls to handler; got %d", want, have)
}
if want, have := 1, len(client.conns); want != have {
t.Fatalf("expected %d connections; got %d", want, have)
}
if want, have := nodeID, client.conns[0].NodeID(); want != have {
t.Fatalf("expected NodeID=%q; got %q", want, have)
}
if want, have := nodeURL, client.conns[0].URL(); want != have {
t.Fatalf("expected URL=%q; got %q", want, have)
}
oldNodeID := client.conns[0].NodeID()
oldURL := client.conns[0].URL()
nodeURL = "http://127.0.0.1:9999" // some other nodeURL to report
err = client.sniff(context.Background(), 2*time.Second)
if err != nil {
t.Fatal(err)
}
if want, have := 2, n; want != have {
t.Fatalf("expected %d calls to handler; got %d", want, have)
}
if want, have := 1, len(client.conns); want != have {
t.Fatalf("expected %d connections; got %d", want, have)
}
newNodeID := client.conns[0].NodeID()
newURL := client.conns[0].URL()
// NodeID mustn't change
if newNodeID != oldNodeID {
t.Fatalf("expected NodeID=%q; got %q", oldNodeID, newNodeID)
}
// URL must have change
if newURL == oldURL {
t.Fatalf("expected to update URL=%q to %q", oldURL, newURL)
}
}
// -- NewSimpleClient --
func TestSimpleClientDefaults(t *testing.T) {
client, err := NewSimpleClient()
if err != nil {
t.Fatal(err)
}
if client.healthcheckEnabled != false {
t.Errorf("expected health checks to be disabled, got: %v", client.healthcheckEnabled)
}
if client.healthcheckTimeoutStartup != off {
t.Errorf("expected health checks timeout on startup = %v, got: %v", off, client.healthcheckTimeoutStartup)
}
if client.healthcheckTimeout != off {
t.Errorf("expected health checks timeout = %v, got: %v", off, client.healthcheckTimeout)
}
if client.healthcheckInterval != off {
t.Errorf("expected health checks interval = %v, got: %v", off, client.healthcheckInterval)
}
if client.snifferEnabled != false {
t.Errorf("expected sniffing to be disabled, got: %v", client.snifferEnabled)
}
if client.snifferTimeoutStartup != off {
t.Errorf("expected sniffer timeout on startup = %v, got: %v", off, client.snifferTimeoutStartup)
}
if client.snifferTimeout != off {
t.Errorf("expected sniffer timeout = %v, got: %v", off, client.snifferTimeout)
}
if client.snifferInterval != off {
t.Errorf("expected sniffer interval = %v, got: %v", off, client.snifferInterval)
}
if client.basicAuthUsername != "" {
t.Errorf("expected no basic auth username; got: %q", client.basicAuthUsername)
}
if client.basicAuthPassword != "" {
t.Errorf("expected no basic auth password; got: %q", client.basicAuthUsername)
}
if client.sendGetBodyAs != "GET" {
t.Errorf("expected sendGetBodyAs to be GET; got: %q", client.sendGetBodyAs)
}
}
// -- Start and stop --
func TestClientStartAndStop(t *testing.T) {
client, err := NewClient()
if err != nil {
t.Fatal(err)
}
running := client.IsRunning()
if !running {
t.Fatalf("expected background processes to run; got: %v", running)
}
// Stop
client.Stop()
running = client.IsRunning()
if running {
t.Fatalf("expected background processes to be stopped; got: %v", running)
}
// Stop again => no-op
client.Stop()
running = client.IsRunning()
if running {
t.Fatalf("expected background processes to be stopped; got: %v", running)
}
// Start
client.Start()
running = client.IsRunning()
if !running {
t.Fatalf("expected background processes to run; got: %v", running)
}
// Start again => no-op
client.Start()
running = client.IsRunning()
if !running {
t.Fatalf("expected background processes to run; got: %v", running)
}
}
func TestClientStartAndStopWithSnifferAndHealthchecksDisabled(t *testing.T) {
client, err := NewClient(SetSniff(false), SetHealthcheck(false))
if err != nil {
t.Fatal(err)
}
running := client.IsRunning()
if !running {
t.Fatalf("expected background processes to run; got: %v", running)
}
// Stop
client.Stop()
running = client.IsRunning()
if running {
t.Fatalf("expected background processes to be stopped; got: %v", running)
}
// Stop again => no-op
client.Stop()
running = client.IsRunning()
if running {
t.Fatalf("expected background processes to be stopped; got: %v", running)
}
// Start
client.Start()
running = client.IsRunning()
if !running {
t.Fatalf("expected background processes to run; got: %v", running)
}
// Start again => no-op
client.Start()
running = client.IsRunning()
if !running {
t.Fatalf("expected background processes to run; got: %v", running)
}
}
// -- Sniffing --
func TestClientSniffNode(t *testing.T) {
client, err := NewClient()
if err != nil {
t.Fatal(err)
}
ch := make(chan []*conn)
go func() { ch <- client.sniffNode(context.Background(), DefaultURL) }()
select {
case nodes := <-ch:
if len(nodes) != 1 {
t.Fatalf("expected %d nodes; got: %d", 1, len(nodes))
}
pattern := `http:\/\/[\d\.]+:9200`
matched, err := regexp.MatchString(pattern, nodes[0].URL())
if err != nil {
t.Fatal(err)
}
if !matched {
t.Fatalf("expected node URL pattern %q; got: %q", pattern, nodes[0].URL())
}
case <-time.After(2 * time.Second):
t.Fatal("expected no timeout in sniff node")
break
}
}
func TestClientSniffOnDefaultURL(t *testing.T) {
client, _ := NewClient()
if client == nil {
t.Fatal("no client returned")
}
ch := make(chan error, 1)
go func() {
ch <- client.sniff(context.Background(), DefaultSnifferTimeoutStartup)
}()
select {
case err := <-ch:
if err != nil {
t.Fatalf("expected sniff to succeed; got: %v", err)
}
if len(client.conns) != 1 {
t.Fatalf("expected %d nodes; got: %d", 1, len(client.conns))
}
pattern := `http:\/\/[\d\.]+:9200`
matched, err := regexp.MatchString(pattern, client.conns[0].URL())
if err != nil {
t.Fatal(err)
}
if !matched {
t.Fatalf("expected node URL pattern %q; got: %q", pattern, client.conns[0].URL())
}
case <-time.After(2 * time.Second):
t.Fatal("expected no timeout in sniff")
break
}
}
func TestClientSniffTimeoutLeak(t *testing.T) {
// This test test checks if sniff requests are canceled
// after timeout.
// It contains couple of hacks which won't be needed once we
// stop supporting Go1.7.
// On Go1.7 it uses server side effects to monitor if connection
// was closed,
// and on Go 1.8+ we're additionally honestly monitoring routine
// leaks via leaktest.
mux := http.NewServeMux()
var reqDoneMu sync.Mutex
var reqDone bool
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
cn, ok := w.(http.CloseNotifier)
if !ok {
t.Fatalf("Writer is not CloseNotifier, but %v", reflect.TypeOf(w).Name())
}
<-cn.CloseNotify()
reqDoneMu.Lock()
reqDone = true
reqDoneMu.Unlock()
})
lis, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("Couldn't setup listener: %v", err)
}
addr := lis.Addr().String()
srv := &http.Server{
Handler: mux,
}
go srv.Serve(lis)
cli := &Client{
c: &http.Client{},
conns: []*conn{
{
url: "http://" + addr + "/",
},
},
snifferEnabled: true,
}
type closer interface {
Shutdown(context.Context) error
}
// pre-Go1.8 Server can't Shutdown
cl, isServerCloseable := (interface{}(srv)).(closer)
// Since Go1.7 can't Shutdown() - there will be leak from server
// Monitor leaks on Go 1.8+
if isServerCloseable {
defer leaktest.CheckTimeout(t, time.Second*10)()
}
cli.sniff(context.Background(), time.Millisecond*500)
if isServerCloseable {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
cl.Shutdown(ctx)
}
<-time.After(time.Second)
reqDoneMu.Lock()
if !reqDone {
reqDoneMu.Unlock()
t.Fatal("Request wasn't canceled or stopped")
}
reqDoneMu.Unlock()
}
func TestClientExtractHostname(t *testing.T) {
tests := []struct {
Scheme string
Address string
Output string
}{
{
Scheme: "http",
Address: "127.0.0.1:9200",
Output: "http://127.0.0.1:9200",
},
{
Scheme: "https",
Address: "127.0.0.1:9200",
Output: "https://127.0.0.1:9200",
},
{
Scheme: "http",
Address: "127.0.0.1:19200",
Output: "http://127.0.0.1:19200",
},
{
Scheme: "http",
Address: "myelk.local/10.1.0.24:9200",
Output: "http://myelk.local:9200",
},
}
client, err := NewClient(SetSniff(false), SetHealthcheck(false))
if err != nil {
t.Fatal(err)
}
for _, test := range tests {
got := client.extractHostname(test.Scheme, test.Address)
if want := test.Output; want != got {
t.Errorf("expected %q; got: %q", want, got)
}
}
}
// -- Selector --
func TestClientSelectConnHealthy(t *testing.T) {
client, err := NewClient(
SetSniff(false),
SetHealthcheck(false),
SetURL("http://127.0.0.1:9200", "http://127.0.0.1:9201"))
if err != nil {
t.Fatal(err)
}
// Both are healthy, so we should get both URLs in round-robin
client.conns[0].MarkAsHealthy()
client.conns[1].MarkAsHealthy()
// #1: Return 1st
c, err := client.next()
if err != nil {
t.Fatal(err)
}
if c.URL() != client.conns[0].URL() {
t.Fatalf("expected %s; got: %s", c.URL(), client.conns[0].URL())
}
// #2: Return 2nd
c, err = client.next()
if err != nil {
t.Fatal(err)
}
if c.URL() != client.conns[1].URL() {
t.Fatalf("expected %s; got: %s", c.URL(), client.conns[1].URL())
}
// #3: Return 1st
c, err = client.next()
if err != nil {
t.Fatal(err)
}
if c.URL() != client.conns[0].URL() {
t.Fatalf("expected %s; got: %s", c.URL(), client.conns[0].URL())
}
}
func TestClientSelectConnHealthyAndDead(t *testing.T) {
client, err := NewClient(
SetSniff(false),
SetHealthcheck(false),
SetURL("http://127.0.0.1:9200", "http://127.0.0.1:9201"))
if err != nil {
t.Fatal(err)
}
// 1st is healthy, second is dead
client.conns[0].MarkAsHealthy()
client.conns[1].MarkAsDead()
// #1: Return 1st
c, err := client.next()
if err != nil {
t.Fatal(err)
}
if c.URL() != client.conns[0].URL() {
t.Fatalf("expected %s; got: %s", c.URL(), client.conns[0].URL())
}
// #2: Return 1st again
c, err = client.next()
if err != nil {
t.Fatal(err)
}
if c.URL() != client.conns[0].URL() {
t.Fatalf("expected %s; got: %s", c.URL(), client.conns[0].URL())
}
// #3: Return 1st again and again
c, err = client.next()
if err != nil {
t.Fatal(err)
}
if c.URL() != client.conns[0].URL() {
t.Fatalf("expected %s; got: %s", c.URL(), client.conns[0].URL())
}
}
func TestClientSelectConnDeadAndHealthy(t *testing.T) {
client, err := NewClient(
SetSniff(false),
SetHealthcheck(false),
SetURL("http://127.0.0.1:9200", "http://127.0.0.1:9201"))
if err != nil {
t.Fatal(err)
}
// 1st is dead, 2nd is healthy
client.conns[0].MarkAsDead()
client.conns[1].MarkAsHealthy()
// #1: Return 2nd
c, err := client.next()
if err != nil {
t.Fatal(err)
}
if c.URL() != client.conns[1].URL() {
t.Fatalf("expected %s; got: %s", c.URL(), client.conns[1].URL())
}
// #2: Return 2nd again
c, err = client.next()
if err != nil {
t.Fatal(err)
}
if c.URL() != client.conns[1].URL() {
t.Fatalf("expected %s; got: %s", c.URL(), client.conns[1].URL())
}
// #3: Return 2nd again and again
c, err = client.next()
if err != nil {
t.Fatal(err)
}
if c.URL() != client.conns[1].URL() {
t.Fatalf("expected %s; got: %s", c.URL(), client.conns[1].URL())
}
}
func TestClientSelectConnAllDead(t *testing.T) {
client, err := NewClient(
SetSniff(false),
SetHealthcheck(false),
SetURL("http://127.0.0.1:9200", "http://127.0.0.1:9201"))
if err != nil {
t.Fatal(err)
}
// Both are dead
client.conns[0].MarkAsDead()
client.conns[1].MarkAsDead()
// If all connections are dead, next should make them alive again, but
// still return an error when it first finds out.
c, err := client.next()
if !IsConnErr(err) {
t.Fatal(err)
}
if c != nil {
t.Fatalf("expected no connection; got: %v", c)
}
// Return a connection
c, err = client.next()
if err != nil {
t.Fatalf("expected no error; got: %v", err)
}
if c == nil {
t.Fatalf("expected connection; got: %v", c)
}
// Return a connection
c, err = client.next()
if err != nil {
t.Fatalf("expected no error; got: %v", err)
}
if c == nil {
t.Fatalf("expected connection; got: %v", c)
}
}
// -- ElasticsearchVersion --
func TestElasticsearchVersion(t *testing.T) {
client, err := NewClient()
if err != nil {
t.Fatal(err)
}
version, err := client.ElasticsearchVersion(DefaultURL)
if err != nil {
t.Fatal(err)
}
if version == "" {
t.Errorf("expected a version number, got: %q", version)
}
}
// -- IndexNames --
func TestIndexNames(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
names, err := client.IndexNames()
if err != nil {
t.Fatal(err)
}
if len(names) == 0 {
t.Fatalf("expected some index names, got: %d", len(names))
}
var found bool
for _, name := range names {
if name == testIndexName {
found = true
break
}
}
if !found {
t.Fatalf("expected to find index %q; got: %v", testIndexName, found)
}
}
// -- PerformRequest --
func TestPerformRequest(t *testing.T) {
client, err := NewClient()
if err != nil {
t.Fatal(err)
}
res, err := client.PerformRequest(context.TODO(), PerformRequestOptions{
Method: "GET",
Path: "/",
})
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected response to be != nil")
}
ret := new(PingResult)
if err := json.Unmarshal(res.Body, ret); err != nil {
t.Fatalf("expected no error on decode; got: %v", err)
}
if ret.ClusterName == "" {
t.Errorf("expected cluster name; got: %q", ret.ClusterName)
}
}
func TestPerformRequestWithStream(t *testing.T) {
client, err := NewClient()
if err != nil {
t.Fatal(err)
}
res, err := client.PerformRequest(context.TODO(), PerformRequestOptions{
Method: "GET",
Path: "/",
Stream: true,
})
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected response to be != nil")
}
if res.BodyReader == nil {
t.Fatal("expected res.BodyReader to be != nil")
}
if res.Body != nil {
t.Fatal("expected res.Body to be == nil")
}
ret := new(PingResult)
if err := json.NewDecoder(res.BodyReader).Decode(ret); err != nil {
t.Fatalf("expected no error on decode; got: %v", err)
}
if ret.ClusterName == "" {
t.Errorf("expected cluster name; got: %q", ret.ClusterName)
}
}
func TestPerformRequestWithSimpleClient(t *testing.T) {
client, err := NewSimpleClient()
if err != nil {
t.Fatal(err)
}
res, err := client.PerformRequest(context.TODO(), PerformRequestOptions{
Method: "GET",
Path: "/",
})
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected response to be != nil")
}
ret := new(PingResult)
if err := json.Unmarshal(res.Body, ret); err != nil {
t.Fatalf("expected no error on decode; got: %v", err)
}
if ret.ClusterName == "" {
t.Errorf("expected cluster name; got: %q", ret.ClusterName)
}
}
func TestPerformRequestWithLogger(t *testing.T) {
var w bytes.Buffer
out := log.New(&w, "LOGGER ", log.LstdFlags)
client, err := NewClient(SetInfoLog(out), SetSniff(false))
if err != nil {
t.Fatal(err)
}
res, err := client.PerformRequest(context.TODO(), PerformRequestOptions{
Method: "GET",
Path: "/",
})
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected response to be != nil")
}
ret := new(PingResult)
if err := json.Unmarshal(res.Body, ret); err != nil {
t.Fatalf("expected no error on decode; got: %v", err)
}
if ret.ClusterName == "" {
t.Errorf("expected cluster name; got: %q", ret.ClusterName)
}
got := w.String()
pattern := `^LOGGER \d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} GET http://.*/ \[status:200, request:\d+\.\d{3}s\]\n`
matched, err := regexp.MatchString(pattern, got)
if err != nil {
t.Fatalf("expected log line to match %q; got: %v", pattern, err)
}
if !matched {
t.Errorf("expected log line to match %q; got: %v", pattern, got)
}
}
func TestPerformRequestWithLoggerAndTracer(t *testing.T) {
var lw bytes.Buffer
lout := log.New(&lw, "LOGGER ", log.LstdFlags)
var tw bytes.Buffer
tout := log.New(&tw, "TRACER ", log.LstdFlags)
client, err := NewClient(SetInfoLog(lout), SetTraceLog(tout), SetSniff(false))
if err != nil {
t.Fatal(err)
}
res, err := client.PerformRequest(context.TODO(), PerformRequestOptions{
Method: "GET",
Path: "/",
})
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected response to be != nil")
}
ret := new(PingResult)
if err := json.Unmarshal(res.Body, ret); err != nil {
t.Fatalf("expected no error on decode; got: %v", err)
}
if ret.ClusterName == "" {
t.Errorf("expected cluster name; got: %q", ret.ClusterName)
}
lgot := lw.String()
if lgot == "" {
t.Errorf("expected logger output; got: %q", lgot)
}
tgot := tw.String()
if tgot == "" {
t.Errorf("expected tracer output; got: %q", tgot)
}
}
func TestPerformRequestWithTracerOnError(t *testing.T) {
var tw bytes.Buffer
tout := log.New(&tw, "TRACER ", log.LstdFlags)
client, err := NewClient(SetTraceLog(tout), SetSniff(false))
if err != nil {
t.Fatal(err)
}
client.PerformRequest(context.TODO(), PerformRequestOptions{
Method: "GET",
Path: "/no-such-index",
})
tgot := tw.String()
if tgot == "" {
t.Errorf("expected tracer output; got: %q", tgot)
}
}
type customLogger struct {
out bytes.Buffer
}
func (l *customLogger) Printf(format string, v ...interface{}) {
l.out.WriteString(fmt.Sprintf(format, v...) + "\n")
}
func TestPerformRequestWithCustomLogger(t *testing.T) {
logger := &customLogger{}
client, err := NewClient(SetInfoLog(logger), SetSniff(false))
if err != nil {
t.Fatal(err)
}
res, err := client.PerformRequest(context.TODO(), PerformRequestOptions{
Method: "GET",
Path: "/",
})
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected response to be != nil")
}
ret := new(PingResult)
if err := json.Unmarshal(res.Body, ret); err != nil {
t.Fatalf("expected no error on decode; got: %v", err)
}
if ret.ClusterName == "" {
t.Errorf("expected cluster name; got: %q", ret.ClusterName)
}
got := logger.out.String()
pattern := `^GET http://.*/ \[status:200, request:\d+\.\d{3}s\]\n`
matched, err := regexp.MatchString(pattern, got)
if err != nil {
t.Fatalf("expected log line to match %q; got: %v", pattern, err)
}
if !matched {
t.Errorf("expected log line to match %q; got: %v", pattern, got)
}
}
func TestPerformRequestWithMaxResponseSize(t *testing.T) {
client, err := NewClient()
if err != nil {
t.Fatal(err)
}
res, err := client.PerformRequest(context.TODO(), PerformRequestOptions{
Method: "GET",
Path: "/",
MaxResponseSize: 1000,
})
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected response to be != nil")
}
_, err = client.PerformRequest(context.TODO(), PerformRequestOptions{
Method: "GET",
Path: "/",
MaxResponseSize: 100,
})
if err != ErrResponseSize {
t.Fatal("expected response size error")
}
}
func TestPerformRequestOnNoConnectionsWithHealthcheckRevival(t *testing.T) {
fail := func(r *http.Request) (*http.Response, error) {
return nil, errors.New("request failed")
}
tr := &failingTransport{path: "/fail", fail: fail}
httpClient := &http.Client{Transport: tr}
client, err := NewClient(SetHttpClient(httpClient), SetMaxRetries(0), SetHealthcheck(true))
if err != nil {
t.Fatal(err)
}
// Run against a failing endpoint to mark connection as dead
res, err := client.PerformRequest(context.TODO(), PerformRequestOptions{
Method: "GET",
Path: "/fail",
})
if err == nil {
t.Fatal(err)
}
if res != nil {
t.Fatal("expected no response")
}
// Forced healthcheck should bring connection back to life and complete request
res, err = client.PerformRequest(context.TODO(), PerformRequestOptions{
Method: "GET",
Path: "/",
})
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected response to be != nil")
}
}
// failingTransport will run a fail callback if it sees a given URL path prefix.
type failingTransport struct {
path string // path prefix to look for
fail func(*http.Request) (*http.Response, error) // call when path prefix is found
next http.RoundTripper // next round-tripper (use http.DefaultTransport if nil)
}
// RoundTrip implements a failing transport.
func (tr *failingTransport) RoundTrip(r *http.Request) (*http.Response, error) {
if strings.HasPrefix(r.URL.Path, tr.path) && tr.fail != nil {
return tr.fail(r)
}
if tr.next != nil {
return tr.next.RoundTrip(r)
}
return http.DefaultTransport.RoundTrip(r)
}
func TestPerformRequestRetryOnHttpError(t *testing.T) {
var numFailedReqs int
fail := func(r *http.Request) (*http.Response, error) {
numFailedReqs += 1
return nil, errors.New("request failed")
}
// Run against a failing endpoint and see if PerformRequest
// retries correctly.
tr := &failingTransport{path: "/fail", fail: fail}
httpClient := &http.Client{Transport: tr}
client, err := NewClient(SetHttpClient(httpClient), SetMaxRetries(5), SetHealthcheck(false))
if err != nil {
t.Fatal(err)
}
res, err := client.PerformRequest(context.TODO(), PerformRequestOptions{
Method: "GET",
Path: "/fail",
})
if err == nil {
t.Fatal("expected error")
}
if res != nil {
t.Fatal("expected no response")
}
// Connection should be marked as dead after it failed
if numFailedReqs != 5 {
t.Errorf("expected %d failed requests; got: %d", 5, numFailedReqs)
}
}
func TestPerformRequestNoRetryOnValidButUnsuccessfulHttpStatus(t *testing.T) {
var numFailedReqs int
fail := func(r *http.Request) (*http.Response, error) {
numFailedReqs++
return &http.Response{Request: r, StatusCode: 500, Body: http.NoBody}, nil
}
// Run against a failing endpoint and see if PerformRequest
// retries correctly.
tr := &failingTransport{path: "/fail", fail: fail}
httpClient := &http.Client{Transport: tr}
client, err := NewClient(SetHttpClient(httpClient), SetMaxRetries(5), SetHealthcheck(false))
if err != nil {
t.Fatal(err)
}
res, err := client.PerformRequest(context.TODO(), PerformRequestOptions{
Method: "GET",
Path: "/fail",
})
if err == nil {
t.Fatal("expected error")
}
if res == nil {
t.Fatal("expected response, got nil")
}
if want, got := 500, res.StatusCode; want != got {
t.Fatalf("expected status code = %d, got %d", want, got)
}
// Retry should not have triggered additional requests because
if numFailedReqs != 1 {
t.Errorf("expected %d failed requests; got: %d", 1, numFailedReqs)
}
}
func TestPerformRequestOnSpecifiedHttpStatusCodes(t *testing.T) {
var numFailedReqs int
fail := func(r *http.Request) (*http.Response, error) {
numFailedReqs++
return &http.Response{Request: r, StatusCode: 429, Body: http.NoBody}, nil
}
// Run against a failing endpoint and see if PerformRequest
// retries correctly.
tr := &failingTransport{path: "/fail", fail: fail}
httpClient := &http.Client{Transport: tr}
client, err := NewClient(
SetHttpClient(httpClient),
SetMaxRetries(5),
SetRetryStatusCodes(429, 504),
SetHealthcheck(false),
)
if err != nil {
t.Fatal(err)
}
res, err := client.PerformRequest(context.TODO(), PerformRequestOptions{
Method: "GET",
Path: "/fail",
})
if err == nil {
t.Fatalf("expected error: err=%v, resp=%+v", err, res)
}
if res == nil {
t.Fatal("expected response, got nil")
}
if want, got := 429, res.StatusCode; want != got {
t.Fatalf("expected status code = %d, got %d", want, got)
}
// Retry should not have triggered additional requests because
if numFailedReqs != 5 {
t.Errorf("expected %d failed requests; got: %d", 1, numFailedReqs)
}
}
// failingBody will return an error when json.Marshal is called on it.
type failingBody struct{}
// MarshalJSON implements the json.Marshaler interface and always returns an error.
func (fb failingBody) MarshalJSON() ([]byte, error) {
return nil, errors.New("failing to marshal")
}
func TestPerformRequestWithSetBodyError(t *testing.T) {
client, err := NewClient()
if err != nil {
t.Fatal(err)
}
res, err := client.PerformRequest(context.TODO(), PerformRequestOptions{
Method: "GET",
Path: "/",
Body: failingBody{},
})
if err == nil {
t.Fatal("expected error")
}
if res != nil {
t.Fatal("expected no response")
}
}
// sleepingTransport will sleep before doing a request.
type sleepingTransport struct {
timeout time.Duration
}
// RoundTrip implements a "sleepy" transport.
func (tr *sleepingTransport) RoundTrip(r *http.Request) (*http.Response, error) {
time.Sleep(tr.timeout)
return http.DefaultTransport.RoundTrip(r)
}
func TestPerformRequestWithCancel(t *testing.T) {
tr := &sleepingTransport{timeout: 3 * time.Second}
httpClient := &http.Client{Transport: tr}
client, err := NewSimpleClient(SetHttpClient(httpClient), SetMaxRetries(0))
if err != nil {
t.Fatal(err)
}
type result struct {
res *Response
err error
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
resc := make(chan result, 1)
go func() {
res, err := client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: "/",
})
resc <- result{res: res, err: err}
}()
select {
case <-time.After(1 * time.Second):
cancel()
case res := <-resc:
t.Fatalf("expected response before cancel, got %v", res)
case <-ctx.Done():
t.Fatalf("expected no early termination, got ctx.Done(): %v", ctx.Err())
}
err = ctx.Err()
if err != context.Canceled {
t.Fatalf("expected error context.Canceled, got: %v", err)
}
}
func TestPerformRequestWithTimeout(t *testing.T) {
tr := &sleepingTransport{timeout: 3 * time.Second}
httpClient := &http.Client{Transport: tr}
client, err := NewSimpleClient(SetHttpClient(httpClient), SetMaxRetries(0))
if err != nil {
t.Fatal(err)
}
type result struct {
res *Response
err error
}
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
resc := make(chan result, 1)
go func() {
res, err := client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: "/",
})
resc <- result{res: res, err: err}
}()
select {
case res := <-resc:
t.Fatalf("expected timeout before response, got %v", res)
case <-ctx.Done():
err := ctx.Err()
if err != context.DeadlineExceeded {
t.Fatalf("expected error context.DeadlineExceeded, got: %v", err)
}
}
}
func TestPerformRequestWithCustomHTTPHeadersOnRequest(t *testing.T) {
client, err := NewClient()
if err != nil {
t.Fatal(err)
}
res, err := client.PerformRequest(context.TODO(), PerformRequestOptions{
Method: "GET",
Path: "/_tasks",
Params: url.Values{
"pretty": []string{"true"},
},
Headers: http.Header{
"X-Opaque-Id": []string{"123456"},
},
})
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected response to be != nil")
}
if want, have := "123456", res.Header.Get("X-Opaque-Id"); want != have {
t.Fatalf("want response header X-Opaque-Id=%q, have %q", want, have)
}
}
func TestPerformRequestWithCustomHTTPHeadersOnClient(t *testing.T) {
client, err := NewClient(SetHeaders(http.Header{
"Custom-Id": []string{"olivere"},
}))
if err != nil {
t.Fatal(err)
}
res, err := client.PerformRequest(context.TODO(), PerformRequestOptions{
Method: "GET",
Path: "/_tasks",
Params: url.Values{
"pretty": []string{"true"},
},
Headers: http.Header{
"X-Opaque-Id": []string{"123456"},
},
})
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected response to be != nil")
}
// Request-level headers have preference
if want, have := "123456", res.Header.Get("X-Opaque-Id"); want != have {
t.Fatalf("want X-Opaque-Id=%q, have %q", want, have)
}
}
func TestPerformRequestSetsDefaultUserAgent(t *testing.T) {
var req *http.Request
h := func(r *http.Request) (*http.Response, error) {
req = new(http.Request)
*req = *r
return &http.Response{Request: r, StatusCode: http.StatusOK, Body: http.NoBody}, nil
}
tr := &failingTransport{path: "/", fail: h}
httpClient := &http.Client{Transport: tr}
client, err := NewClient(SetHttpClient(httpClient), SetSniff(false), SetHealthcheck(false))
if err != nil {
t.Fatal(err)
}
res, err := client.PerformRequest(context.TODO(), PerformRequestOptions{
Method: "GET",
Path: "/",
Params: url.Values{
"pretty": []string{"true"},
},
})
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected response to be != nil")
}
// Have a default for User-Agent
if want, have := "elastic/"+Version+" ("+runtime.GOOS+"-"+runtime.GOARCH+")", req.Header.Get("User-Agent"); want != have {
t.Fatalf("want User-Agent=%q, have %q", want, have)
}
}
func TestPerformRequestWithCustomHTTPHeadersPriority(t *testing.T) {
var req *http.Request
h := func(r *http.Request) (*http.Response, error) {
req = new(http.Request)
*req = *r
return &http.Response{Request: r, StatusCode: http.StatusOK, Body: http.NoBody}, nil
}
tr := &failingTransport{path: "/", fail: h}
httpClient := &http.Client{Transport: tr}
client, err := NewClient(SetHttpClient(httpClient), SetHeaders(http.Header{
"Custom-Id": []string{"olivere"},
"User-Agent": []string{"My user agent"},
"X-Opaque-Id": []string{"sandra"}, // <- will be overridden by request-level header
}), SetSniff(false), SetHealthcheck(false))
if err != nil {
t.Fatal(err)
}
res, err := client.PerformRequest(context.TODO(), PerformRequestOptions{
Method: "GET",
Path: "/",
Params: url.Values{
"pretty": []string{"true"},
},
Headers: http.Header{
"X-Opaque-Id": []string{"123456"}, // <- request-level has preference
"X-Somewhat": []string{"somewhat"},
},
})
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected response to be != nil")
}
if req == nil {
t.Fatal("expected to record HTTP request")
}
if want, have := "123456", req.Header.Get("X-Opaque-Id"); want != have {
t.Fatalf("want X-Opaque-Id=%q, have %q", want, have)
}
if want, have := "olivere", req.Header.Get("Custom-Id"); want != have {
t.Fatalf("want Custom-Id=%q, have %q", want, have)
}
if want, have := "My user agent", req.Header.Get("User-Agent"); want != have {
t.Fatalf("want User-Agent=%q, have %q", want, have)
}
if want, have := "somewhat", req.Header.Get("X-Somewhat"); want != have {
t.Fatalf("want X-Somewhat=%q, have %q", want, have)
}
}
// -- Compression --
// Notice that the trace log does always print "Accept-Encoding: gzip"
// regardless of whether compression is enabled or not. This is because
// of the underlying "httputil.DumpRequestOut".
//
// Use a real HTTP proxy/recorder to convince yourself that
// "Accept-Encoding: gzip" is NOT sent when DisableCompression
// is set to true.
//
// See also:
// https://groups.google.com/forum/#!topic/golang-nuts/ms8QNCzew8Q
func TestPerformRequestWithCompressionEnabled(t *testing.T) {
testPerformRequestWithCompression(t, &http.Client{
Transport: &http.Transport{
DisableCompression: true,
},
})
}
func TestPerformRequestWithCompressionDisabled(t *testing.T) {
testPerformRequestWithCompression(t, &http.Client{
Transport: &http.Transport{
DisableCompression: false,
},
})
}
func testPerformRequestWithCompression(t *testing.T, hc *http.Client) {
client, err := NewClient(SetHttpClient(hc), SetSniff(false))
if err != nil {
t.Fatal(err)
}
res, err := client.PerformRequest(context.TODO(), PerformRequestOptions{
Method: "GET",
Path: "/",
})
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected response to be != nil")
}
ret := new(PingResult)
if err := json.Unmarshal(res.Body, ret); err != nil {
t.Fatalf("expected no error on decode; got: %v", err)
}
if ret.ClusterName == "" {
t.Errorf("expected cluster name; got: %q", ret.ClusterName)
}
}
================================================
FILE: cluster-test/Makefile
================================================
.PHONY: build run-omega-cluster-test
default: build
build:
go build cluster-test.go
run-omega-cluster-test:
go run -race cluster-test.go \
-nodes=http://192.168.2.65:8200,http://192.168.2.64:8200 \
-n=5 \
-retries=5 \
-sniff=true -sniffer=10s \
-healthcheck=true -healthchecker=5s \
-errorlog=errors.log
================================================
FILE: cluster-test/README.md
================================================
# Cluster Test
This directory contains a program you can use to test a cluster.
Here's how:
First, install a cluster of Elasticsearch nodes. You can install them on
different computers, or start several nodes on a single machine.
Build cluster-test by `go build cluster-test.go` (or build with `make`).
Run `./cluster-test -h` to get a list of flags:
```sh
$ ./cluster-test -h
Usage of ./cluster-test:
-errorlog="": error log file
-healthcheck=true: enable or disable healthchecks
-healthchecker=1m0s: healthcheck interval
-index="twitter": name of ES index to use
-infolog="": info log file
-n=5: number of goroutines that run searches
-nodes="": comma-separated list of ES URLs (e.g. 'http://192.168.2.10:9200,http://192.168.2.11:9200')
-retries=0: number of retries
-sniff=true: enable or disable sniffer
-sniffer=15m0s: sniffer interval
-tracelog="": trace log file
```
Example:
```sh
$ ./cluster-test -nodes=http://127.0.0.1:9200,http://127.0.0.1:9201,http://127.0.0.1:9202 -n=5 -index=twitter -retries=5 -sniff=true -sniffer=10s -healthcheck=true -healthchecker=5s -errorlog=error.log
```
The above example will create an index and start some search jobs on the
cluster defined by http://127.0.0.1:9200, http://127.0.0.1:9201,
and http://127.0.0.1:9202.
* It will create an index called `twitter` on the cluster (`-index=twitter`)
* It will run 5 search jobs in parallel (`-n=5`).
* It will retry failed requests 5 times (`-retries=5`).
* It will sniff the cluster periodically (`-sniff=true`).
* It will sniff the cluster every 10 seconds (`-sniffer=10s`).
* It will perform health checks periodically (`-healthcheck=true`).
* It will perform health checks on the nodes every 5 seconds (`-healthchecker=5s`).
* It will write an error log file (`-errorlog=error.log`).
If you want to test Elastic with nodes going up and down, you can use a
chaos monkey script like this and run it on the nodes of your cluster:
```sh
#!/bin/bash
while true
do
echo "Starting ES node"
elasticsearch -d -Xmx4g -Xms1g -Des.config=elasticsearch.yml -p es.pid
sleep `jot -r 1 10 300` # wait for 10-300s
echo "Stopping ES node"
kill -TERM `cat es.pid`
sleep `jot -r 1 10 60` # wait for 10-60s
done
```
================================================
FILE: cluster-test/cluster-test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package main
import (
"context"
"encoding/json"
"errors"
"flag"
"fmt"
"log"
"math/rand"
"os"
"runtime"
"strings"
"sync/atomic"
"time"
"github.com/olivere/elastic/v7"
)
type Tweet struct {
User string `json:"user"`
Message string `json:"message"`
Retweets int `json:"retweets"`
Image string `json:"image,omitempty"`
Created time.Time `json:"created,omitempty"`
Tags []string `json:"tags,omitempty"`
Location string `json:"location,omitempty"`
Suggest *elastic.SuggestField `json:"suggest_field,omitempty"`
}
var (
nodes = flag.String("nodes", "", "comma-separated list of ES URLs (e.g. 'http://192.168.2.10:9200,http://192.168.2.11:9200')")
n = flag.Int("n", 5, "number of goroutines that run searches")
index = flag.String("index", "twitter", "name of ES index to use")
errorlogfile = flag.String("errorlog", "", "error log file")
infologfile = flag.String("infolog", "", "info log file")
tracelogfile = flag.String("tracelog", "", "trace log file")
retries = flag.Int("retries", 0, "number of retries")
sniff = flag.Bool("sniff", elastic.DefaultSnifferEnabled, "enable or disable sniffer")
sniffer = flag.Duration("sniffer", elastic.DefaultSnifferInterval, "sniffer interval")
healthcheck = flag.Bool("healthcheck", elastic.DefaultHealthcheckEnabled, "enable or disable healthchecks")
healthchecker = flag.Duration("healthchecker", elastic.DefaultHealthcheckInterval, "healthcheck interval")
)
func main() {
flag.Parse()
runtime.GOMAXPROCS(runtime.NumCPU())
if *nodes == "" {
log.Fatal("no nodes specified")
}
urls := strings.SplitN(*nodes, ",", -1)
testcase, err := NewTestCase(*index, urls)
if err != nil {
log.Fatal(err)
}
testcase.SetErrorLogFile(*errorlogfile)
testcase.SetInfoLogFile(*infologfile)
testcase.SetTraceLogFile(*tracelogfile)
testcase.SetMaxRetries(*retries)
testcase.SetHealthcheck(*healthcheck)
testcase.SetHealthcheckInterval(*healthchecker)
testcase.SetSniff(*sniff)
testcase.SetSnifferInterval(*sniffer)
if err := testcase.Run(*n); err != nil {
log.Fatal(err)
}
select {}
}
type RunInfo struct {
Success bool
}
type TestCase struct {
nodes []string
client *elastic.Client
runs int64
failures int64
runCh chan RunInfo
index string
errorlogfile string
infologfile string
tracelogfile string
maxRetries int
healthcheck bool
healthcheckInterval time.Duration
sniff bool
snifferInterval time.Duration
}
func NewTestCase(index string, nodes []string) (*TestCase, error) {
if index == "" {
return nil, errors.New("no index name specified")
}
return &TestCase{
index: index,
nodes: nodes,
runCh: make(chan RunInfo),
}, nil
}
func (t *TestCase) SetIndex(name string) {
t.index = name
}
func (t *TestCase) SetErrorLogFile(name string) {
t.errorlogfile = name
}
func (t *TestCase) SetInfoLogFile(name string) {
t.infologfile = name
}
func (t *TestCase) SetTraceLogFile(name string) {
t.tracelogfile = name
}
func (t *TestCase) SetMaxRetries(n int) {
t.maxRetries = n
}
func (t *TestCase) SetSniff(enabled bool) {
t.sniff = enabled
}
func (t *TestCase) SetSnifferInterval(d time.Duration) {
t.snifferInterval = d
}
func (t *TestCase) SetHealthcheck(enabled bool) {
t.healthcheck = enabled
}
func (t *TestCase) SetHealthcheckInterval(d time.Duration) {
t.healthcheckInterval = d
}
func (t *TestCase) Run(n int) error {
if err := t.setup(); err != nil {
return err
}
for i := 1; i < n; i++ {
go t.search()
}
go t.monitor()
return nil
}
func (t *TestCase) monitor() {
print := func() {
fmt.Printf("\033[32m%5d\033[0m; \033[31m%5d\033[0m: %s%s\r", t.runs, t.failures, t.client.String(), " ")
}
for {
select {
case run := <-t.runCh:
atomic.AddInt64(&t.runs, 1)
if !run.Success {
atomic.AddInt64(&t.failures, 1)
fmt.Println()
}
print()
case <-time.After(5 * time.Second):
// Print stats after some inactivity
print()
break
}
}
}
func (t *TestCase) setup() error {
var options []elastic.ClientOptionFunc
if t.errorlogfile != "" {
f, err := os.OpenFile(t.errorlogfile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0664)
if err != nil {
return err
}
logger := log.New(f, "", log.Ltime|log.Lmicroseconds|log.Lshortfile)
options = append(options, elastic.SetErrorLog(logger))
}
if t.infologfile != "" {
f, err := os.OpenFile(t.infologfile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0664)
if err != nil {
return err
}
logger := log.New(f, "", log.LstdFlags)
options = append(options, elastic.SetInfoLog(logger))
}
// Trace request and response details like this
if t.tracelogfile != "" {
f, err := os.OpenFile(t.tracelogfile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0664)
if err != nil {
return err
}
logger := log.New(f, "", log.LstdFlags)
options = append(options, elastic.SetTraceLog(logger))
}
options = append(options, elastic.SetURL(t.nodes...))
options = append(options, elastic.SetMaxRetries(t.maxRetries))
options = append(options, elastic.SetSniff(t.sniff))
options = append(options, elastic.SetSnifferInterval(t.snifferInterval))
options = append(options, elastic.SetHealthcheck(t.healthcheck))
options = append(options, elastic.SetHealthcheckInterval(t.healthcheckInterval))
client, err := elastic.NewClient(options...)
if err != nil {
// Handle error
return err
}
t.client = client
ctx := context.Background()
// Use the IndexExists service to check if a specified index exists.
exists, err := t.client.IndexExists(t.index).Do(ctx)
if err != nil {
return err
}
if exists {
deleteIndex, err := t.client.DeleteIndex(t.index).Do(ctx)
if err != nil {
return err
}
if !deleteIndex.Acknowledged {
return errors.New("delete index not acknowledged")
}
}
// Create a new index.
createIndex, err := t.client.CreateIndex(t.index).Do(ctx)
if err != nil {
return err
}
if !createIndex.Acknowledged {
return errors.New("create index not acknowledged")
}
// Index a tweet (using JSON serialization)
tweet1 := Tweet{User: "olivere", Message: "Take Five", Retweets: 0}
_, err = t.client.Index().
Index(t.index).
Type("tweet").
Id("1").
BodyJson(tweet1).
Do(ctx)
if err != nil {
return err
}
// Index a second tweet (by string)
tweet2 := `{"user" : "olivere", "message" : "It's a Raggy Waltz"}`
_, err = t.client.Index().
Index(t.index).
Type("tweet").
Id("2").
BodyString(tweet2).
Do(ctx)
if err != nil {
return err
}
// Flush to make sure the documents got written.
_, err = t.client.Flush().Index(t.index).Do(ctx)
if err != nil {
return err
}
return nil
}
func (t *TestCase) search() {
ctx := context.Background()
// Loop forever to check for connection issues
for {
// Get tweet with specified ID
_, err := t.client.Get().
Index(t.index).
Type("tweet").
Id("1").
Do(ctx)
if err != nil {
//failf("Get failed: %v", err)
t.runCh <- RunInfo{Success: false}
continue
}
if elastic.IsNotFound(err) {
//log.Printf("Document %s not found\n", "1")
//fmt.Printf("Got document %s in version %d from index %s, type %s\n", get1.Id, get1.Version, get1.Index, get1.Type)
t.runCh <- RunInfo{Success: false}
continue
}
// Search with a term query
searchResult, err := t.client.Search().
Index(t.index). // search in index t.index
Query(elastic.NewTermQuery("user", "olivere")). // specify the query
Sort("user", true). // sort by "user" field, ascending
From(0).Size(10). // take documents 0-9
Pretty(true). // pretty print request and response JSON
Do(ctx) // execute
if err != nil {
//failf("Search failed: %v\n", err)
t.runCh <- RunInfo{Success: false}
continue
}
// searchResult is of type SearchResult and returns hits, suggestions,
// and all kinds of other information from Elasticsearch.
//fmt.Printf("Query took %d milliseconds\n", searchResult.TookInMillis)
// Number of hits
if searchResult.Hits.TotalHits.Value > 0 {
//fmt.Printf("Found a total of %d tweets\n", searchResult.Hits.TotalHits)
// Iterate through results
for _, hit := range searchResult.Hits.Hits {
// hit.Index contains the name of the index
// Deserialize hit.Source into a Tweet (could also be just a map[string]interface{}).
var tweet Tweet
err := json.Unmarshal(hit.Source, &tweet)
if err != nil {
// Deserialization failed
//failf("Deserialize failed: %v\n", err)
t.runCh <- RunInfo{Success: false}
continue
}
// Work with tweet
//fmt.Printf("Tweet by %s: %s\n", t.User, t.Message)
}
} else {
// No hits
//fmt.Print("Found no tweets\n")
}
t.runCh <- RunInfo{Success: true}
// Sleep some time
time.Sleep(time.Duration(rand.Intn(500)) * time.Millisecond)
}
}
================================================
FILE: cluster_health.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// ClusterHealthService allows to get a very simple status on the health of the cluster.
//
// See http://www.elastic.co/guide/en/elasticsearch/reference/7.0/cluster-health.html
// for details.
type ClusterHealthService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
indices []string
level string
local *bool
masterTimeout string
timeout string
waitForActiveShards *int
waitForNodes string
waitForNoRelocatingShards *bool
waitForStatus string
}
// NewClusterHealthService creates a new ClusterHealthService.
func NewClusterHealthService(client *Client) *ClusterHealthService {
return &ClusterHealthService{
client: client,
indices: make([]string, 0),
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *ClusterHealthService) Pretty(pretty bool) *ClusterHealthService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *ClusterHealthService) Human(human bool) *ClusterHealthService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *ClusterHealthService) ErrorTrace(errorTrace bool) *ClusterHealthService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *ClusterHealthService) FilterPath(filterPath ...string) *ClusterHealthService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *ClusterHealthService) Header(name string, value string) *ClusterHealthService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *ClusterHealthService) Headers(headers http.Header) *ClusterHealthService {
s.headers = headers
return s
}
// Index limits the information returned to specific indices.
func (s *ClusterHealthService) Index(indices ...string) *ClusterHealthService {
s.indices = append(s.indices, indices...)
return s
}
// Level specifies the level of detail for returned information.
func (s *ClusterHealthService) Level(level string) *ClusterHealthService {
s.level = level
return s
}
// Local indicates whether to return local information. If it is true,
// we do not retrieve the state from master node (default: false).
func (s *ClusterHealthService) Local(local bool) *ClusterHealthService {
s.local = &local
return s
}
// MasterTimeout specifies an explicit operation timeout for connection to master node.
func (s *ClusterHealthService) MasterTimeout(masterTimeout string) *ClusterHealthService {
s.masterTimeout = masterTimeout
return s
}
// Timeout specifies an explicit operation timeout.
func (s *ClusterHealthService) Timeout(timeout string) *ClusterHealthService {
s.timeout = timeout
return s
}
// WaitForActiveShards can be used to wait until the specified number of shards are active.
func (s *ClusterHealthService) WaitForActiveShards(waitForActiveShards int) *ClusterHealthService {
s.waitForActiveShards = &waitForActiveShards
return s
}
// WaitForNodes can be used to wait until the specified number of nodes are available.
// Example: "12" to wait for exact values, ">12" and "<12" for ranges.
func (s *ClusterHealthService) WaitForNodes(waitForNodes string) *ClusterHealthService {
s.waitForNodes = waitForNodes
return s
}
// WaitForNoRelocatingShards can be used to wait until all shard relocations are finished.
func (s *ClusterHealthService) WaitForNoRelocatingShards(waitForNoRelocatingShards bool) *ClusterHealthService {
s.waitForNoRelocatingShards = &waitForNoRelocatingShards
return s
}
// WaitForStatus can be used to wait until the cluster is in a specific state.
// Valid values are: green, yellow, or red.
func (s *ClusterHealthService) WaitForStatus(waitForStatus string) *ClusterHealthService {
s.waitForStatus = waitForStatus
return s
}
// WaitForGreenStatus will wait for the "green" state.
func (s *ClusterHealthService) WaitForGreenStatus() *ClusterHealthService {
return s.WaitForStatus("green")
}
// WaitForYellowStatus will wait for the "yellow" state.
func (s *ClusterHealthService) WaitForYellowStatus() *ClusterHealthService {
return s.WaitForStatus("yellow")
}
// buildURL builds the URL for the operation.
func (s *ClusterHealthService) buildURL() (string, url.Values, error) {
// Build URL
var err error
var path string
if len(s.indices) > 0 {
path, err = uritemplates.Expand("/_cluster/health/{index}", map[string]string{
"index": strings.Join(s.indices, ","),
})
} else {
path = "/_cluster/health"
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.level != "" {
params.Set("level", s.level)
}
if s.local != nil {
params.Set("local", fmt.Sprintf("%v", *s.local))
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
if s.timeout != "" {
params.Set("timeout", s.timeout)
}
if s.waitForActiveShards != nil {
params.Set("wait_for_active_shards", fmt.Sprintf("%v", s.waitForActiveShards))
}
if s.waitForNodes != "" {
params.Set("wait_for_nodes", s.waitForNodes)
}
if s.waitForNoRelocatingShards != nil {
params.Set("wait_for_no_relocating_shards", fmt.Sprintf("%v", *s.waitForNoRelocatingShards))
}
if s.waitForStatus != "" {
params.Set("wait_for_status", s.waitForStatus)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *ClusterHealthService) Validate() error {
return nil
}
// Do executes the operation.
func (s *ClusterHealthService) Do(ctx context.Context) (*ClusterHealthResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(ClusterHealthResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// ClusterHealthResponse is the response of ClusterHealthService.Do.
type ClusterHealthResponse struct {
ClusterName string `json:"cluster_name"`
Status string `json:"status"`
TimedOut bool `json:"timed_out"`
NumberOfNodes int `json:"number_of_nodes"`
NumberOfDataNodes int `json:"number_of_data_nodes"`
ActivePrimaryShards int `json:"active_primary_shards"`
ActiveShards int `json:"active_shards"`
RelocatingShards int `json:"relocating_shards"`
InitializingShards int `json:"initializing_shards"`
UnassignedShards int `json:"unassigned_shards"`
DelayedUnassignedShards int `json:"delayed_unassigned_shards"`
NumberOfPendingTasks int `json:"number_of_pending_tasks"`
NumberOfInFlightFetch int `json:"number_of_in_flight_fetch"`
TaskMaxWaitTimeInQueue string `json:"task_max_waiting_in_queue"` // "0s"
TaskMaxWaitTimeInQueueInMillis int `json:"task_max_waiting_in_queue_millis"` // 0
ActiveShardsPercent string `json:"active_shards_percent"` // "100.0%"
ActiveShardsPercentAsNumber float64 `json:"active_shards_percent_as_number"` // 100.0
// Index name -> index health
Indices map[string]*ClusterIndexHealth `json:"indices"`
}
// ClusterIndexHealth will be returned as part of ClusterHealthResponse.
type ClusterIndexHealth struct {
Status string `json:"status"`
NumberOfShards int `json:"number_of_shards"`
NumberOfReplicas int `json:"number_of_replicas"`
ActivePrimaryShards int `json:"active_primary_shards"`
ActiveShards int `json:"active_shards"`
RelocatingShards int `json:"relocating_shards"`
InitializingShards int `json:"initializing_shards"`
UnassignedShards int `json:"unassigned_shards"`
// Shards by id, e.g. "0" or "1"
Shards map[string]*ClusterShardHealth `json:"shards"`
}
// ClusterShardHealth will be returned as part of ClusterHealthResponse.
type ClusterShardHealth struct {
Status string `json:"status"`
PrimaryActive bool `json:"primary_active"`
ActiveShards int `json:"active_shards"`
RelocatingShards int `json:"relocating_shards"`
InitializingShards int `json:"initializing_shards"`
UnassignedShards int `json:"unassigned_shards"`
}
================================================
FILE: cluster_health_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"net/url"
"testing"
)
func TestClusterHealth(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
// Get cluster health
res, err := client.ClusterHealth().Index(testIndexName).Level("shards").Pretty(true).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatalf("expected res to be != nil; got: %v", res)
}
if res.Status != "green" && res.Status != "red" && res.Status != "yellow" {
t.Fatalf("expected status \"green\", \"red\", or \"yellow\"; got: %q", res.Status)
}
}
func TestClusterHealthURLs(t *testing.T) {
tests := []struct {
Service *ClusterHealthService
ExpectedPath string
ExpectedParams url.Values
}{
{
Service: &ClusterHealthService{
indices: []string{},
},
ExpectedPath: "/_cluster/health",
},
{
Service: &ClusterHealthService{
indices: []string{"twitter"},
},
ExpectedPath: "/_cluster/health/twitter",
},
{
Service: &ClusterHealthService{
indices: []string{"twitter", "gplus"},
},
ExpectedPath: "/_cluster/health/twitter%2Cgplus",
},
{
Service: &ClusterHealthService{
indices: []string{"twitter"},
waitForStatus: "yellow",
},
ExpectedPath: "/_cluster/health/twitter",
ExpectedParams: url.Values{"wait_for_status": []string{"yellow"}},
},
}
for _, test := range tests {
gotPath, gotParams, err := test.Service.buildURL()
if err != nil {
t.Fatalf("expected no error; got: %v", err)
}
if gotPath != test.ExpectedPath {
t.Errorf("expected URL path = %q; got: %q", test.ExpectedPath, gotPath)
}
if gotParams.Encode() != test.ExpectedParams.Encode() {
t.Errorf("expected URL params = %v; got: %v", test.ExpectedParams, gotParams)
}
}
}
func TestClusterHealthWaitForStatus(t *testing.T) {
client := setupTestClientAndCreateIndex(t) //, SetTraceLog(log.New(os.Stdout, "", 0)))
// Ensure preconditions are met: A green cluster.
health, err := client.ClusterHealth().Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if got, want := health.Status, "green"; got != want {
t.Skipf("precondition failed: expected cluster to be %q, not %q", want, got)
}
// Cluster health on an index that does not exist should never get to yellow
health, err = client.ClusterHealth().Index("no-such-index").WaitForStatus("yellow").Timeout("1s").Do(context.TODO())
if err == nil {
t.Fatalf("expected timeout error; got: %v", err)
}
if !IsTimeout(err) {
t.Fatalf("expected timeout error; got: %v", err)
}
if health != nil {
t.Fatalf("expected no response; got: %v", health)
}
// Cluster wide health
health, err = client.ClusterHealth().WaitForGreenStatus().Timeout("10s").Do(context.TODO())
if err != nil {
t.Fatalf("expected no error; got: %v", err)
}
if health.TimedOut != false {
t.Fatalf("expected no timeout; got: %v "+
"(does your local cluster contain unassigned shards?)", health.TimedOut)
}
if health.Status != "green" {
t.Fatalf("expected health = %q; got: %q", "green", health.Status)
}
// Cluster wide health via shortcut on client
err = client.WaitForGreenStatus("10s")
if err != nil {
t.Fatalf("expected no error; got: %v", err)
}
}
================================================
FILE: cluster_reroute.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"errors"
"fmt"
"net/http"
"net/url"
"strings"
)
// ClusterRerouteService allows for manual changes to the allocation of
// individual shards in the cluster. For example, a shard can be moved from
// one node to another explicitly, an allocation can be cancelled, and
// an unassigned shard can be explicitly allocated to a specific node.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/cluster-reroute.html
// for details.
type ClusterRerouteService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
metrics []string
dryRun *bool
explain *bool
retryFailed *bool
masterTimeout string
timeout string
commands []AllocationCommand
body interface{}
}
// NewClusterRerouteService creates a new ClusterRerouteService.
func NewClusterRerouteService(client *Client) *ClusterRerouteService {
return &ClusterRerouteService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *ClusterRerouteService) Pretty(pretty bool) *ClusterRerouteService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *ClusterRerouteService) Human(human bool) *ClusterRerouteService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *ClusterRerouteService) ErrorTrace(errorTrace bool) *ClusterRerouteService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *ClusterRerouteService) FilterPath(filterPath ...string) *ClusterRerouteService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *ClusterRerouteService) Header(name string, value string) *ClusterRerouteService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *ClusterRerouteService) Headers(headers http.Header) *ClusterRerouteService {
s.headers = headers
return s
}
// Metric limits the information returned to the specified metric.
// It can be one of: "_all", "blocks", "metadata", "nodes", "routing_table", "master_node", "version".
// Defaults to all but metadata.
func (s *ClusterRerouteService) Metric(metrics ...string) *ClusterRerouteService {
s.metrics = append(s.metrics, metrics...)
return s
}
// DryRun indicates whether to simulate the operation only and return the
// resulting state.
func (s *ClusterRerouteService) DryRun(dryRun bool) *ClusterRerouteService {
s.dryRun = &dryRun
return s
}
// Explain, when set to true, returns an explanation of why the commands
// can or cannot be executed.
func (s *ClusterRerouteService) Explain(explain bool) *ClusterRerouteService {
s.explain = &explain
return s
}
// RetryFailed indicates whether to retry allocation of shards that are blocked
// due to too many subsequent allocation failures.
func (s *ClusterRerouteService) RetryFailed(retryFailed bool) *ClusterRerouteService {
s.retryFailed = &retryFailed
return s
}
// MasterTimeout specifies an explicit timeout for connection to master.
func (s *ClusterRerouteService) MasterTimeout(masterTimeout string) *ClusterRerouteService {
s.masterTimeout = masterTimeout
return s
}
// Timeout specifies an explicit operationtimeout.
func (s *ClusterRerouteService) Timeout(timeout string) *ClusterRerouteService {
s.timeout = timeout
return s
}
// Add adds one or more commands to be executed.
func (s *ClusterRerouteService) Add(commands ...AllocationCommand) *ClusterRerouteService {
s.commands = append(s.commands, commands...)
return s
}
// Body specifies the body to be sent.
// If you specify Body, the commands passed via Add are ignored.
// In other words: Body takes precedence over Add.
func (s *ClusterRerouteService) Body(body interface{}) *ClusterRerouteService {
s.body = body
return s
}
// buildURL builds the URL for the operation.
func (s *ClusterRerouteService) buildURL() (string, url.Values, error) {
// Build URL
path := "/_cluster/reroute"
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if v := s.dryRun; v != nil {
params.Set("dry_run", fmt.Sprint(*v))
}
if v := s.explain; v != nil {
params.Set("explain", fmt.Sprint(*v))
}
if v := s.retryFailed; v != nil {
params.Set("retry_failed", fmt.Sprint(*v))
}
if len(s.metrics) > 0 {
params.Set("metric", strings.Join(s.metrics, ","))
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
if s.timeout != "" {
params.Set("timeout", s.timeout)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *ClusterRerouteService) Validate() error {
if s.body == nil && len(s.commands) == 0 {
return errors.New("missing allocate commands or raw body")
}
return nil
}
// Do executes the operation.
func (s *ClusterRerouteService) Do(ctx context.Context) (*ClusterRerouteResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Setup HTTP request body
var body interface{}
if s.body != nil {
body = s.body
} else {
commands := make([]interface{}, len(s.commands))
for i, cmd := range s.commands {
src, err := cmd.Source()
if err != nil {
return nil, err
}
commands[i] = map[string]interface{}{
cmd.Name(): src,
}
}
query := make(map[string]interface{})
query["commands"] = commands
body = query
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "POST",
Path: path,
Params: params,
Body: body,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(ClusterRerouteResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// ClusterRerouteResponse is the response of ClusterRerouteService.Do.
type ClusterRerouteResponse struct {
State *ClusterStateResponse `json:"state"`
Explanations []RerouteExplanation `json:"explanations,omitempty"`
}
// RerouteExplanation is returned in ClusterRerouteResponse if
// the "explain" parameter is set to "true".
type RerouteExplanation struct {
Command string `json:"command"`
Parameters map[string]interface{} `json:"parameters"`
Decisions []RerouteDecision `json:"decisions"`
}
// RerouteDecision is a decision the decider made while rerouting.
type RerouteDecision interface{}
// -- Allocation commands --
// AllocationCommand is a command to be executed in a call
// to Cluster Reroute API.
type AllocationCommand interface {
Name() string
Source() (interface{}, error)
}
var _ AllocationCommand = (*MoveAllocationCommand)(nil)
// MoveAllocationCommand moves a shard from a specific node to
// another node.
type MoveAllocationCommand struct {
index string
shardId int
fromNode string
toNode string
}
// NewMoveAllocationCommand creates a new MoveAllocationCommand.
func NewMoveAllocationCommand(index string, shardId int, fromNode, toNode string) *MoveAllocationCommand {
return &MoveAllocationCommand{
index: index,
shardId: shardId,
fromNode: fromNode,
toNode: toNode,
}
}
// Name of the command in a request to the Cluster Reroute API.
func (cmd *MoveAllocationCommand) Name() string { return "move" }
// Source generates the (inner) JSON to be used when serializing the command.
func (cmd *MoveAllocationCommand) Source() (interface{}, error) {
source := make(map[string]interface{})
source["index"] = cmd.index
source["shard"] = cmd.shardId
source["from_node"] = cmd.fromNode
source["to_node"] = cmd.toNode
return source, nil
}
var _ AllocationCommand = (*CancelAllocationCommand)(nil)
// CancelAllocationCommand cancels relocation, or recovery of a given shard on a node.
type CancelAllocationCommand struct {
index string
shardId int
node string
allowPrimary bool
}
// NewCancelAllocationCommand creates a new CancelAllocationCommand.
func NewCancelAllocationCommand(index string, shardId int, node string, allowPrimary bool) *CancelAllocationCommand {
return &CancelAllocationCommand{
index: index,
shardId: shardId,
node: node,
allowPrimary: allowPrimary,
}
}
// Name of the command in a request to the Cluster Reroute API.
func (cmd *CancelAllocationCommand) Name() string { return "cancel" }
// Source generates the (inner) JSON to be used when serializing the command.
func (cmd *CancelAllocationCommand) Source() (interface{}, error) {
source := make(map[string]interface{})
source["index"] = cmd.index
source["shard"] = cmd.shardId
source["node"] = cmd.node
source["allow_primary"] = cmd.allowPrimary
return source, nil
}
var _ AllocationCommand = (*AllocateStalePrimaryAllocationCommand)(nil)
// AllocateStalePrimaryAllocationCommand allocates an unassigned stale
// primary shard to a specific node. Use with extreme care as this will
// result in data loss. Allocation deciders are ignored.
type AllocateStalePrimaryAllocationCommand struct {
index string
shardId int
node string
acceptDataLoss bool
}
// NewAllocateStalePrimaryAllocationCommand creates a new
// AllocateStalePrimaryAllocationCommand.
func NewAllocateStalePrimaryAllocationCommand(index string, shardId int, node string, acceptDataLoss bool) *AllocateStalePrimaryAllocationCommand {
return &AllocateStalePrimaryAllocationCommand{
index: index,
shardId: shardId,
node: node,
acceptDataLoss: acceptDataLoss,
}
}
// Name of the command in a request to the Cluster Reroute API.
func (cmd *AllocateStalePrimaryAllocationCommand) Name() string { return "allocate_stale_primary" }
// Source generates the (inner) JSON to be used when serializing the command.
func (cmd *AllocateStalePrimaryAllocationCommand) Source() (interface{}, error) {
source := make(map[string]interface{})
source["index"] = cmd.index
source["shard"] = cmd.shardId
source["node"] = cmd.node
source["accept_data_loss"] = cmd.acceptDataLoss
return source, nil
}
var _ AllocationCommand = (*AllocateReplicaAllocationCommand)(nil)
// AllocateReplicaAllocationCommand allocates an unassigned replica shard
// to a specific node. Checks if allocation deciders allow allocation.
type AllocateReplicaAllocationCommand struct {
index string
shardId int
node string
}
// NewAllocateReplicaAllocationCommand creates a new
// AllocateReplicaAllocationCommand.
func NewAllocateReplicaAllocationCommand(index string, shardId int, node string) *AllocateReplicaAllocationCommand {
return &AllocateReplicaAllocationCommand{
index: index,
shardId: shardId,
node: node,
}
}
// Name of the command in a request to the Cluster Reroute API.
func (cmd *AllocateReplicaAllocationCommand) Name() string { return "allocate_replica" }
// Source generates the (inner) JSON to be used when serializing the command.
func (cmd *AllocateReplicaAllocationCommand) Source() (interface{}, error) {
source := make(map[string]interface{})
source["index"] = cmd.index
source["shard"] = cmd.shardId
source["node"] = cmd.node
return source, nil
}
// AllocateEmptyPrimaryAllocationCommand allocates an unassigned empty
// primary shard to a specific node. Use with extreme care as this will
// result in data loss. Allocation deciders are ignored.
type AllocateEmptyPrimaryAllocationCommand struct {
index string
shardId int
node string
acceptDataLoss bool
}
// NewAllocateEmptyPrimaryAllocationCommand creates a new
// AllocateEmptyPrimaryAllocationCommand.
func NewAllocateEmptyPrimaryAllocationCommand(index string, shardId int, node string, acceptDataLoss bool) *AllocateEmptyPrimaryAllocationCommand {
return &AllocateEmptyPrimaryAllocationCommand{
index: index,
shardId: shardId,
node: node,
acceptDataLoss: acceptDataLoss,
}
}
// Name of the command in a request to the Cluster Reroute API.
func (cmd *AllocateEmptyPrimaryAllocationCommand) Name() string { return "allocate_empty_primary" }
// Source generates the (inner) JSON to be used when serializing the command.
func (cmd *AllocateEmptyPrimaryAllocationCommand) Source() (interface{}, error) {
source := make(map[string]interface{})
source["index"] = cmd.index
source["shard"] = cmd.shardId
source["node"] = cmd.node
source["accept_data_loss"] = cmd.acceptDataLoss
return source, nil
}
================================================
FILE: cluster_reroute_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"net/url"
"strings"
"testing"
)
func TestClusterRerouteURLs(t *testing.T) {
trueFlag := true
truePtr := &trueFlag
tests := []struct {
Service *ClusterRerouteService
ExpectedPath string
ExpectedParams url.Values
}{
{
Service: &ClusterRerouteService{},
ExpectedPath: "/_cluster/reroute",
},
{
Service: &ClusterRerouteService{
dryRun: truePtr,
metrics: []string{"blocks", "nodes"},
},
ExpectedPath: "/_cluster/reroute",
ExpectedParams: url.Values{
"dry_run": []string{"true"},
"metric": []string{"blocks,nodes"},
},
},
}
for _, tt := range tests {
gotPath, gotParams, err := tt.Service.buildURL()
if err != nil {
t.Fatalf("expected no error; got: %v", err)
}
if gotPath != tt.ExpectedPath {
t.Errorf("expected URL path = %q; got: %q", tt.ExpectedPath, gotPath)
}
if gotParams.Encode() != tt.ExpectedParams.Encode() {
t.Errorf("expected URL params = %v; got: %v", tt.ExpectedParams, gotParams)
}
}
}
func TestClusterReroute(t *testing.T) {
// client := setupTestClientAndCreateIndex(t, SetTraceLog(log.New(os.Stdout, "", 0)))
client := setupTestClientAndCreateIndex(t)
t.Run("Commands", func(t *testing.T) {
testClusterRerouteWithCommands(client, t)
})
t.Run("NoBody", func(t *testing.T) {
testClusterRerouteWithoutBody(client, t)
})
}
func testClusterRerouteWithCommands(client *Client, t *testing.T) {
// Get cluster nodes
var nodes []string
{
res, err := client.ClusterState().Do(context.Background())
if err != nil {
t.Fatal(err)
}
for node := range res.Nodes {
nodes = append(nodes, node)
}
if len(nodes) == 0 {
t.Fatal("expected at least one node in cluster")
}
}
// Perform a nop cluster reroute
res, err := client.ClusterReroute().
DryRun(true).
Add(
NewMoveAllocationCommand(testIndexName, 0, nodes[0], nodes[0]),
NewCancelAllocationCommand(testIndexName, 0, nodes[0], true),
).
Pretty(true).
Do(context.Background())
// Expect an error here: We just test if it's of a specific kind
if err == nil {
t.Fatal("expected an error, got nil")
}
if res != nil {
t.Fatalf("expected res to be != nil; got: %v", res)
}
e, ok := err.(*Error)
if !ok {
t.Fatalf("expected an error of type *elastic.Error, got %T", err)
}
if want, have := 400, e.Status; want != have {
t.Fatalf("expected Status=%d, have %d", want, have)
}
}
func testClusterRerouteWithoutBody(client *Client, t *testing.T) {
// Perform a nop cluster reroute
res, err := client.ClusterReroute().
DryRun(true).
Pretty(true).
RetryFailed(true).
MasterTimeout("10s").
Do(context.Background())
// Expect an error here: We just test if it's of a specific kind
if err == nil {
t.Fatal("expected an error, got nil")
}
if res != nil {
t.Fatalf("expected res to be != nil; got: %v", res)
}
if !strings.Contains(err.Error(), "missing allocate commands or raw body") {
t.Fatalf("expected Error~=%s, have %s", "missing allocate commands or raw body", err.Error())
}
}
================================================
FILE: cluster_state.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// ClusterStateService allows to get a comprehensive state information of the whole cluster.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/cluster-state.html
// for details.
type ClusterStateService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
indices []string
metrics []string
allowNoIndices *bool
expandWildcards string
flatSettings *bool
ignoreUnavailable *bool
local *bool
masterTimeout string
}
// NewClusterStateService creates a new ClusterStateService.
func NewClusterStateService(client *Client) *ClusterStateService {
return &ClusterStateService{
client: client,
indices: make([]string, 0),
metrics: make([]string, 0),
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *ClusterStateService) Pretty(pretty bool) *ClusterStateService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *ClusterStateService) Human(human bool) *ClusterStateService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *ClusterStateService) ErrorTrace(errorTrace bool) *ClusterStateService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *ClusterStateService) FilterPath(filterPath ...string) *ClusterStateService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *ClusterStateService) Header(name string, value string) *ClusterStateService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *ClusterStateService) Headers(headers http.Header) *ClusterStateService {
s.headers = headers
return s
}
// Index is a list of index names. Use _all or an empty string to
// perform the operation on all indices.
func (s *ClusterStateService) Index(indices ...string) *ClusterStateService {
s.indices = append(s.indices, indices...)
return s
}
// Metric limits the information returned to the specified metric.
// It can be one of: version, master_node, nodes, routing_table, metadata,
// blocks, or customs.
func (s *ClusterStateService) Metric(metrics ...string) *ClusterStateService {
s.metrics = append(s.metrics, metrics...)
return s
}
// AllowNoIndices indicates whether to ignore if a wildcard indices
// expression resolves into no concrete indices.
// (This includes `_all` string or when no indices have been specified).
func (s *ClusterStateService) AllowNoIndices(allowNoIndices bool) *ClusterStateService {
s.allowNoIndices = &allowNoIndices
return s
}
// ExpandWildcards indicates whether to expand wildcard expression to
// concrete indices that are open, closed or both..
func (s *ClusterStateService) ExpandWildcards(expandWildcards string) *ClusterStateService {
s.expandWildcards = expandWildcards
return s
}
// FlatSettings, when set, returns settings in flat format (default: false).
func (s *ClusterStateService) FlatSettings(flatSettings bool) *ClusterStateService {
s.flatSettings = &flatSettings
return s
}
// IgnoreUnavailable indicates whether specified concrete indices should be
// ignored when unavailable (missing or closed).
func (s *ClusterStateService) IgnoreUnavailable(ignoreUnavailable bool) *ClusterStateService {
s.ignoreUnavailable = &ignoreUnavailable
return s
}
// Local indicates whether to return local information. When set, it does not
// retrieve the state from master node (default: false).
func (s *ClusterStateService) Local(local bool) *ClusterStateService {
s.local = &local
return s
}
// MasterTimeout specifies timeout for connection to master.
func (s *ClusterStateService) MasterTimeout(masterTimeout string) *ClusterStateService {
s.masterTimeout = masterTimeout
return s
}
// buildURL builds the URL for the operation.
func (s *ClusterStateService) buildURL() (string, url.Values, error) {
// Build URL
metrics := strings.Join(s.metrics, ",")
if metrics == "" {
metrics = "_all"
}
indices := strings.Join(s.indices, ",")
if indices == "" {
indices = "_all"
}
path, err := uritemplates.Expand("/_cluster/state/{metrics}/{indices}", map[string]string{
"metrics": metrics,
"indices": indices,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.allowNoIndices != nil {
params.Set("allow_no_indices", fmt.Sprintf("%v", *s.allowNoIndices))
}
if s.expandWildcards != "" {
params.Set("expand_wildcards", s.expandWildcards)
}
if s.flatSettings != nil {
params.Set("flat_settings", fmt.Sprintf("%v", *s.flatSettings))
}
if s.ignoreUnavailable != nil {
params.Set("ignore_unavailable", fmt.Sprintf("%v", *s.ignoreUnavailable))
}
if s.local != nil {
params.Set("local", fmt.Sprintf("%v", *s.local))
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *ClusterStateService) Validate() error {
return nil
}
// Do executes the operation.
func (s *ClusterStateService) Do(ctx context.Context) (*ClusterStateResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(ClusterStateResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// ClusterStateResponse is the response of ClusterStateService.Do.
type ClusterStateResponse struct {
ClusterName string `json:"cluster_name"`
ClusterUUID string `json:"cluster_uuid"`
Version int64 `json:"version"`
StateUUID string `json:"state_uuid"`
MasterNode string `json:"master_node"`
Blocks map[string]*clusterBlocks `json:"blocks"`
Nodes map[string]*discoveryNode `json:"nodes"`
Metadata *clusterStateMetadata `json:"metadata"`
RoutingTable *clusterStateRoutingTable `json:"routing_table"`
RoutingNodes *clusterStateRoutingNode `json:"routing_nodes"`
Snapshots map[string]interface{} `json:"snapshots"`
SnapshotDeletions map[string]interface{} `json:"snapshot_deletions"`
Customs map[string]interface{} `json:"customs"`
}
type clusterBlocks struct {
Global map[string]*clusterBlock `json:"global"` // id -> cluster block
Indices map[string]*clusterBlock `json:"indices"` // index name -> cluster block
}
type clusterBlock struct {
Description string `json:"description"`
Retryable bool `json:"retryable"`
DisableStatePersistence bool `json:"disable_state_persistence"`
Levels []string `json:"levels"`
}
type clusterStateMetadata struct {
ClusterUUID string `json:"cluster_uuid"`
ClusterUUIDCommitted bool `json:"cluster_uuid_committed"`
ClusterCoordination *clusterCoordinationMetaData `json:"cluster_coordination"`
Templates map[string]*indexTemplateMetaData `json:"templates"` // template name -> index template metadata
Indices map[string]*indexMetaData `json:"indices"` // index name _> meta data
RoutingTable struct {
Indices map[string]*indexRoutingTable `json:"indices"` // index name -> routing table
} `json:"routing_table"`
RoutingNodes struct {
Unassigned []*shardRouting `json:"unassigned"`
Nodes []*shardRouting `json:"nodes"`
} `json:"routing_nodes"`
DataStream map[string]interface{} `json:"data_stream,omitempty"`
Customs map[string]interface{} `json:"customs"`
Ingest map[string]interface{} `json:"ingest"`
StoredScripts map[string]interface{} `json:"stored_scripts"`
IndexGraveyard map[string]interface{} `json:"index-graveyard"`
IndexLifecycle map[string]interface{} `json:"index_lifecycle"`
Repositories map[string]interface{} `json:"repositories"`
IndexTemplate map[string]interface{} `json:"index_template"`
PersistentTasks map[string]interface{} `json:"persistent_tasks"`
ComponentTemplate map[string]interface{} `json:"component_template"`
}
type clusterCoordinationMetaData struct {
Term int64 `json:"term"`
LastCommittedConfig interface{} `json:"last_committed_config,omitempty"`
LastAcceptedConfig interface{} `json:"last_accepted_config,omitempty"`
VotingConfigExclusions []interface{} `json:"voting_config_exclusions,omitempty"`
}
type discoveryNode struct {
Name string `json:"name"` // server name, e.g. "es1"
EphemeralID string `json:"ephemeral_id"` // e.g. "paHSLpn6QyuVy_n-GM1JAQ"
TransportAddress string `json:"transport_address"` // e.g. inet[/1.2.3.4:9300]
Attributes map[string]interface{} `json:"attributes"` // e.g. { "data": true, "master": true }
Roles []string `json:"roles,omitempty"` // e.g. ["data","data_cold","master",...]
}
type clusterStateRoutingTable struct {
Indices map[string]interface{} `json:"indices"`
}
type clusterStateRoutingNode struct {
Unassigned []*shardRouting `json:"unassigned"`
// Node Id -> shardRouting
Nodes map[string][]*shardRouting `json:"nodes"`
}
type indexTemplateMetaData struct {
IndexPatterns []string `json:"index_patterns"` // e.g. ["store-*"]
Order int `json:"order"`
Version int `json:"version"`
Settings map[string]interface{} `json:"settings"` // index settings
Mappings map[string]interface{} `json:"mappings"` // type name -> mapping
Aliases map[string]interface{} `json:"aliases"`
}
type indexMetaData struct {
State string `json:"state"`
Settings map[string]interface{} `json:"settings"`
Mappings map[string]interface{} `json:"mappings"`
Aliases []string `json:"aliases"` // e.g. [ "alias1", "alias2" ]
PrimaryTerms map[string]interface{} `json:"primary_terms"`
InSyncAllocations map[string]interface{} `json:"in_sync_allocations"`
Version int `json:"version"`
MappingVersion int `json:"mapping_version"`
SettingsVersion int `json:"settings_version"`
AliasesVersion int `json:"aliases_version"`
RoutingNumShards int `json:"routing_num_shards"`
RolloverInfo interface{} `json:"rollover_info,omitempty"`
System interface{} `json:"system,omitempty"`
TimestampRange interface{} `json:"timestamp_range,omitempty"`
ILM map[string]interface{} `json:"ilm,omitempty"`
}
type indexRoutingTable struct {
Shards map[string]*shardRouting `json:"shards"`
}
type shardRouting struct {
State string `json:"state"`
Primary bool `json:"primary"`
Node string `json:"node"`
RelocatingNode string `json:"relocating_node"`
Shard int `json:"shard"`
Index string `json:"index"`
Version int64 `json:"version"`
RestoreSource *RestoreSource `json:"restore_source"`
AllocationId *allocationId `json:"allocation_id"`
UnassignedInfo *unassignedInfo `json:"unassigned_info"`
}
type RestoreSource struct {
Repository string `json:"repository"`
Snapshot string `json:"snapshot"`
Version string `json:"version"`
Index string `json:"index"`
}
type allocationId struct {
Id string `json:"id"`
RelocationId string `json:"relocation_id"`
}
type unassignedInfo struct {
Reason string `json:"reason"`
At string `json:"at"`
Details string `json:"details"`
}
================================================
FILE: cluster_state_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"net/url"
"testing"
)
func TestClusterState(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
// Get cluster state
res, err := client.ClusterState().Index("_all").Metric("_all").Pretty(true).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatalf("expected res to be != nil; got: %v", res)
}
if res.ClusterName == "" {
t.Fatalf("expected a cluster name; got: %q", res.ClusterName)
}
}
func TestClusterStateURLs(t *testing.T) {
tests := []struct {
Service *ClusterStateService
ExpectedPath string
ExpectedParams url.Values
}{
{
Service: &ClusterStateService{
indices: []string{},
metrics: []string{},
},
ExpectedPath: "/_cluster/state/_all/_all",
},
{
Service: &ClusterStateService{
indices: []string{"twitter"},
metrics: []string{},
},
ExpectedPath: "/_cluster/state/_all/twitter",
},
{
Service: &ClusterStateService{
indices: []string{"twitter", "gplus"},
metrics: []string{},
},
ExpectedPath: "/_cluster/state/_all/twitter%2Cgplus",
},
{
Service: &ClusterStateService{
indices: []string{},
metrics: []string{"nodes"},
},
ExpectedPath: "/_cluster/state/nodes/_all",
},
{
Service: &ClusterStateService{
indices: []string{"twitter"},
metrics: []string{"nodes"},
},
ExpectedPath: "/_cluster/state/nodes/twitter",
},
{
Service: &ClusterStateService{
indices: []string{"twitter"},
metrics: []string{"nodes"},
masterTimeout: "1s",
},
ExpectedPath: "/_cluster/state/nodes/twitter",
ExpectedParams: url.Values{"master_timeout": []string{"1s"}},
},
}
for _, test := range tests {
gotPath, gotParams, err := test.Service.buildURL()
if err != nil {
t.Fatalf("expected no error; got: %v", err)
}
if gotPath != test.ExpectedPath {
t.Errorf("expected URL path = %q; got: %q", test.ExpectedPath, gotPath)
}
if gotParams.Encode() != test.ExpectedParams.Encode() {
t.Errorf("expected URL params = %v; got: %v", test.ExpectedParams, gotParams)
}
}
}
func TestClusterStateGet(t *testing.T) {
client := setupTestClientAndCreateIndex(t) //, SetTraceLog(log.New(os.Stdout, "", 0)))
state, err := client.ClusterState().Pretty(true).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if want, have := "elasticsearch", state.ClusterName; want != have {
t.Fatalf("ClusterName: want %q, have %q", want, have)
}
}
================================================
FILE: cluster_stats.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// ClusterStatsService is documented at
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/cluster-stats.html.
type ClusterStatsService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
nodeId []string
flatSettings *bool
}
// NewClusterStatsService creates a new ClusterStatsService.
func NewClusterStatsService(client *Client) *ClusterStatsService {
return &ClusterStatsService{
client: client,
nodeId: make([]string, 0),
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *ClusterStatsService) Pretty(pretty bool) *ClusterStatsService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *ClusterStatsService) Human(human bool) *ClusterStatsService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *ClusterStatsService) ErrorTrace(errorTrace bool) *ClusterStatsService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *ClusterStatsService) FilterPath(filterPath ...string) *ClusterStatsService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *ClusterStatsService) Header(name string, value string) *ClusterStatsService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *ClusterStatsService) Headers(headers http.Header) *ClusterStatsService {
s.headers = headers
return s
}
// NodeId is documented as: A comma-separated list of node IDs or names to limit the returned information; use `_local` to return information from the node you're connecting to, leave empty to get information from all nodes.
func (s *ClusterStatsService) NodeId(nodeId []string) *ClusterStatsService {
s.nodeId = nodeId
return s
}
// FlatSettings is documented as: Return settings in flat format (default: false).
func (s *ClusterStatsService) FlatSettings(flatSettings bool) *ClusterStatsService {
s.flatSettings = &flatSettings
return s
}
// buildURL builds the URL for the operation.
func (s *ClusterStatsService) buildURL() (string, url.Values, error) {
// Build URL
var err error
var path string
if len(s.nodeId) > 0 {
path, err = uritemplates.Expand("/_cluster/stats/nodes/{node_id}", map[string]string{
"node_id": strings.Join(s.nodeId, ","),
})
if err != nil {
return "", url.Values{}, err
}
} else {
path, err = uritemplates.Expand("/_cluster/stats", map[string]string{})
if err != nil {
return "", url.Values{}, err
}
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.flatSettings != nil {
params.Set("flat_settings", fmt.Sprintf("%v", *s.flatSettings))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *ClusterStatsService) Validate() error {
return nil
}
// Do executes the operation.
func (s *ClusterStatsService) Do(ctx context.Context) (*ClusterStatsResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(ClusterStatsResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// ClusterStatsResponse is the response of ClusterStatsService.Do.
type ClusterStatsResponse struct {
NodesStats *ClusterStatsNodesResponse `json:"_nodes,omitempty"`
Timestamp int64 `json:"timestamp"`
ClusterName string `json:"cluster_name"`
ClusterUUID string `json:"cluster_uuid"`
Status string `json:"status,omitempty"` // e.g. green
Indices *ClusterStatsIndices `json:"indices"`
Nodes *ClusterStatsNodes `json:"nodes"`
}
type ClusterStatsNodesResponse struct {
Total int `json:"total"`
Successful int `json:"successful"`
Failed int `json:"failed"`
Failures []*FailedNodeException `json:"failures,omitempty"`
}
type ClusterStatsIndices struct {
Count int `json:"count"` // number of indices
Shards *ClusterStatsIndicesShards `json:"shards"`
Docs *ClusterStatsIndicesDocs `json:"docs"`
Store *ClusterStatsIndicesStore `json:"store"`
FieldData *ClusterStatsIndicesFieldData `json:"fielddata"`
QueryCache *ClusterStatsIndicesQueryCache `json:"query_cache"`
Completion *ClusterStatsIndicesCompletion `json:"completion"`
Segments *IndexStatsSegments `json:"segments"`
Analysis *ClusterStatsAnalysisStats `json:"analysis"`
Mappings *ClusterStatsMappingStats `json:"mappings"`
Versions []*ClusterStatsVersionStats `json:"versions"`
}
type ClusterStatsAnalysisStats struct {
CharFilterTypes []IndexFeatureStats `json:"char_filter_types,omitempty"`
TokenizerTypes []IndexFeatureStats `json:"tokenizer_types,omitempty"`
FilterTypes []IndexFeatureStats `json:"filter_types,omitempty"`
AnalyzerTypes []IndexFeatureStats `json:"analyzer_types,omitempty"`
BuiltInCharFilters []IndexFeatureStats `json:"built_in_char_filters,omitempty"`
BuiltInTokenizers []IndexFeatureStats `json:"built_in_tokenizers,omitempty"`
BuiltInFilters []IndexFeatureStats `json:"built_in_filters,omitempty"`
BuiltInAnalyzers []IndexFeatureStats `json:"built_in_analyzers,omitempty"`
}
type ClusterStatsMappingStats struct {
FieldTypes []IndexFeatureStats `json:"field_types"`
RuntimeFieldTypes []RuntimeFieldStats `json:"runtime_field_types"`
}
type IndexFeatureStats struct {
Name string `json:"name"`
Count int `json:"count"`
IndexCount int `json:"index_count"`
ScriptCount int `json:"script_count"`
}
type RuntimeFieldStats struct {
Name string `json:"name"`
Count int `json:"count"`
IndexCount int `json:"index_count"`
ScriptlessCount int `json:"scriptless_count"`
ShadowedCount int `json:"shadowed_count"`
Lang []string `json:"lang"`
// FieldScriptStats
LinesMax int64 `json:"lines_max"`
LinesTotal int64 `json:"lines_total"`
CharsMax int64 `json:"chars_max"`
CharsTotal int64 `json:"chars_total"`
SourceMax int64 `json:"source_max"`
SourceTotal int64 `json:"source_total"`
DocMax int64 `json:"doc_max"`
DocTotal int64 `json:"doc_total"`
}
type FieldScriptStats struct {
LinesMax int64 `json:"lines_max"`
LinesTotal int64 `json:"lines_total"`
CharsMax int64 `json:"chars_max"`
CharsTotal int64 `json:"chars_total"`
SourceMax int64 `json:"source_max"`
SourceTotal int64 `json:"source_total"`
DocMax int64 `json:"doc_max"`
DocTotal int64 `json:"doc_total"`
}
type ClusterStatsVersionStats struct {
Version string `json:"version"`
IndexCount int `json:"index_count"`
PrimaryShardCount int `json:"primary_shard_count"`
TotalPrimarySize string `json:"total_primary_size,omitempty"`
TotalPrimaryBytes int64 `json:"total_primary_bytes,omitempty"`
}
type ClusterStatsIndicesShards struct {
Total int `json:"total"`
Primaries int `json:"primaries"`
Replication float64 `json:"replication"`
Index *ClusterStatsIndicesShardsIndex `json:"index"`
}
type ClusterStatsIndicesShardsIndex struct {
Shards *ClusterStatsIndicesShardsIndexIntMinMax `json:"shards"`
Primaries *ClusterStatsIndicesShardsIndexIntMinMax `json:"primaries"`
Replication *ClusterStatsIndicesShardsIndexFloat64MinMax `json:"replication"`
}
type ClusterStatsIndicesShardsIndexIntMinMax struct {
Min int `json:"min"`
Max int `json:"max"`
Avg float64 `json:"avg"`
}
type ClusterStatsIndicesShardsIndexFloat64MinMax struct {
Min float64 `json:"min"`
Max float64 `json:"max"`
Avg float64 `json:"avg"`
}
type ClusterStatsIndicesDocs struct {
Count int `json:"count"`
Deleted int `json:"deleted"`
}
type ClusterStatsIndicesStore struct {
Size string `json:"size"` // e.g. "5.3gb"
SizeInBytes int64 `json:"size_in_bytes"`
TotalDataSetSize string `json:"total_data_set_size,omitempty"`
TotalDataSetSizeInBytes int64 `json:"total_data_set_size_in_bytes,omitempty"`
Reserved string `json:"reserved,omitempty"`
ReservedInBytes int64 `json:"reserved_in_bytes,omitempty"`
}
type ClusterStatsIndicesFieldData struct {
MemorySize string `json:"memory_size"` // e.g. "61.3kb"
MemorySizeInBytes int64 `json:"memory_size_in_bytes"`
Evictions int64 `json:"evictions"`
Fields map[string]struct {
MemorySize string `json:"memory_size"` // e.g. "61.3kb"
MemorySizeInBytes int64 `json:"memory_size_in_bytes"`
} `json:"fields,omitempty"`
}
type ClusterStatsIndicesQueryCache struct {
MemorySize string `json:"memory_size"` // e.g. "61.3kb"
MemorySizeInBytes int64 `json:"memory_size_in_bytes"`
TotalCount int64 `json:"total_count"`
HitCount int64 `json:"hit_count"`
MissCount int64 `json:"miss_count"`
CacheSize int64 `json:"cache_size"`
CacheCount int64 `json:"cache_count"`
Evictions int64 `json:"evictions"`
}
type ClusterStatsIndicesCompletion struct {
Size string `json:"size"` // e.g. "61.3kb"
SizeInBytes int64 `json:"size_in_bytes"`
Fields map[string]struct {
Size string `json:"size"` // e.g. "61.3kb"
SizeInBytes int64 `json:"size_in_bytes"`
} `json:"fields,omitempty"`
}
type ClusterStatsIndicesSegmentsFile struct {
Size string `json:"size"` // e.g. "61.3kb"
SizeInBytes int64 `json:"size_in_bytes"`
Description string `json:"description,omitempty"`
}
// ---
type ClusterStatsNodes struct {
Count *ClusterStatsNodesCount `json:"count"`
Versions []string `json:"versions"`
OS *ClusterStatsNodesOsStats `json:"os"`
Process *ClusterStatsNodesProcessStats `json:"process"`
JVM *ClusterStatsNodesJvmStats `json:"jvm"`
FS *ClusterStatsNodesFsStats `json:"fs"`
Plugins []*ClusterStatsNodesPlugin `json:"plugins"`
NetworkTypes *ClusterStatsNodesNetworkTypes `json:"network_types"`
DiscoveryTypes *ClusterStatsNodesDiscoveryTypes `json:"discovery_types"`
PackagingTypes *ClusterStatsNodesPackagingTypes `json:"packaging_types"`
Ingest *ClusterStatsNodesIngest `json:"ingest"`
}
type ClusterStatsNodesCount struct {
Total int `json:"total"`
Data int `json:"data"`
DataCold int `json:"data_cold"`
DataContent int `json:"data_content"`
DataFrozen int `json:"data_frozen"`
DataHot int `json:"data_hot"`
DataWarm int `json:"data_warm"`
CoordinatingOnly int `json:"coordinating_only"`
Master int `json:"master"`
Ingest int `json:"ingest"`
ML int `json:"ml"`
RemoteClusterClient int `json:"remote_cluster_client"`
Transform int `json:"transform"`
VotingOnly int `json:"voting_only"`
}
type ClusterStatsNodesOsStats struct {
AvailableProcessors int `json:"available_processors"`
AllocatedProcessors int `json:"allocated_processors"`
Names []struct {
Name string `json:"name"`
Value int `json:"count"`
} `json:"names"`
PrettyNames []struct {
PrettyName string `json:"pretty_name"`
Value int `json:"count"`
} `json:"pretty_names"`
Mem *ClusterStatsNodesOsStatsMem `json:"mem"`
Architectures []struct {
Arch string `json:"arch"`
Count int `json:"count"`
} `json:"architectures"`
// CPU []*ClusterStatsNodesOsStatsCPU `json:"cpu"`
}
type ClusterStatsNodesOsStatsMem struct {
Total string `json:"total"` // e.g. "16gb"
TotalInBytes int64 `json:"total_in_bytes"`
Free string `json:"free"` // e.g. "12gb"
FreeInBytes int64 `json:"free_in_bytes"`
Used string `json:"used"` // e.g. "4gb"
UsedInBytes int64 `json:"used_in_bytes"`
FreePercent int `json:"free_percent"`
UsedPercent int `json:"used_percent"`
}
type ClusterStatsNodesOsStatsCPU struct {
Vendor string `json:"vendor"`
Model string `json:"model"`
MHz int `json:"mhz"`
TotalCores int `json:"total_cores"`
TotalSockets int `json:"total_sockets"`
CoresPerSocket int `json:"cores_per_socket"`
CacheSize string `json:"cache_size"` // e.g. "256b"
CacheSizeInBytes int64 `json:"cache_size_in_bytes"`
Count int `json:"count"`
}
type ClusterStatsNodesProcessStats struct {
CPU *ClusterStatsNodesProcessStatsCPU `json:"cpu"`
OpenFileDescriptors *ClusterStatsNodesProcessStatsOpenFileDescriptors `json:"open_file_descriptors"`
}
type ClusterStatsNodesProcessStatsCPU struct {
Percent float64 `json:"percent"`
}
type ClusterStatsNodesProcessStatsOpenFileDescriptors struct {
Min int64 `json:"min"`
Max int64 `json:"max"`
Avg int64 `json:"avg"`
}
type ClusterStatsNodesJvmStats struct {
MaxUptime string `json:"max_uptime"` // e.g. "5h"
MaxUptimeInMillis int64 `json:"max_uptime_in_millis"`
Versions []*ClusterStatsNodesJvmStatsVersion `json:"versions"`
Mem *ClusterStatsNodesJvmStatsMem `json:"mem"`
Threads int64 `json:"threads"`
}
type ClusterStatsNodesJvmStatsVersion struct {
Version string `json:"version"` // e.g. "1.8.0_45"
VMName string `json:"vm_name"` // e.g. "Java HotSpot(TM) 64-Bit Server VM"
VMVersion string `json:"vm_version"` // e.g. "25.45-b02"
VMVendor string `json:"vm_vendor"` // e.g. "Oracle Corporation"
BundledJDK bool `json:"bundled_jdk"`
UsingBundledJDK bool `json:"using_bundled_jdk"`
Count int `json:"count"`
}
type ClusterStatsNodesJvmStatsMem struct {
HeapUsed string `json:"heap_used"`
HeapUsedInBytes int64 `json:"heap_used_in_bytes"`
HeapMax string `json:"heap_max"`
HeapMaxInBytes int64 `json:"heap_max_in_bytes"`
}
type ClusterStatsNodesFsStats struct {
Path string `json:"path"`
Mount string `json:"mount"`
Dev string `json:"dev"`
Total string `json:"total"` // e.g. "930.7gb"`
TotalInBytes int64 `json:"total_in_bytes"`
Free string `json:"free"` // e.g. "930.7gb"`
FreeInBytes int64 `json:"free_in_bytes"`
Available string `json:"available"` // e.g. "930.7gb"`
AvailableInBytes int64 `json:"available_in_bytes"`
DiskReads int64 `json:"disk_reads"`
DiskWrites int64 `json:"disk_writes"`
DiskIOOp int64 `json:"disk_io_op"`
DiskReadSize string `json:"disk_read_size"` // e.g. "0b"`
DiskReadSizeInBytes int64 `json:"disk_read_size_in_bytes"`
DiskWriteSize string `json:"disk_write_size"` // e.g. "0b"`
DiskWriteSizeInBytes int64 `json:"disk_write_size_in_bytes"`
DiskIOSize string `json:"disk_io_size"` // e.g. "0b"`
DiskIOSizeInBytes int64 `json:"disk_io_size_in_bytes"`
DiskQueue string `json:"disk_queue"`
DiskServiceTime string `json:"disk_service_time"`
}
type ClusterStatsNodesPlugin struct {
Name string `json:"name"`
Version string `json:"version"`
Description string `json:"description"`
URL string `json:"url"`
JVM bool `json:"jvm"`
Site bool `json:"site"`
}
type ClusterStatsNodesNetworkTypes struct {
TransportTypes map[string]interface{} `json:"transport_types"` // e.g. "netty4": 1
HTTPTypes map[string]interface{} `json:"http_types"` // e.g. "netty4": 1
}
type ClusterStatsNodesDiscoveryTypes interface{}
type ClusterStatsNodesPackagingTypes []*ClusterStatsNodesPackagingType
type ClusterStatsNodesPackagingType struct {
Flavor string `json:"flavor"` // e.g. "oss"
Type string `json:"type"` // e.g. "docker"
Count int `json:"count"` // e.g. 1
}
type ClusterStatsNodesIngest struct {
NumberOfPipelines int `json:"number_of_pipelines"`
ProcessorStats map[string]interface{} `json:"processor_stats"`
}
================================================
FILE: cluster_stats_integration_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestClusterStats(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t) //, SetTraceLog(log.New(os.Stdout, "", 0)))
// Get cluster stats
res, err := client.ClusterStats().Human(true).Pretty(true).Do(context.Background())
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatalf("expected res to be != nil; got: %v", res)
}
if res.ClusterName == "" {
t.Fatalf("expected a cluster name; got: %q", res.ClusterName)
}
if res.Nodes == nil {
t.Fatalf("expected nodes; got: %v", res.Nodes)
}
if res.Nodes.Count == nil {
t.Fatalf("expected nodes count; got: %v", res.Nodes.Count)
}
}
================================================
FILE: cluster_stats_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"net/url"
"testing"
)
func TestClusterStatsURLs(t *testing.T) {
fFlag := false
tFlag := true
tests := []struct {
Service *ClusterStatsService
ExpectedPath string
ExpectedParams url.Values
}{
{
Service: &ClusterStatsService{
nodeId: []string{},
},
ExpectedPath: "/_cluster/stats",
},
{
Service: &ClusterStatsService{
nodeId: []string{"node1"},
},
ExpectedPath: "/_cluster/stats/nodes/node1",
},
{
Service: &ClusterStatsService{
nodeId: []string{"node1", "node2"},
},
ExpectedPath: "/_cluster/stats/nodes/node1%2Cnode2",
},
{
Service: &ClusterStatsService{
nodeId: []string{},
flatSettings: &tFlag,
},
ExpectedPath: "/_cluster/stats",
ExpectedParams: url.Values{"flat_settings": []string{"true"}},
},
{
Service: &ClusterStatsService{
nodeId: []string{"node1"},
flatSettings: &fFlag,
},
ExpectedPath: "/_cluster/stats/nodes/node1",
ExpectedParams: url.Values{"flat_settings": []string{"false"}},
},
}
for _, test := range tests {
gotPath, gotParams, err := test.Service.buildURL()
if err != nil {
t.Fatalf("expected no error; got: %v", err)
}
if gotPath != test.ExpectedPath {
t.Errorf("expected URL path = %q; got: %q", test.ExpectedPath, gotPath)
}
if gotParams.Encode() != test.ExpectedParams.Encode() {
t.Errorf("expected URL params = %v; got: %v", test.ExpectedParams, gotParams)
}
}
}
// -- TestClusterStatsErrorResponse --
var clusterStatsErrorResponseTests = []struct {
Body string
ExpectedNodesStatsFailures int
}{
// #0
{
Body: `{
"_nodes": {
"total": 2,
"successful": 1,
"failed": 1,
"failures": [
{
"type": "failed_node_exception",
"reason": "Failed node [mhUZF1sPTcu2b-pIJfqQRg]",
"node_id": "mhUZF1sPTcu2b-pIJfqQRg",
"caused_by": {
"type": "node_not_connected_exception",
"reason": "[es02][172.27.0.2:9300] Node not connected"
}
}
]
},
"cluster_name": "es-docker-cluster",
"cluster_uuid": "r-OkEGlJTFOE8wP36G-VSg",
"timestamp": 1621834319499,
"indices": {
"count": 0,
"shards": {},
"docs": {
"count": 0,
"deleted": 0
},
"store": {
"size_in_bytes": 0,
"reserved_in_bytes": 0
},
"fielddata": {
"memory_size_in_bytes": 0,
"evictions": 0
},
"query_cache": {
"memory_size_in_bytes": 0,
"total_count": 0,
"hit_count": 0,
"miss_count": 0,
"cache_size": 0,
"cache_count": 0,
"evictions": 0
},
"completion": {
"size_in_bytes": 0
},
"segments": {
"count": 0,
"memory_in_bytes": 0,
"terms_memory_in_bytes": 0,
"stored_fields_memory_in_bytes": 0,
"term_vectors_memory_in_bytes": 0,
"norms_memory_in_bytes": 0,
"points_memory_in_bytes": 0,
"doc_values_memory_in_bytes": 0,
"index_writer_memory_in_bytes": 0,
"version_map_memory_in_bytes": 0,
"fixed_bit_set_memory_in_bytes": 0,
"max_unsafe_auto_id_timestamp": -9223372036854775808,
"file_sizes": {}
},
"mappings": {
"field_types": []
},
"analysis": {
"char_filter_types": [],
"tokenizer_types": [],
"filter_types": [],
"analyzer_types": [],
"built_in_char_filters": [],
"built_in_tokenizers": [],
"built_in_filters": [],
"built_in_analyzers": []
}
},
"nodes": {
"count": {
"total": 1,
"coordinating_only": 0,
"data": 1,
"data_cold": 1,
"data_content": 1,
"data_hot": 1,
"data_warm": 1,
"ingest": 1,
"master": 1,
"ml": 1,
"remote_cluster_client": 1,
"transform": 1,
"voting_only": 0
},
"versions": [
"7.10.0"
],
"os": {
"available_processors": 6,
"allocated_processors": 6,
"names": [
{
"name": "Linux",
"count": 1
}
],
"pretty_names": [
{
"pretty_name": "CentOS Linux 8 (Core)",
"count": 1
}
],
"mem": {
"total_in_bytes": 2084679680,
"free_in_bytes": 590282752,
"used_in_bytes": 1494396928,
"free_percent": 28,
"used_percent": 72
}
},
"process": {
"cpu": {
"percent": 0
},
"open_file_descriptors": {
"min": 260,
"max": 260,
"avg": 260
}
},
"jvm": {
"max_uptime_in_millis": 1042623,
"versions": [
{
"version": "15.0.1",
"vm_name": "OpenJDK 64-Bit Server VM",
"vm_version": "15.0.1+9",
"vm_vendor": "AdoptOpenJDK",
"bundled_jdk": true,
"using_bundled_jdk": true,
"count": 1
}
],
"mem": {
"heap_used_in_bytes": 208299344,
"heap_max_in_bytes": 314572800
},
"threads": 32
},
"fs": {
"total_in_bytes": 62725623808,
"free_in_bytes": 26955173888,
"available_in_bytes": 23738458112
},
"plugins": [],
"network_types": {
"transport_types": {
"security4": 1
},
"http_types": {
"security4": 1
}
},
"discovery_types": {
"zen": 1
},
"packaging_types": [
{
"flavor": "default",
"type": "docker",
"count": 1
}
],
"ingest": {
"number_of_pipelines": 0,
"processor_stats": {}
}
}
}`,
ExpectedNodesStatsFailures: 1,
},
// #1 7.13.2 happy path
{
Body: `{
"_nodes" : {
"total" : 3,
"successful" : 3,
"failed" : 0
},
"cluster_name" : "elasticsearch",
"cluster_uuid" : "8TTeQMxRSZmffmYcTjP21w",
"timestamp" : 1625645280402,
"status" : "green",
"indices" : {
"count" : 0,
"shards" : { },
"docs" : {
"count" : 0,
"deleted" : 0
},
"store" : {
"size_in_bytes" : 0,
"total_data_set_size_in_bytes" : 0,
"reserved_in_bytes" : 0
},
"fielddata" : {
"memory_size_in_bytes" : 0,
"evictions" : 0
},
"query_cache" : {
"memory_size_in_bytes" : 0,
"total_count" : 0,
"hit_count" : 0,
"miss_count" : 0,
"cache_size" : 0,
"cache_count" : 0,
"evictions" : 0
},
"completion" : {
"size_in_bytes" : 0
},
"segments" : {
"count" : 0,
"memory_in_bytes" : 0,
"terms_memory_in_bytes" : 0,
"stored_fields_memory_in_bytes" : 0,
"term_vectors_memory_in_bytes" : 0,
"norms_memory_in_bytes" : 0,
"points_memory_in_bytes" : 0,
"doc_values_memory_in_bytes" : 0,
"index_writer_memory_in_bytes" : 0,
"version_map_memory_in_bytes" : 0,
"fixed_bit_set_memory_in_bytes" : 0,
"max_unsafe_auto_id_timestamp" : -9223372036854775808,
"file_sizes" : { }
},
"mappings" : {
"field_types" : [ ],
"runtime_field_types" : [ ]
},
"analysis" : {
"char_filter_types" : [ ],
"tokenizer_types" : [ ],
"filter_types" : [ ],
"analyzer_types" : [ ],
"built_in_char_filters" : [ ],
"built_in_tokenizers" : [ ],
"built_in_filters" : [ ],
"built_in_analyzers" : [ ]
},
"versions" : [ ]
},
"nodes" : {
"count" : {
"total" : 3,
"coordinating_only" : 0,
"data" : 3,
"data_cold" : 3,
"data_content" : 3,
"data_frozen" : 3,
"data_hot" : 3,
"data_warm" : 3,
"ingest" : 3,
"master" : 3,
"ml" : 3,
"remote_cluster_client" : 3,
"transform" : 3,
"voting_only" : 0
},
"versions" : [
"7.13.2"
],
"os" : {
"available_processors" : 12,
"allocated_processors" : 12,
"names" : [
{
"name" : "Linux",
"count" : 3
}
],
"pretty_names" : [
{
"pretty_name" : "CentOS Linux 8",
"count" : 3
}
],
"architectures" : [
{
"arch" : "aarch64",
"count" : 3
}
],
"mem" : {
"total_in_bytes" : 25013551104,
"free_in_bytes" : 250650624,
"used_in_bytes" : 24762900480,
"free_percent" : 1,
"used_percent" : 99
}
},
"process" : {
"cpu" : {
"percent" : 0
},
"open_file_descriptors" : {
"min" : 329,
"max" : 330,
"avg" : 329
}
},
"jvm" : {
"max_uptime_in_millis" : 119028,
"versions" : [
{
"version" : "16",
"vm_name" : "OpenJDK 64-Bit Server VM",
"vm_version" : "16+36",
"vm_vendor" : "AdoptOpenJDK",
"bundled_jdk" : true,
"using_bundled_jdk" : true,
"count" : 3
}
],
"mem" : {
"heap_used_in_bytes" : 748080592,
"heap_max_in_bytes" : 3221225472
},
"threads" : 87
},
"fs" : {
"total_in_bytes" : 481133838336,
"free_in_bytes" : 459472416768,
"available_in_bytes" : 434940936192
},
"plugins" : [ ],
"network_types" : {
"transport_types" : {
"netty4" : 3
},
"http_types" : {
"netty4" : 3
}
},
"discovery_types" : {
"zen" : 3
},
"packaging_types" : [
{
"flavor" : "default",
"type" : "docker",
"count" : 3
}
],
"ingest" : {
"number_of_pipelines" : 2,
"processor_stats" : {
"gsub" : {
"count" : 0,
"failed" : 0,
"current" : 0,
"time_in_millis" : 0
},
"script" : {
"count" : 0,
"failed" : 0,
"current" : 0,
"time_in_millis" : 0
}
}
}
}
}`,
ExpectedNodesStatsFailures: 0,
},
// #2 7.13.2 failed nodes
{
Body: `{
"_nodes" : {
"total" : 2,
"successful" : 1,
"failed" : 1,
"failures" : [
{
"type" : "failed_node_exception",
"reason" : "Failed node [agq-C6ZPSYmPgNePwb26_Q]",
"node_id" : "agq-C6ZPSYmPgNePwb26_Q",
"caused_by" : {
"type" : "node_not_connected_exception",
"reason" : "[es2][172.23.0.4:9300] Node not connected"
}
}
]
},
"cluster_name" : "elasticsearch",
"cluster_uuid" : "8TTeQMxRSZmffmYcTjP21w",
"timestamp" : 1625645352594,
"indices" : {
"count" : 0,
"shards" : { },
"docs" : {
"count" : 0,
"deleted" : 0
},
"store" : {
"size_in_bytes" : 0,
"total_data_set_size_in_bytes" : 0,
"reserved_in_bytes" : 0
},
"fielddata" : {
"memory_size_in_bytes" : 0,
"evictions" : 0
},
"query_cache" : {
"memory_size_in_bytes" : 0,
"total_count" : 0,
"hit_count" : 0,
"miss_count" : 0,
"cache_size" : 0,
"cache_count" : 0,
"evictions" : 0
},
"completion" : {
"size_in_bytes" : 0
},
"segments" : {
"count" : 0,
"memory_in_bytes" : 0,
"terms_memory_in_bytes" : 0,
"stored_fields_memory_in_bytes" : 0,
"term_vectors_memory_in_bytes" : 0,
"norms_memory_in_bytes" : 0,
"points_memory_in_bytes" : 0,
"doc_values_memory_in_bytes" : 0,
"index_writer_memory_in_bytes" : 0,
"version_map_memory_in_bytes" : 0,
"fixed_bit_set_memory_in_bytes" : 0,
"max_unsafe_auto_id_timestamp" : -9223372036854775808,
"file_sizes" : { }
},
"mappings" : {
"field_types" : [ ],
"runtime_field_types" : [ ]
},
"analysis" : {
"char_filter_types" : [ ],
"tokenizer_types" : [ ],
"filter_types" : [ ],
"analyzer_types" : [ ],
"built_in_char_filters" : [ ],
"built_in_tokenizers" : [ ],
"built_in_filters" : [ ],
"built_in_analyzers" : [ ]
},
"versions" : [ ]
},
"nodes" : {
"count" : {
"total" : 1,
"coordinating_only" : 0,
"data" : 1,
"data_cold" : 1,
"data_content" : 1,
"data_frozen" : 1,
"data_hot" : 1,
"data_warm" : 1,
"ingest" : 1,
"master" : 1,
"ml" : 1,
"remote_cluster_client" : 1,
"transform" : 1,
"voting_only" : 0
},
"versions" : [
"7.13.2"
],
"os" : {
"available_processors" : 4,
"allocated_processors" : 4,
"names" : [
{
"name" : "Linux",
"count" : 1
}
],
"pretty_names" : [
{
"pretty_name" : "CentOS Linux 8",
"count" : 1
}
],
"architectures" : [
{
"arch" : "aarch64",
"count" : 1
}
],
"mem" : {
"total_in_bytes" : 8337850368,
"free_in_bytes" : 2963259392,
"used_in_bytes" : 5374590976,
"free_percent" : 36,
"used_percent" : 64
}
},
"process" : {
"cpu" : {
"percent" : 0
},
"open_file_descriptors" : {
"min" : 279,
"max" : 279,
"avg" : 279
}
},
"jvm" : {
"max_uptime_in_millis" : 189845,
"versions" : [
{
"version" : "16",
"vm_name" : "OpenJDK 64-Bit Server VM",
"vm_version" : "16+36",
"vm_vendor" : "AdoptOpenJDK",
"bundled_jdk" : true,
"using_bundled_jdk" : true,
"count" : 1
}
],
"mem" : {
"heap_used_in_bytes" : 315085776,
"heap_max_in_bytes" : 1073741824
},
"threads" : 34
},
"fs" : {
"total_in_bytes" : 160377946112,
"free_in_bytes" : 153157689344,
"available_in_bytes" : 144980529152
},
"plugins" : [ ],
"network_types" : {
"transport_types" : {
"netty4" : 1
},
"http_types" : {
"netty4" : 1
}
},
"discovery_types" : {
"zen" : 1
},
"packaging_types" : [
{
"flavor" : "default",
"type" : "docker",
"count" : 1
}
],
"ingest" : {
"number_of_pipelines" : 1,
"processor_stats" : {
"gsub" : {
"count" : 0,
"failed" : 0,
"current" : 0,
"time_in_millis" : 0
},
"script" : {
"count" : 0,
"failed" : 0,
"current" : 0,
"time_in_millis" : 0
}
}
}
}
}`,
ExpectedNodesStatsFailures: 1,
},
// #3 7.12.1 happy path
{
Body: `{
"_nodes" : {
"total" : 3,
"successful" : 3,
"failed" : 0
},
"cluster_name" : "elasticsearch",
"cluster_uuid" : "XmKLDCnmRqqK3Ve01uJU0Q",
"timestamp" : 1625645497836,
"status" : "green",
"indices" : {
"count" : 0,
"shards" : { },
"docs" : {
"count" : 0,
"deleted" : 0
},
"store" : {
"size_in_bytes" : 0,
"reserved_in_bytes" : 0
},
"fielddata" : {
"memory_size_in_bytes" : 0,
"evictions" : 0
},
"query_cache" : {
"memory_size_in_bytes" : 0,
"total_count" : 0,
"hit_count" : 0,
"miss_count" : 0,
"cache_size" : 0,
"cache_count" : 0,
"evictions" : 0
},
"completion" : {
"size_in_bytes" : 0
},
"segments" : {
"count" : 0,
"memory_in_bytes" : 0,
"terms_memory_in_bytes" : 0,
"stored_fields_memory_in_bytes" : 0,
"term_vectors_memory_in_bytes" : 0,
"norms_memory_in_bytes" : 0,
"points_memory_in_bytes" : 0,
"doc_values_memory_in_bytes" : 0,
"index_writer_memory_in_bytes" : 0,
"version_map_memory_in_bytes" : 0,
"fixed_bit_set_memory_in_bytes" : 0,
"max_unsafe_auto_id_timestamp" : -9223372036854775808,
"file_sizes" : { }
},
"mappings" : {
"field_types" : [ ]
},
"analysis" : {
"char_filter_types" : [ ],
"tokenizer_types" : [ ],
"filter_types" : [ ],
"analyzer_types" : [ ],
"built_in_char_filters" : [ ],
"built_in_tokenizers" : [ ],
"built_in_filters" : [ ],
"built_in_analyzers" : [ ]
},
"versions" : [ ]
},
"nodes" : {
"count" : {
"total" : 3,
"coordinating_only" : 0,
"data" : 3,
"data_cold" : 3,
"data_content" : 3,
"data_frozen" : 3,
"data_hot" : 3,
"data_warm" : 3,
"ingest" : 3,
"master" : 3,
"ml" : 3,
"remote_cluster_client" : 3,
"transform" : 3,
"voting_only" : 0
},
"versions" : [
"7.12.1"
],
"os" : {
"available_processors" : 12,
"allocated_processors" : 12,
"names" : [
{
"name" : "Linux",
"count" : 3
}
],
"pretty_names" : [
{
"pretty_name" : "CentOS Linux 8",
"count" : 3
}
],
"architectures" : [
{
"arch" : "aarch64",
"count" : 3
}
],
"mem" : {
"total_in_bytes" : 25013551104,
"free_in_bytes" : 277807104,
"used_in_bytes" : 24735744000,
"free_percent" : 1,
"used_percent" : 99
}
},
"process" : {
"cpu" : {
"percent" : 1
},
"open_file_descriptors" : {
"min" : 332,
"max" : 333,
"avg" : 332
}
},
"jvm" : {
"max_uptime_in_millis" : 36043,
"versions" : [
{
"version" : "16",
"vm_name" : "OpenJDK 64-Bit Server VM",
"vm_version" : "16+36",
"vm_vendor" : "AdoptOpenJDK",
"bundled_jdk" : true,
"using_bundled_jdk" : true,
"count" : 3
}
],
"mem" : {
"heap_used_in_bytes" : 754473472,
"heap_max_in_bytes" : 3221225472
},
"threads" : 91
},
"fs" : {
"total_in_bytes" : 481133838336,
"free_in_bytes" : 457492058112,
"available_in_bytes" : 432960577536
},
"plugins" : [ ],
"network_types" : {
"transport_types" : {
"netty4" : 3
},
"http_types" : {
"netty4" : 3
}
},
"discovery_types" : {
"zen" : 3
},
"packaging_types" : [
{
"flavor" : "default",
"type" : "docker",
"count" : 3
}
],
"ingest" : {
"number_of_pipelines" : 2,
"processor_stats" : {
"gsub" : {
"count" : 0,
"failed" : 0,
"current" : 0,
"time_in_millis" : 0
},
"script" : {
"count" : 0,
"failed" : 0,
"current" : 0,
"time_in_millis" : 0
}
}
}
}
}`,
ExpectedNodesStatsFailures: 0,
},
// #4 7.12.1 failed nodes
{
Body: `{
"_nodes" : {
"total" : 3,
"successful" : 1,
"failed" : 2,
"failures" : [
{
"type" : "failed_node_exception",
"reason" : "Failed node [eE61wq63TxW0yN1ILeoZ5g]",
"node_id" : "eE61wq63TxW0yN1ILeoZ5g",
"caused_by" : {
"type" : "node_not_connected_exception",
"reason" : "[es3][172.24.0.4:9300] Node not connected"
}
},
{
"type" : "failed_node_exception",
"reason" : "Failed node [AAQlfCf0TJ644wJvjaOSyA]",
"node_id" : "AAQlfCf0TJ644wJvjaOSyA",
"caused_by" : {
"type" : "node_not_connected_exception",
"reason" : "[es2][172.24.0.3:9300] Node not connected"
}
}
]
},
"cluster_name" : "elasticsearch",
"cluster_uuid" : "XmKLDCnmRqqK3Ve01uJU0Q",
"timestamp" : 1625645535819,
"indices" : {
"count" : 0,
"shards" : { },
"docs" : {
"count" : 0,
"deleted" : 0
},
"store" : {
"size_in_bytes" : 0,
"reserved_in_bytes" : 0
},
"fielddata" : {
"memory_size_in_bytes" : 0,
"evictions" : 0
},
"query_cache" : {
"memory_size_in_bytes" : 0,
"total_count" : 0,
"hit_count" : 0,
"miss_count" : 0,
"cache_size" : 0,
"cache_count" : 0,
"evictions" : 0
},
"completion" : {
"size_in_bytes" : 0
},
"segments" : {
"count" : 0,
"memory_in_bytes" : 0,
"terms_memory_in_bytes" : 0,
"stored_fields_memory_in_bytes" : 0,
"term_vectors_memory_in_bytes" : 0,
"norms_memory_in_bytes" : 0,
"points_memory_in_bytes" : 0,
"doc_values_memory_in_bytes" : 0,
"index_writer_memory_in_bytes" : 0,
"version_map_memory_in_bytes" : 0,
"fixed_bit_set_memory_in_bytes" : 0,
"max_unsafe_auto_id_timestamp" : -9223372036854775808,
"file_sizes" : { }
},
"mappings" : {
"field_types" : [ ]
},
"analysis" : {
"char_filter_types" : [ ],
"tokenizer_types" : [ ],
"filter_types" : [ ],
"analyzer_types" : [ ],
"built_in_char_filters" : [ ],
"built_in_tokenizers" : [ ],
"built_in_filters" : [ ],
"built_in_analyzers" : [ ]
},
"versions" : [ ]
},
"nodes" : {
"count" : {
"total" : 1,
"coordinating_only" : 0,
"data" : 1,
"data_cold" : 1,
"data_content" : 1,
"data_frozen" : 1,
"data_hot" : 1,
"data_warm" : 1,
"ingest" : 1,
"master" : 1,
"ml" : 1,
"remote_cluster_client" : 1,
"transform" : 1,
"voting_only" : 0
},
"versions" : [
"7.12.1"
],
"os" : {
"available_processors" : 4,
"allocated_processors" : 4,
"names" : [
{
"name" : "Linux",
"count" : 1
}
],
"pretty_names" : [
{
"pretty_name" : "CentOS Linux 8",
"count" : 1
}
],
"architectures" : [
{
"arch" : "aarch64",
"count" : 1
}
],
"mem" : {
"total_in_bytes" : 8337850368,
"free_in_bytes" : 2912473088,
"used_in_bytes" : 5425377280,
"free_percent" : 35,
"used_percent" : 65
}
},
"process" : {
"cpu" : {
"percent" : 0
},
"open_file_descriptors" : {
"min" : 308,
"max" : 308,
"avg" : 308
}
},
"jvm" : {
"max_uptime_in_millis" : 74067,
"versions" : [
{
"version" : "16",
"vm_name" : "OpenJDK 64-Bit Server VM",
"vm_version" : "16+36",
"vm_vendor" : "AdoptOpenJDK",
"bundled_jdk" : true,
"using_bundled_jdk" : true,
"count" : 1
}
],
"mem" : {
"heap_used_in_bytes" : 219877888,
"heap_max_in_bytes" : 1073741824
},
"threads" : 32
},
"fs" : {
"total_in_bytes" : 160377946112,
"free_in_bytes" : 152497557504,
"available_in_bytes" : 144320397312
},
"plugins" : [ ],
"network_types" : {
"transport_types" : {
"netty4" : 1
},
"http_types" : {
"netty4" : 1
}
},
"discovery_types" : {
"zen" : 1
},
"packaging_types" : [
{
"flavor" : "default",
"type" : "docker",
"count" : 1
}
],
"ingest" : {
"number_of_pipelines" : 1,
"processor_stats" : {
"gsub" : {
"count" : 0,
"failed" : 0,
"current" : 0,
"time_in_millis" : 0
},
"script" : {
"count" : 0,
"failed" : 0,
"current" : 0,
"time_in_millis" : 0
}
}
}
}
}`,
ExpectedNodesStatsFailures: 2,
},
// #5 7.10.0 happy path
{
Body: `{
"_nodes" : {
"total" : 3,
"successful" : 3,
"failed" : 0
},
"cluster_name" : "elasticsearch",
"cluster_uuid" : "XvRExZcTTqiiYa8Qa0E6LA",
"timestamp" : 1625645743243,
"status" : "green",
"indices" : {
"count" : 0,
"shards" : { },
"docs" : {
"count" : 0,
"deleted" : 0
},
"store" : {
"size_in_bytes" : 0,
"reserved_in_bytes" : 0
},
"fielddata" : {
"memory_size_in_bytes" : 0,
"evictions" : 0
},
"query_cache" : {
"memory_size_in_bytes" : 0,
"total_count" : 0,
"hit_count" : 0,
"miss_count" : 0,
"cache_size" : 0,
"cache_count" : 0,
"evictions" : 0
},
"completion" : {
"size_in_bytes" : 0
},
"segments" : {
"count" : 0,
"memory_in_bytes" : 0,
"terms_memory_in_bytes" : 0,
"stored_fields_memory_in_bytes" : 0,
"term_vectors_memory_in_bytes" : 0,
"norms_memory_in_bytes" : 0,
"points_memory_in_bytes" : 0,
"doc_values_memory_in_bytes" : 0,
"index_writer_memory_in_bytes" : 0,
"version_map_memory_in_bytes" : 0,
"fixed_bit_set_memory_in_bytes" : 0,
"max_unsafe_auto_id_timestamp" : -9223372036854775808,
"file_sizes" : { }
},
"mappings" : {
"field_types" : [ ]
},
"analysis" : {
"char_filter_types" : [ ],
"tokenizer_types" : [ ],
"filter_types" : [ ],
"analyzer_types" : [ ],
"built_in_char_filters" : [ ],
"built_in_tokenizers" : [ ],
"built_in_filters" : [ ],
"built_in_analyzers" : [ ]
}
},
"nodes" : {
"count" : {
"total" : 3,
"coordinating_only" : 0,
"data" : 3,
"data_cold" : 3,
"data_content" : 3,
"data_hot" : 3,
"data_warm" : 3,
"ingest" : 3,
"master" : 3,
"ml" : 3,
"remote_cluster_client" : 3,
"transform" : 3,
"voting_only" : 0
},
"versions" : [
"7.10.0"
],
"os" : {
"available_processors" : 12,
"allocated_processors" : 12,
"names" : [
{
"name" : "Linux",
"count" : 3
}
],
"pretty_names" : [
{
"pretty_name" : "CentOS Linux 8 (Core)",
"count" : 3
}
],
"mem" : {
"total_in_bytes" : 25013551104,
"free_in_bytes" : 251179008,
"used_in_bytes" : 24762372096,
"free_percent" : 1,
"used_percent" : 99
}
},
"process" : {
"cpu" : {
"percent" : 1
},
"open_file_descriptors" : {
"min" : 304,
"max" : 304,
"avg" : 304
}
},
"jvm" : {
"max_uptime_in_millis" : 31334,
"versions" : [
{
"version" : "15.0.1",
"vm_name" : "OpenJDK 64-Bit Server VM",
"vm_version" : "15.0.1+9",
"vm_vendor" : "AdoptOpenJDK",
"bundled_jdk" : true,
"using_bundled_jdk" : true,
"count" : 3
}
],
"mem" : {
"heap_used_in_bytes" : 746485240,
"heap_max_in_bytes" : 3221225472
},
"threads" : 93
},
"fs" : {
"total_in_bytes" : 481133838336,
"free_in_bytes" : 454960951296,
"available_in_bytes" : 430429470720
},
"plugins" : [ ],
"network_types" : {
"transport_types" : {
"netty4" : 3
},
"http_types" : {
"netty4" : 3
}
},
"discovery_types" : {
"zen" : 3
},
"packaging_types" : [
{
"flavor" : "default",
"type" : "docker",
"count" : 3
}
],
"ingest" : {
"number_of_pipelines" : 2,
"processor_stats" : {
"gsub" : {
"count" : 0,
"failed" : 0,
"current" : 0,
"time_in_millis" : 0
},
"script" : {
"count" : 0,
"failed" : 0,
"current" : 0,
"time_in_millis" : 0
}
}
}
}
}`,
ExpectedNodesStatsFailures: 0,
},
// #6 7.10.1 failed nodes
{
Body: `{
"_nodes" : {
"total" : 3,
"successful" : 1,
"failed" : 2,
"failures" : [
{
"type" : "failed_node_exception",
"reason" : "Failed node [fkiRoRjkQY-oij3qaBW9CQ]",
"node_id" : "fkiRoRjkQY-oij3qaBW9CQ",
"caused_by" : {
"type" : "node_not_connected_exception",
"reason" : "[es2][172.25.0.3:9300] Node not connected"
}
},
{
"type" : "failed_node_exception",
"reason" : "Failed node [-fP8gimVQLu2tQV8luneBQ]",
"node_id" : "-fP8gimVQLu2tQV8luneBQ",
"caused_by" : {
"type" : "node_not_connected_exception",
"reason" : "[es3][172.25.0.4:9300] Node not connected"
}
}
]
},
"cluster_name" : "elasticsearch",
"cluster_uuid" : "XvRExZcTTqiiYa8Qa0E6LA",
"timestamp" : 1625645771492,
"indices" : {
"count" : 0,
"shards" : { },
"docs" : {
"count" : 0,
"deleted" : 0
},
"store" : {
"size_in_bytes" : 0,
"reserved_in_bytes" : 0
},
"fielddata" : {
"memory_size_in_bytes" : 0,
"evictions" : 0
},
"query_cache" : {
"memory_size_in_bytes" : 0,
"total_count" : 0,
"hit_count" : 0,
"miss_count" : 0,
"cache_size" : 0,
"cache_count" : 0,
"evictions" : 0
},
"completion" : {
"size_in_bytes" : 0
},
"segments" : {
"count" : 0,
"memory_in_bytes" : 0,
"terms_memory_in_bytes" : 0,
"stored_fields_memory_in_bytes" : 0,
"term_vectors_memory_in_bytes" : 0,
"norms_memory_in_bytes" : 0,
"points_memory_in_bytes" : 0,
"doc_values_memory_in_bytes" : 0,
"index_writer_memory_in_bytes" : 0,
"version_map_memory_in_bytes" : 0,
"fixed_bit_set_memory_in_bytes" : 0,
"max_unsafe_auto_id_timestamp" : -9223372036854775808,
"file_sizes" : { }
},
"mappings" : {
"field_types" : [ ]
},
"analysis" : {
"char_filter_types" : [ ],
"tokenizer_types" : [ ],
"filter_types" : [ ],
"analyzer_types" : [ ],
"built_in_char_filters" : [ ],
"built_in_tokenizers" : [ ],
"built_in_filters" : [ ],
"built_in_analyzers" : [ ]
}
},
"nodes" : {
"count" : {
"total" : 1,
"coordinating_only" : 0,
"data" : 1,
"data_cold" : 1,
"data_content" : 1,
"data_hot" : 1,
"data_warm" : 1,
"ingest" : 1,
"master" : 1,
"ml" : 1,
"remote_cluster_client" : 1,
"transform" : 1,
"voting_only" : 0
},
"versions" : [
"7.10.0"
],
"os" : {
"available_processors" : 4,
"allocated_processors" : 4,
"names" : [
{
"name" : "Linux",
"count" : 1
}
],
"pretty_names" : [
{
"pretty_name" : "CentOS Linux 8 (Core)",
"count" : 1
}
],
"mem" : {
"total_in_bytes" : 8337850368,
"free_in_bytes" : 2907955200,
"used_in_bytes" : 5429895168,
"free_percent" : 35,
"used_percent" : 65
}
},
"process" : {
"cpu" : {
"percent" : 0
},
"open_file_descriptors" : {
"min" : 253,
"max" : 253,
"avg" : 253
}
},
"jvm" : {
"max_uptime_in_millis" : 59624,
"versions" : [
{
"version" : "15.0.1",
"vm_name" : "OpenJDK 64-Bit Server VM",
"vm_version" : "15.0.1+9",
"vm_vendor" : "AdoptOpenJDK",
"bundled_jdk" : true,
"using_bundled_jdk" : true,
"count" : 1
}
],
"mem" : {
"heap_used_in_bytes" : 308600832,
"heap_max_in_bytes" : 1073741824
},
"threads" : 35
},
"fs" : {
"total_in_bytes" : 160377946112,
"free_in_bytes" : 151653855232,
"available_in_bytes" : 143476695040
},
"plugins" : [ ],
"network_types" : {
"transport_types" : {
"netty4" : 1
},
"http_types" : {
"netty4" : 1
}
},
"discovery_types" : {
"zen" : 1
},
"packaging_types" : [
{
"flavor" : "default",
"type" : "docker",
"count" : 1
}
],
"ingest" : {
"number_of_pipelines" : 1,
"processor_stats" : {
"gsub" : {
"count" : 0,
"failed" : 0,
"current" : 0,
"time_in_millis" : 0
},
"script" : {
"count" : 0,
"failed" : 0,
"current" : 0,
"time_in_millis" : 0
}
}
}
}
}`,
ExpectedNodesStatsFailures: 2,
},
}
func TestClusterStatsErrorResponse(t *testing.T) {
for i, tt := range clusterStatsErrorResponseTests {
var resp ClusterStatsResponse
if err := json.Unmarshal([]byte(tt.Body), &resp); err != nil {
t.Fatal(err)
}
if want, have := tt.ExpectedNodesStatsFailures, len(resp.NodesStats.Failures); want != have {
t.Fatalf("case #%d: expected %d errors, got %d", i, want, have)
}
}
}
================================================
FILE: config/config.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package config
import (
"fmt"
"net/url"
"strconv"
"strings"
)
// Config represents an Elasticsearch configuration.
type Config struct {
URL string
Index string
Username string
Password string
Shards int
Replicas int
Sniff *bool
Healthcheck *bool
Infolog string
Errorlog string
Tracelog string
}
// Parse returns the Elasticsearch configuration by extracting it
// from the URL, its path, and its query string.
//
// Example:
// http://127.0.0.1:9200/store-blobs?shards=1&replicas=0&sniff=false&tracelog=elastic.trace.log
//
// The code above will return a URL of http://127.0.0.1:9200, an index name
// of store-blobs, and the related settings from the query string.
func Parse(elasticURL string) (*Config, error) {
cfg := &Config{
Shards: 1,
Replicas: 0,
Sniff: nil,
}
uri, err := url.Parse(elasticURL)
if err != nil {
return nil, fmt.Errorf("error parsing elastic parameter %q: %v", elasticURL, err)
}
index := strings.TrimSuffix(strings.TrimPrefix(uri.Path, "/"), "/")
if uri.User != nil {
cfg.Username = uri.User.Username()
cfg.Password, _ = uri.User.Password()
}
uri.User = nil
if i, err := strconv.Atoi(uri.Query().Get("shards")); err == nil {
cfg.Shards = i
}
if i, err := strconv.Atoi(uri.Query().Get("replicas")); err == nil {
cfg.Replicas = i
}
if s := uri.Query().Get("sniff"); s != "" {
if b, err := strconv.ParseBool(s); err == nil {
cfg.Sniff = &b
}
}
if s := uri.Query().Get("healthcheck"); s != "" {
if b, err := strconv.ParseBool(s); err == nil {
cfg.Healthcheck = &b
}
}
if s := uri.Query().Get("infolog"); s != "" {
cfg.Infolog = s
}
if s := uri.Query().Get("errorlog"); s != "" {
cfg.Errorlog = s
}
if s := uri.Query().Get("tracelog"); s != "" {
cfg.Tracelog = s
}
uri.Path = ""
uri.RawQuery = ""
cfg.URL = uri.String()
cfg.Index = index
return cfg, nil
}
================================================
FILE: config/config_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package config
import "testing"
func TestParse(t *testing.T) {
urls := "http://user:pwd@elastic:19220/store-blobs?shards=5&replicas=2&sniff=true&healthcheck=false&errorlog=elastic.error.log&infolog=elastic.info.log&tracelog=elastic.trace.log"
cfg, err := Parse(urls)
if err != nil {
t.Fatal(err)
}
if want, got := "http://elastic:19220", cfg.URL; want != got {
t.Fatalf("expected URL = %q, got %q", want, got)
}
if want, got := "store-blobs", cfg.Index; want != got {
t.Fatalf("expected Index = %q, got %q", want, got)
}
if want, got := "user", cfg.Username; want != got {
t.Fatalf("expected Username = %q, got %q", want, got)
}
if want, got := "pwd", cfg.Password; want != got {
t.Fatalf("expected Password = %q, got %q", want, got)
}
if want, got := 5, cfg.Shards; want != got {
t.Fatalf("expected Shards = %v, got %v", want, got)
}
if want, got := 2, cfg.Replicas; want != got {
t.Fatalf("expected Replicas = %v, got %v", want, got)
}
if want, got := true, *cfg.Sniff; want != got {
t.Fatalf("expected Sniff = %v, got %v", want, got)
}
if want, got := false, *cfg.Healthcheck; want != got {
t.Fatalf("expected Healthcheck = %v, got %v", want, got)
}
if want, got := "elastic.error.log", cfg.Errorlog; want != got {
t.Fatalf("expected Errorlog = %q, got %q", want, got)
}
if want, got := "elastic.info.log", cfg.Infolog; want != got {
t.Fatalf("expected Infolog = %q, got %q", want, got)
}
if want, got := "elastic.trace.log", cfg.Tracelog; want != got {
t.Fatalf("expected Tracelog = %q, got %q", want, got)
}
}
func TestParseDoesNotFailWithoutIndexName(t *testing.T) {
urls := "http://user:pwd@elastic:19220/?shards=5&replicas=2&sniff=true&errorlog=elastic.error.log&infolog=elastic.info.log&tracelog=elastic.trace.log"
cfg, err := Parse(urls)
if err != nil {
t.Fatal(err)
}
if want, got := "http://elastic:19220", cfg.URL; want != got {
t.Fatalf("expected URL = %q, got %q", want, got)
}
if want, got := "", cfg.Index; want != got {
t.Fatalf("expected Index = %q, got %q", want, got)
}
}
func TestParseTrimsIndexName(t *testing.T) {
urls := "http://user:pwd@elastic:19220/store-blobs/?sniff=true"
cfg, err := Parse(urls)
if err != nil {
t.Fatal(err)
}
if want, got := "http://elastic:19220", cfg.URL; want != got {
t.Fatalf("expected URL = %q, got %q", want, got)
}
if want, got := "store-blobs", cfg.Index; want != got {
t.Fatalf("expected Index = %q, got %q", want, got)
}
}
================================================
FILE: config/doc.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
/*
Package config allows parsing a configuration for Elasticsearch
from a URL.
*/
package config
================================================
FILE: connection.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"fmt"
"sync"
"time"
)
// conn represents a single connection to a node in a cluster.
type conn struct {
sync.RWMutex
nodeID string // node ID
url string
failures int
dead bool
deadSince *time.Time
}
// newConn creates a new connection to the given URL.
func newConn(nodeID, url string) *conn {
c := &conn{
nodeID: nodeID,
url: url,
}
return c
}
// String returns a representation of the connection status.
func (c *conn) String() string {
c.RLock()
defer c.RUnlock()
return fmt.Sprintf("%s [dead=%v,failures=%d,deadSince=%v]", c.url, c.dead, c.failures, c.deadSince)
}
// NodeID returns the ID of the node of this connection.
func (c *conn) NodeID() string {
c.RLock()
defer c.RUnlock()
return c.nodeID
}
// URL returns the URL of this connection.
func (c *conn) URL() string {
c.RLock()
defer c.RUnlock()
return c.url
}
// IsDead returns true if this connection is marked as dead, i.e. a previous
// request to the URL has been unsuccessful.
func (c *conn) IsDead() bool {
c.RLock()
defer c.RUnlock()
return c.dead
}
// MarkAsDead marks this connection as dead, increments the failures
// counter and stores the current time in dead since.
func (c *conn) MarkAsDead() {
c.Lock()
c.dead = true
if c.deadSince == nil {
utcNow := time.Now().UTC()
c.deadSince = &utcNow
}
c.failures += 1
c.Unlock()
}
// MarkAsAlive marks this connection as eligible to be returned from the
// pool of connections by the selector.
func (c *conn) MarkAsAlive() {
c.Lock()
c.dead = false
c.Unlock()
}
// MarkAsHealthy marks this connection as healthy, i.e. a request has been
// successfully performed with it.
func (c *conn) MarkAsHealthy() {
c.Lock()
c.dead = false
c.deadSince = nil
c.failures = 0
c.Unlock()
}
================================================
FILE: count.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// CountService is a convenient service for determining the
// number of documents in an index. Use SearchService with
// a SearchType of count for counting with queries etc.
type CountService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
index []string
typ []string
allowNoIndices *bool
analyzeWildcard *bool
analyzer string
defaultOperator string
df string
expandWildcards string
ignoreUnavailable *bool
ignoreThrottled *bool
lenient *bool
lowercaseExpandedTerms *bool
minScore interface{}
preference string
q string
query Query
routing string
terminateAfter *int
bodyJson interface{}
bodyString string
}
// NewCountService creates a new CountService.
func NewCountService(client *Client) *CountService {
return &CountService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *CountService) Pretty(pretty bool) *CountService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *CountService) Human(human bool) *CountService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *CountService) ErrorTrace(errorTrace bool) *CountService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *CountService) FilterPath(filterPath ...string) *CountService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *CountService) Header(name string, value string) *CountService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *CountService) Headers(headers http.Header) *CountService {
s.headers = headers
return s
}
// Index sets the names of the indices to restrict the results.
func (s *CountService) Index(index ...string) *CountService {
if s.index == nil {
s.index = make([]string, 0)
}
s.index = append(s.index, index...)
return s
}
// Type sets the types to use to restrict the results.
//
// Deprecated: Types are in the process of being removed. Instead of using a type, prefer to
// filter on a field on the document.
func (s *CountService) Type(typ ...string) *CountService {
if s.typ == nil {
s.typ = make([]string, 0)
}
s.typ = append(s.typ, typ...)
return s
}
// AllowNoIndices indicates whether to ignore if a wildcard indices
// expression resolves into no concrete indices. (This includes "_all" string
// or when no indices have been specified).
func (s *CountService) AllowNoIndices(allowNoIndices bool) *CountService {
s.allowNoIndices = &allowNoIndices
return s
}
// AnalyzeWildcard specifies whether wildcard and prefix queries should be
// analyzed (default: false).
func (s *CountService) AnalyzeWildcard(analyzeWildcard bool) *CountService {
s.analyzeWildcard = &analyzeWildcard
return s
}
// Analyzer specifies the analyzer to use for the query string.
func (s *CountService) Analyzer(analyzer string) *CountService {
s.analyzer = analyzer
return s
}
// DefaultOperator specifies the default operator for query string query (AND or OR).
func (s *CountService) DefaultOperator(defaultOperator string) *CountService {
s.defaultOperator = defaultOperator
return s
}
// Df specifies the field to use as default where no field prefix is given
// in the query string.
func (s *CountService) Df(df string) *CountService {
s.df = df
return s
}
// ExpandWildcards indicates whether to expand wildcard expression to
// concrete indices that are open, closed or both.
func (s *CountService) ExpandWildcards(expandWildcards string) *CountService {
s.expandWildcards = expandWildcards
return s
}
// IgnoreUnavailable indicates whether specified concrete indices should be
// ignored when unavailable (missing or closed).
func (s *CountService) IgnoreUnavailable(ignoreUnavailable bool) *CountService {
s.ignoreUnavailable = &ignoreUnavailable
return s
}
// IgnoreThrottled indicates whether specified concrete, expanded or aliased
// indices should be ignored when throttled.
func (s *CountService) IgnoreThrottled(ignoreThrottled bool) *CountService {
s.ignoreThrottled = &ignoreThrottled
return s
}
// Lenient specifies whether format-based query failures (such as
// providing text to a numeric field) should be ignored.
func (s *CountService) Lenient(lenient bool) *CountService {
s.lenient = &lenient
return s
}
// LowercaseExpandedTerms specifies whether query terms should be lowercased.
func (s *CountService) LowercaseExpandedTerms(lowercaseExpandedTerms bool) *CountService {
s.lowercaseExpandedTerms = &lowercaseExpandedTerms
return s
}
// MinScore indicates to include only documents with a specific `_score`
// value in the result.
func (s *CountService) MinScore(minScore interface{}) *CountService {
s.minScore = minScore
return s
}
// Preference specifies the node or shard the operation should be
// performed on (default: random).
func (s *CountService) Preference(preference string) *CountService {
s.preference = preference
return s
}
// Q in the Lucene query string syntax. You can also use Query to pass
// a Query struct.
func (s *CountService) Q(q string) *CountService {
s.q = q
return s
}
// Query specifies the query to pass. You can also pass a query string with Q.
func (s *CountService) Query(query Query) *CountService {
s.query = query
return s
}
// Routing specifies the routing value.
func (s *CountService) Routing(routing string) *CountService {
s.routing = routing
return s
}
// TerminateAfter indicates the maximum count for each shard, upon reaching
// which the query execution will terminate early.
func (s *CountService) TerminateAfter(terminateAfter int) *CountService {
s.terminateAfter = &terminateAfter
return s
}
// BodyJson specifies the query to restrict the results specified with the
// Query DSL (optional). The interface{} will be serialized to a JSON document,
// so use a map[string]interface{}.
func (s *CountService) BodyJson(body interface{}) *CountService {
s.bodyJson = body
return s
}
// Body specifies a query to restrict the results specified with
// the Query DSL (optional).
func (s *CountService) BodyString(body string) *CountService {
s.bodyString = body
return s
}
// buildURL builds the URL for the operation.
func (s *CountService) buildURL() (string, url.Values, error) {
var err error
var path string
if len(s.index) > 0 && len(s.typ) > 0 {
path, err = uritemplates.Expand("/{index}/{type}/_count", map[string]string{
"index": strings.Join(s.index, ","),
"type": strings.Join(s.typ, ","),
})
} else if len(s.index) > 0 {
path, err = uritemplates.Expand("/{index}/_count", map[string]string{
"index": strings.Join(s.index, ","),
})
} else if len(s.typ) > 0 {
path, err = uritemplates.Expand("/_all/{type}/_count", map[string]string{
"type": strings.Join(s.typ, ","),
})
} else {
path = "/_all/_count"
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.allowNoIndices != nil {
params.Set("allow_no_indices", fmt.Sprintf("%v", *s.allowNoIndices))
}
if s.analyzeWildcard != nil {
params.Set("analyze_wildcard", fmt.Sprintf("%v", *s.analyzeWildcard))
}
if s.analyzer != "" {
params.Set("analyzer", s.analyzer)
}
if s.defaultOperator != "" {
params.Set("default_operator", s.defaultOperator)
}
if s.df != "" {
params.Set("df", s.df)
}
if s.expandWildcards != "" {
params.Set("expand_wildcards", s.expandWildcards)
}
if s.ignoreUnavailable != nil {
params.Set("ignore_unavailable", fmt.Sprintf("%v", *s.ignoreUnavailable))
}
if s.ignoreThrottled != nil {
params.Set("ignore_throttled", fmt.Sprintf("%v", *s.ignoreThrottled))
}
if s.lenient != nil {
params.Set("lenient", fmt.Sprintf("%v", *s.lenient))
}
if s.lowercaseExpandedTerms != nil {
params.Set("lowercase_expanded_terms", fmt.Sprintf("%v", *s.lowercaseExpandedTerms))
}
if s.minScore != nil {
params.Set("min_score", fmt.Sprintf("%v", s.minScore))
}
if s.preference != "" {
params.Set("preference", s.preference)
}
if s.q != "" {
params.Set("q", s.q)
}
if s.routing != "" {
params.Set("routing", s.routing)
}
if s.terminateAfter != nil {
params.Set("terminate_after", fmt.Sprintf("%v", *s.terminateAfter))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *CountService) Validate() error {
return nil
}
// Do executes the operation.
func (s *CountService) Do(ctx context.Context) (int64, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return 0, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return 0, err
}
// Setup HTTP request body
var body interface{}
if s.query != nil {
src, err := s.query.Source()
if err != nil {
return 0, err
}
query := make(map[string]interface{})
query["query"] = src
body = query
} else if s.bodyJson != nil {
body = s.bodyJson
} else if s.bodyString != "" {
body = s.bodyString
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "POST",
Path: path,
Params: params,
Body: body,
Headers: s.headers,
})
if err != nil {
return 0, err
}
// Return result
ret := new(CountResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return 0, err
}
if ret != nil {
return ret.Count, nil
}
return int64(0), nil
}
// CountResponse is the response of using the Count API.
type CountResponse struct {
Count int64 `json:"count"`
TerminatedEarly bool `json:"terminated_early,omitempty"`
Shards *ShardsInfo `json:"_shards,omitempty"`
}
================================================
FILE: count_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestCountURL(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tests := []struct {
Indices []string
Types []string
Expected string
}{
{
[]string{},
[]string{},
"/_all/_count",
},
{
[]string{},
[]string{"tweet"},
"/_all/tweet/_count",
},
{
[]string{"twitter-*"},
[]string{"tweet", "follower"},
"/twitter-%2A/tweet%2Cfollower/_count",
},
{
[]string{"twitter-2014", "twitter-2015"},
[]string{"tweet", "follower"},
"/twitter-2014%2Ctwitter-2015/tweet%2Cfollower/_count",
},
}
for _, test := range tests {
path, _, err := client.Count().Index(test.Indices...).Type(test.Types...).buildURL()
if err != nil {
t.Fatal(err)
}
if path != test.Expected {
t.Errorf("expected %q; got: %q", test.Expected, path)
}
}
}
func TestCount(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
tweet2 := tweet{User: "olivere", Message: "Another unrelated topic."}
tweet3 := tweet{User: "sandrae", Message: "Cycling is fun."}
// Add all documents
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("3").BodyJson(&tweet3).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Count documents
count, err := client.Count(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if count != 3 {
t.Errorf("expected Count = %d; got %d", 3, count)
}
// Count documents
count, err = client.Count(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if count != 3 {
t.Errorf("expected Count = %d; got %d", 3, count)
}
// Count documents
count, err = client.Count(testIndexNameEmpty).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if count != 0 {
t.Errorf("expected Count = %d; got %d", 0, count)
}
// Count with query
query := NewTermQuery("user", "olivere")
count, err = client.Count(testIndexName).Query(query).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if count != 2 {
t.Errorf("expected Count = %d; got %d", 2, count)
}
}
================================================
FILE: decoder.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"bytes"
"encoding/json"
)
// Decoder is used to decode responses from Elasticsearch.
// Users of elastic can implement their own marshaler for advanced purposes
// and set them per Client (see SetDecoder). If none is specified,
// DefaultDecoder is used.
type Decoder interface {
Decode(data []byte, v interface{}) error
}
// DefaultDecoder uses json.Unmarshal from the Go standard library
// to decode JSON data.
type DefaultDecoder struct{}
// Decode decodes with json.Unmarshal from the Go standard library.
func (u *DefaultDecoder) Decode(data []byte, v interface{}) error {
return json.Unmarshal(data, v)
}
// NumberDecoder uses json.NewDecoder, with UseNumber() enabled, from
// the Go standard library to decode JSON data.
type NumberDecoder struct{}
// Decode decodes with json.Unmarshal from the Go standard library.
func (u *NumberDecoder) Decode(data []byte, v interface{}) error {
dec := json.NewDecoder(bytes.NewReader(data))
dec.UseNumber()
return dec.Decode(v)
}
================================================
FILE: decoder_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"bytes"
"context"
"encoding/json"
"sync/atomic"
"testing"
)
type decoder struct {
N int64
}
func (d *decoder) Decode(data []byte, v interface{}) error {
atomic.AddInt64(&d.N, 1)
dec := json.NewDecoder(bytes.NewReader(data))
dec.UseNumber()
return dec.Decode(v)
}
func TestDecoder(t *testing.T) {
dec := &decoder{}
client := setupTestClientAndCreateIndex(t, SetDecoder(dec), SetMaxRetries(0))
tweet := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
// Add a document
indexResult, err := client.Index().
Index(testIndexName).
Id("1").
BodyJson(&tweet).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if indexResult == nil {
t.Errorf("expected result to be != nil; got: %v", indexResult)
}
if dec.N == 0 {
t.Errorf("expected at least 1 call of decoder; got: %d", dec.N)
}
}
================================================
FILE: delete.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// DeleteService allows to delete a typed JSON document from a specified
// index based on its id.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-delete.html
// for details.
type DeleteService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
id string
index string
typ string
routing string
timeout string
version interface{}
versionType string
waitForActiveShards string
parent string
refresh string
ifSeqNo *int64
ifPrimaryTerm *int64
}
// NewDeleteService creates a new DeleteService.
func NewDeleteService(client *Client) *DeleteService {
return &DeleteService{
client: client,
typ: "_doc",
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *DeleteService) Pretty(pretty bool) *DeleteService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *DeleteService) Human(human bool) *DeleteService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *DeleteService) ErrorTrace(errorTrace bool) *DeleteService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *DeleteService) FilterPath(filterPath ...string) *DeleteService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *DeleteService) Header(name string, value string) *DeleteService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *DeleteService) Headers(headers http.Header) *DeleteService {
s.headers = headers
return s
}
// Type is the type of the document.
//
// Deprecated: Types are in the process of being removed.
func (s *DeleteService) Type(typ string) *DeleteService {
s.typ = typ
return s
}
// Id is the document ID.
func (s *DeleteService) Id(id string) *DeleteService {
s.id = id
return s
}
// Index is the name of the index.
func (s *DeleteService) Index(index string) *DeleteService {
s.index = index
return s
}
// Routing is a specific routing value.
func (s *DeleteService) Routing(routing string) *DeleteService {
s.routing = routing
return s
}
// Timeout is an explicit operation timeout.
func (s *DeleteService) Timeout(timeout string) *DeleteService {
s.timeout = timeout
return s
}
// Version is an explicit version number for concurrency control.
func (s *DeleteService) Version(version interface{}) *DeleteService {
s.version = version
return s
}
// VersionType is a specific version type.
func (s *DeleteService) VersionType(versionType string) *DeleteService {
s.versionType = versionType
return s
}
// WaitForActiveShards sets the number of shard copies that must be active
// before proceeding with the delete operation. Defaults to 1, meaning the
// primary shard only. Set to `all` for all shard copies, otherwise set to
// any non-negative value less than or equal to the total number of copies
// for the shard (number of replicas + 1).
func (s *DeleteService) WaitForActiveShards(waitForActiveShards string) *DeleteService {
s.waitForActiveShards = waitForActiveShards
return s
}
// Parent is the ID of parent document.
func (s *DeleteService) Parent(parent string) *DeleteService {
s.parent = parent
return s
}
// Refresh the index after performing the operation.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-refresh.html
// for details.
func (s *DeleteService) Refresh(refresh string) *DeleteService {
s.refresh = refresh
return s
}
// IfSeqNo indicates to only perform the delete operation if the last
// operation that has changed the document has the specified sequence number.
func (s *DeleteService) IfSeqNo(seqNo int64) *DeleteService {
s.ifSeqNo = &seqNo
return s
}
// IfPrimaryTerm indicates to only perform the delete operation if the
// last operation that has changed the document has the specified primary term.
func (s *DeleteService) IfPrimaryTerm(primaryTerm int64) *DeleteService {
s.ifPrimaryTerm = &primaryTerm
return s
}
// buildURL builds the URL for the operation.
func (s *DeleteService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/{index}/{type}/{id}", map[string]string{
"index": s.index,
"type": s.typ,
"id": s.id,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.refresh != "" {
params.Set("refresh", s.refresh)
}
if s.routing != "" {
params.Set("routing", s.routing)
}
if s.timeout != "" {
params.Set("timeout", s.timeout)
}
if v := s.version; v != nil {
params.Set("version", fmt.Sprint(v))
}
if s.versionType != "" {
params.Set("version_type", s.versionType)
}
if s.waitForActiveShards != "" {
params.Set("wait_for_active_shards", s.waitForActiveShards)
}
if s.parent != "" {
params.Set("parent", s.parent)
}
if v := s.ifSeqNo; v != nil {
params.Set("if_seq_no", fmt.Sprintf("%d", *v))
}
if v := s.ifPrimaryTerm; v != nil {
params.Set("if_primary_term", fmt.Sprintf("%d", *v))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *DeleteService) Validate() error {
var invalid []string
if s.typ == "" {
invalid = append(invalid, "Type")
}
if s.id == "" {
invalid = append(invalid, "Id")
}
if s.index == "" {
invalid = append(invalid, "Index")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation. If the document is not found (404), Elasticsearch will
// still return a response. This response is serialized and returned as well. In other
// words, for HTTP status code 404, both an error and a response might be returned.
func (s *DeleteService) Do(ctx context.Context) (*DeleteResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "DELETE",
Path: path,
Params: params,
IgnoreErrors: []int{http.StatusNotFound},
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(DeleteResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
// If we have a 404, we return both a result and an error, just like ES does
if res.StatusCode == http.StatusNotFound {
return ret, &Error{Status: http.StatusNotFound}
}
return ret, nil
}
// -- Result of a delete request.
// DeleteResponse is the outcome of running DeleteService.Do.
type DeleteResponse struct {
Index string `json:"_index,omitempty"`
Type string `json:"_type,omitempty"`
Id string `json:"_id,omitempty"`
Version int64 `json:"_version,omitempty"`
Result string `json:"result,omitempty"`
Shards *ShardsInfo `json:"_shards,omitempty"`
SeqNo int64 `json:"_seq_no,omitempty"`
PrimaryTerm int64 `json:"_primary_term,omitempty"`
Status int `json:"status,omitempty"`
ForcedRefresh bool `json:"forced_refresh,omitempty"`
}
================================================
FILE: delete_by_query.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// DeleteByQueryService deletes documents that match a query.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-delete-by-query.html.
type DeleteByQueryService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
index []string
typ []string
query Query
body interface{}
xSource []string
xSourceExclude []string
xSourceInclude []string
analyzer string
analyzeWildcard *bool
allowNoIndices *bool
conflicts string
defaultOperator string
df string
docvalueFields []string
expandWildcards string
explain *bool
from *int
ignoreUnavailable *bool
lenient *bool
lowercaseExpandedTerms *bool
maxDocs *int
preference string
q string
refresh string
requestCache *bool
requestsPerSecond *int
routing []string
scroll string
scrollSize *int
searchTimeout string
searchType string
size *int
slices interface{}
sort []string
stats []string
storedFields []string
suggestField string
suggestMode string
suggestSize *int
suggestText string
terminateAfter *int
timeout string
trackScores *bool
version *bool
waitForActiveShards string
waitForCompletion *bool
}
// NewDeleteByQueryService creates a new DeleteByQueryService.
// You typically use the client's DeleteByQuery to get a reference to
// the service.
func NewDeleteByQueryService(client *Client) *DeleteByQueryService {
builder := &DeleteByQueryService{
client: client,
}
return builder
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *DeleteByQueryService) Pretty(pretty bool) *DeleteByQueryService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *DeleteByQueryService) Human(human bool) *DeleteByQueryService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *DeleteByQueryService) ErrorTrace(errorTrace bool) *DeleteByQueryService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *DeleteByQueryService) FilterPath(filterPath ...string) *DeleteByQueryService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *DeleteByQueryService) Header(name string, value string) *DeleteByQueryService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *DeleteByQueryService) Headers(headers http.Header) *DeleteByQueryService {
s.headers = headers
return s
}
// Index sets the indices on which to perform the delete operation.
func (s *DeleteByQueryService) Index(index ...string) *DeleteByQueryService {
s.index = append(s.index, index...)
return s
}
// Type limits the delete operation to the given types.
//
// Deprecated: Types are in the process of being removed. Instead of
// using a type, prefer to filter on a field of the document.
func (s *DeleteByQueryService) Type(typ ...string) *DeleteByQueryService {
s.typ = append(s.typ, typ...)
return s
}
// XSource is true or false to return the _source field or not,
// or a list of fields to return.
func (s *DeleteByQueryService) XSource(xSource ...string) *DeleteByQueryService {
s.xSource = append(s.xSource, xSource...)
return s
}
// XSourceExclude represents a list of fields to exclude from the returned _source field.
func (s *DeleteByQueryService) XSourceExclude(xSourceExclude ...string) *DeleteByQueryService {
s.xSourceExclude = append(s.xSourceExclude, xSourceExclude...)
return s
}
// XSourceInclude represents a list of fields to extract and return from the _source field.
func (s *DeleteByQueryService) XSourceInclude(xSourceInclude ...string) *DeleteByQueryService {
s.xSourceInclude = append(s.xSourceInclude, xSourceInclude...)
return s
}
// Analyzer to use for the query string.
func (s *DeleteByQueryService) Analyzer(analyzer string) *DeleteByQueryService {
s.analyzer = analyzer
return s
}
// AnalyzeWildcard specifies whether wildcard and prefix queries should be
// analyzed (default: false).
func (s *DeleteByQueryService) AnalyzeWildcard(analyzeWildcard bool) *DeleteByQueryService {
s.analyzeWildcard = &analyzeWildcard
return s
}
// AllowNoIndices indicates whether to ignore if a wildcard indices
// expression resolves into no concrete indices (including the _all string
// or when no indices have been specified).
func (s *DeleteByQueryService) AllowNoIndices(allow bool) *DeleteByQueryService {
s.allowNoIndices = &allow
return s
}
// Conflicts indicates what to do when the process detects version conflicts.
// Possible values are "proceed" and "abort".
func (s *DeleteByQueryService) Conflicts(conflicts string) *DeleteByQueryService {
s.conflicts = conflicts
return s
}
// AbortOnVersionConflict aborts the request on version conflicts.
// It is an alias to setting Conflicts("abort").
func (s *DeleteByQueryService) AbortOnVersionConflict() *DeleteByQueryService {
s.conflicts = "abort"
return s
}
// ProceedOnVersionConflict proceeds the request on version conflicts.
// It is an alias to setting Conflicts("proceed").
func (s *DeleteByQueryService) ProceedOnVersionConflict() *DeleteByQueryService {
s.conflicts = "proceed"
return s
}
// DefaultOperator for query string query (AND or OR).
func (s *DeleteByQueryService) DefaultOperator(defaultOperator string) *DeleteByQueryService {
s.defaultOperator = defaultOperator
return s
}
// DF is the field to use as default where no field prefix is given in the query string.
func (s *DeleteByQueryService) DF(defaultField string) *DeleteByQueryService {
s.df = defaultField
return s
}
// DefaultField is the field to use as default where no field prefix is given in the query string.
// It is an alias to the DF func.
func (s *DeleteByQueryService) DefaultField(defaultField string) *DeleteByQueryService {
s.df = defaultField
return s
}
// DocvalueFields specifies the list of fields to return as the docvalue representation of a field for each hit.
func (s *DeleteByQueryService) DocvalueFields(docvalueFields ...string) *DeleteByQueryService {
s.docvalueFields = docvalueFields
return s
}
// ExpandWildcards indicates whether to expand wildcard expression to
// concrete indices that are open, closed or both. It can be "open" or "closed".
func (s *DeleteByQueryService) ExpandWildcards(expand string) *DeleteByQueryService {
s.expandWildcards = expand
return s
}
// Explain specifies whether to return detailed information about score
// computation as part of a hit.
func (s *DeleteByQueryService) Explain(explain bool) *DeleteByQueryService {
s.explain = &explain
return s
}
// From is the starting offset (default: 0).
func (s *DeleteByQueryService) From(from int) *DeleteByQueryService {
s.from = &from
return s
}
// IgnoreUnavailable indicates whether specified concrete indices should be
// ignored when unavailable (missing or closed).
func (s *DeleteByQueryService) IgnoreUnavailable(ignore bool) *DeleteByQueryService {
s.ignoreUnavailable = &ignore
return s
}
// Lenient specifies whether format-based query failures
// (such as providing text to a numeric field) should be ignored.
func (s *DeleteByQueryService) Lenient(lenient bool) *DeleteByQueryService {
s.lenient = &lenient
return s
}
// LowercaseExpandedTerms specifies whether query terms should be lowercased.
func (s *DeleteByQueryService) LowercaseExpandedTerms(lowercaseExpandedTerms bool) *DeleteByQueryService {
s.lowercaseExpandedTerms = &lowercaseExpandedTerms
return s
}
// MaxDocs specifies the maximum number of documents to process.
// Defaults to all documents.
func (s *DeleteByQueryService) MaxDocs(maxDocs int) *DeleteByQueryService {
s.maxDocs = &maxDocs
return s
}
// Preference specifies the node or shard the operation should be performed on
// (default: random).
func (s *DeleteByQueryService) Preference(preference string) *DeleteByQueryService {
s.preference = preference
return s
}
// Q specifies the query in Lucene query string syntax. You can also use
// Query to programmatically specify the query.
func (s *DeleteByQueryService) Q(query string) *DeleteByQueryService {
s.q = query
return s
}
// QueryString is an alias to Q. Notice that you can also use Query to
// programmatically set the query.
func (s *DeleteByQueryService) QueryString(query string) *DeleteByQueryService {
s.q = query
return s
}
// Query sets the query programmatically.
func (s *DeleteByQueryService) Query(query Query) *DeleteByQueryService {
s.query = query
return s
}
// Refresh indicates whether the effected indexes should be refreshed.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-refresh.html
// for details.
func (s *DeleteByQueryService) Refresh(refresh string) *DeleteByQueryService {
s.refresh = refresh
return s
}
// RequestCache specifies if request cache should be used for this request
// or not, defaults to index level setting.
func (s *DeleteByQueryService) RequestCache(requestCache bool) *DeleteByQueryService {
s.requestCache = &requestCache
return s
}
// RequestsPerSecond sets the throttle on this request in sub-requests per second.
// -1 means set no throttle as does "unlimited" which is the only non-float this accepts.
func (s *DeleteByQueryService) RequestsPerSecond(requestsPerSecond int) *DeleteByQueryService {
s.requestsPerSecond = &requestsPerSecond
return s
}
// Routing is a list of specific routing values.
func (s *DeleteByQueryService) Routing(routing ...string) *DeleteByQueryService {
s.routing = append(s.routing, routing...)
return s
}
// Scroll specifies how long a consistent view of the index should be maintained
// for scrolled search.
func (s *DeleteByQueryService) Scroll(scroll string) *DeleteByQueryService {
s.scroll = scroll
return s
}
// ScrollSize is the size on the scroll request powering the update_by_query.
func (s *DeleteByQueryService) ScrollSize(scrollSize int) *DeleteByQueryService {
s.scrollSize = &scrollSize
return s
}
// SearchTimeout defines an explicit timeout for each search request.
// Defaults to no timeout.
func (s *DeleteByQueryService) SearchTimeout(searchTimeout string) *DeleteByQueryService {
s.searchTimeout = searchTimeout
return s
}
// SearchType is the search operation type. Possible values are
// "query_then_fetch" and "dfs_query_then_fetch".
func (s *DeleteByQueryService) SearchType(searchType string) *DeleteByQueryService {
s.searchType = searchType
return s
}
// Size represents the number of hits to return (default: 10).
func (s *DeleteByQueryService) Size(size int) *DeleteByQueryService {
s.size = &size
return s
}
// Slices represents the number of slices (default: 1).
// It used to be a number, but can be set to "auto" as of 6.7.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-delete-by-query.html#docs-delete-by-query-automatic-slice
// for details.
func (s *DeleteByQueryService) Slices(slices interface{}) *DeleteByQueryService {
s.slices = slices
return s
}
// Sort is a list of : pairs.
func (s *DeleteByQueryService) Sort(sort ...string) *DeleteByQueryService {
s.sort = append(s.sort, sort...)
return s
}
// SortByField adds a sort order.
func (s *DeleteByQueryService) SortByField(field string, ascending bool) *DeleteByQueryService {
if ascending {
s.sort = append(s.sort, fmt.Sprintf("%s:asc", field))
} else {
s.sort = append(s.sort, fmt.Sprintf("%s:desc", field))
}
return s
}
// Stats specifies specific tag(s) of the request for logging and statistical purposes.
func (s *DeleteByQueryService) Stats(stats ...string) *DeleteByQueryService {
s.stats = append(s.stats, stats...)
return s
}
// StoredFields specifies the list of stored fields to return as part of a hit.
func (s *DeleteByQueryService) StoredFields(storedFields ...string) *DeleteByQueryService {
s.storedFields = storedFields
return s
}
// SuggestField specifies which field to use for suggestions.
func (s *DeleteByQueryService) SuggestField(suggestField string) *DeleteByQueryService {
s.suggestField = suggestField
return s
}
// SuggestMode specifies the suggest mode. Possible values are
// "missing", "popular", and "always".
func (s *DeleteByQueryService) SuggestMode(suggestMode string) *DeleteByQueryService {
s.suggestMode = suggestMode
return s
}
// SuggestSize specifies how many suggestions to return in response.
func (s *DeleteByQueryService) SuggestSize(suggestSize int) *DeleteByQueryService {
s.suggestSize = &suggestSize
return s
}
// SuggestText specifies the source text for which the suggestions should be returned.
func (s *DeleteByQueryService) SuggestText(suggestText string) *DeleteByQueryService {
s.suggestText = suggestText
return s
}
// TerminateAfter indicates the maximum number of documents to collect
// for each shard, upon reaching which the query execution will terminate early.
func (s *DeleteByQueryService) TerminateAfter(terminateAfter int) *DeleteByQueryService {
s.terminateAfter = &terminateAfter
return s
}
// Timeout is the time each individual bulk request should wait for shards
// that are unavailable.
func (s *DeleteByQueryService) Timeout(timeout string) *DeleteByQueryService {
s.timeout = timeout
return s
}
// TimeoutInMillis sets the timeout in milliseconds.
func (s *DeleteByQueryService) TimeoutInMillis(timeoutInMillis int) *DeleteByQueryService {
s.timeout = fmt.Sprintf("%dms", timeoutInMillis)
return s
}
// TrackScores indicates whether to calculate and return scores even if
// they are not used for sorting.
func (s *DeleteByQueryService) TrackScores(trackScores bool) *DeleteByQueryService {
s.trackScores = &trackScores
return s
}
// Version specifies whether to return document version as part of a hit.
func (s *DeleteByQueryService) Version(version bool) *DeleteByQueryService {
s.version = &version
return s
}
// WaitForActiveShards sets the number of shard copies that must be active before proceeding
// with the update by query operation. Defaults to 1, meaning the primary shard only.
// Set to `all` for all shard copies, otherwise set to any non-negative value less than or equal
// to the total number of copies for the shard (number of replicas + 1).
func (s *DeleteByQueryService) WaitForActiveShards(waitForActiveShards string) *DeleteByQueryService {
s.waitForActiveShards = waitForActiveShards
return s
}
// WaitForCompletion indicates if the request should block until the reindex is complete.
func (s *DeleteByQueryService) WaitForCompletion(waitForCompletion bool) *DeleteByQueryService {
s.waitForCompletion = &waitForCompletion
return s
}
// Body specifies the body of the request. It overrides data being specified via SearchService.
func (s *DeleteByQueryService) Body(body string) *DeleteByQueryService {
s.body = body
return s
}
// buildURL builds the URL for the operation.
func (s *DeleteByQueryService) buildURL() (string, url.Values, error) {
// Build URL
var err error
var path string
if len(s.typ) > 0 {
path, err = uritemplates.Expand("/{index}/{type}/_delete_by_query", map[string]string{
"index": strings.Join(s.index, ","),
"type": strings.Join(s.typ, ","),
})
} else {
path, err = uritemplates.Expand("/{index}/_delete_by_query", map[string]string{
"index": strings.Join(s.index, ","),
})
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if len(s.xSource) > 0 {
params.Set("_source", strings.Join(s.xSource, ","))
}
if len(s.xSourceExclude) > 0 {
params.Set("_source_excludes", strings.Join(s.xSourceExclude, ","))
}
if len(s.xSourceInclude) > 0 {
params.Set("_source_includes", strings.Join(s.xSourceInclude, ","))
}
if s.analyzer != "" {
params.Set("analyzer", s.analyzer)
}
if s.analyzeWildcard != nil {
params.Set("analyze_wildcard", fmt.Sprintf("%v", *s.analyzeWildcard))
}
if s.defaultOperator != "" {
params.Set("default_operator", s.defaultOperator)
}
if s.df != "" {
params.Set("df", s.df)
}
if s.explain != nil {
params.Set("explain", fmt.Sprintf("%v", *s.explain))
}
if len(s.storedFields) > 0 {
params.Set("stored_fields", strings.Join(s.storedFields, ","))
}
if len(s.docvalueFields) > 0 {
params.Set("docvalue_fields", strings.Join(s.docvalueFields, ","))
}
if s.from != nil {
params.Set("from", fmt.Sprintf("%d", *s.from))
}
if s.ignoreUnavailable != nil {
params.Set("ignore_unavailable", fmt.Sprintf("%v", *s.ignoreUnavailable))
}
if s.allowNoIndices != nil {
params.Set("allow_no_indices", fmt.Sprintf("%v", *s.allowNoIndices))
}
if s.conflicts != "" {
params.Set("conflicts", s.conflicts)
}
if s.expandWildcards != "" {
params.Set("expand_wildcards", s.expandWildcards)
}
if s.lenient != nil {
params.Set("lenient", fmt.Sprintf("%v", *s.lenient))
}
if s.lowercaseExpandedTerms != nil {
params.Set("lowercase_expanded_terms", fmt.Sprintf("%v", *s.lowercaseExpandedTerms))
}
if s.maxDocs != nil {
params.Set("max_docs", fmt.Sprint(*s.maxDocs))
}
if s.preference != "" {
params.Set("preference", s.preference)
}
if s.q != "" {
params.Set("q", s.q)
}
if len(s.routing) > 0 {
params.Set("routing", strings.Join(s.routing, ","))
}
if s.scroll != "" {
params.Set("scroll", s.scroll)
}
if s.searchType != "" {
params.Set("search_type", s.searchType)
}
if s.searchTimeout != "" {
params.Set("search_timeout", s.searchTimeout)
}
if s.size != nil {
params.Set("size", fmt.Sprintf("%d", *s.size))
}
if s.slices != nil {
params.Set("slices", fmt.Sprintf("%v", s.slices))
}
if len(s.sort) > 0 {
params.Set("sort", strings.Join(s.sort, ","))
}
if s.terminateAfter != nil {
params.Set("terminate_after", fmt.Sprintf("%v", *s.terminateAfter))
}
if len(s.stats) > 0 {
params.Set("stats", strings.Join(s.stats, ","))
}
if s.suggestField != "" {
params.Set("suggest_field", s.suggestField)
}
if s.suggestMode != "" {
params.Set("suggest_mode", s.suggestMode)
}
if s.suggestSize != nil {
params.Set("suggest_size", fmt.Sprintf("%v", *s.suggestSize))
}
if s.suggestText != "" {
params.Set("suggest_text", s.suggestText)
}
if s.timeout != "" {
params.Set("timeout", s.timeout)
}
if s.trackScores != nil {
params.Set("track_scores", fmt.Sprintf("%v", *s.trackScores))
}
if s.version != nil {
params.Set("version", fmt.Sprintf("%v", *s.version))
}
if s.requestCache != nil {
params.Set("request_cache", fmt.Sprintf("%v", *s.requestCache))
}
if s.refresh != "" {
params.Set("refresh", s.refresh)
}
if s.waitForActiveShards != "" {
params.Set("wait_for_active_shards", s.waitForActiveShards)
}
if s.scrollSize != nil {
params.Set("scroll_size", fmt.Sprintf("%d", *s.scrollSize))
}
if s.waitForCompletion != nil {
params.Set("wait_for_completion", fmt.Sprintf("%v", *s.waitForCompletion))
}
if s.requestsPerSecond != nil {
params.Set("requests_per_second", fmt.Sprintf("%v", *s.requestsPerSecond))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *DeleteByQueryService) Validate() error {
var invalid []string
if len(s.index) == 0 {
invalid = append(invalid, "Index")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the delete-by-query operation.
func (s *DeleteByQueryService) Do(ctx context.Context) (*BulkIndexByScrollResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Set body if there is a query set
var body interface{}
if s.body != nil {
body = s.body
} else if s.query != nil {
src, err := s.query.Source()
if err != nil {
return nil, err
}
body = map[string]interface{}{
"query": src,
}
}
// Get response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "POST",
Path: path,
Params: params,
Body: body,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return result
ret := new(BulkIndexByScrollResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// DoAsync executes the delete-by-query operation asynchronously by starting a new task.
// Callers need to use the Task Management API to watch the outcome of the reindexing
// operation.
func (s *DeleteByQueryService) DoAsync(ctx context.Context) (*StartTaskResult, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// DoAsync only makes sense with WaitForCompletion set to true
if s.waitForCompletion != nil && *s.waitForCompletion {
return nil, fmt.Errorf("cannot start a task with WaitForCompletion set to true")
}
f := false
s.waitForCompletion = &f
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Set body if there is a query set
var body interface{}
if s.body != nil {
body = s.body
} else if s.query != nil {
src, err := s.query.Source()
if err != nil {
return nil, err
}
body = map[string]interface{}{
"query": src,
}
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "POST",
Path: path,
Params: params,
Body: body,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(StartTaskResult)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// BulkIndexByScrollResponse is the outcome of executing Do with
// DeleteByQueryService and UpdateByQueryService.
type BulkIndexByScrollResponse struct {
Header http.Header `json:"-"`
Took int64 `json:"took"`
SliceId *int64 `json:"slice_id,omitempty"`
TimedOut bool `json:"timed_out"`
Total int64 `json:"total"`
Updated int64 `json:"updated,omitempty"`
Created int64 `json:"created,omitempty"`
Deleted int64 `json:"deleted"`
Batches int64 `json:"batches"`
VersionConflicts int64 `json:"version_conflicts"`
Noops int64 `json:"noops"`
Retries struct {
Bulk int64 `json:"bulk"`
Search int64 `json:"search"`
} `json:"retries,omitempty"`
Throttled string `json:"throttled"`
ThrottledMillis int64 `json:"throttled_millis"`
RequestsPerSecond float64 `json:"requests_per_second"`
Canceled string `json:"canceled,omitempty"`
ThrottledUntil string `json:"throttled_until"`
ThrottledUntilMillis int64 `json:"throttled_until_millis"`
Failures []bulkIndexByScrollResponseFailure `json:"failures"`
}
type bulkIndexByScrollResponseFailure struct {
Index string `json:"index,omitempty"`
Type string `json:"type,omitempty"`
Id string `json:"id,omitempty"`
Status int `json:"status,omitempty"`
Shard int `json:"shard,omitempty"`
Node int `json:"node,omitempty"`
// TODO "cause" contains exception details
// TODO "reason" contains exception details
}
================================================
FILE: delete_by_query_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestDeleteByQueryBuildURL(t *testing.T) {
client := setupTestClient(t)
tests := []struct {
Indices []string
Types []string
Expected string
ExpectErr bool
}{
{
[]string{},
[]string{},
"",
true,
},
{
[]string{"index1"},
[]string{},
"/index1/_delete_by_query",
false,
},
{
[]string{"index1", "index2"},
[]string{},
"/index1%2Cindex2/_delete_by_query",
false,
},
{
[]string{},
[]string{"type1"},
"",
true,
},
{
[]string{"index1"},
[]string{"type1"},
"/index1/type1/_delete_by_query",
false,
},
{
[]string{"index1", "index2"},
[]string{"type1", "type2"},
"/index1%2Cindex2/type1%2Ctype2/_delete_by_query",
false,
},
}
for i, test := range tests {
builder := client.DeleteByQuery().Index(test.Indices...).Type(test.Types...)
err := builder.Validate()
if err != nil {
if !test.ExpectErr {
t.Errorf("case #%d: %v", i+1, err)
continue
}
} else {
// err == nil
if test.ExpectErr {
t.Errorf("case #%d: expected error", i+1)
continue
}
path, _, _ := builder.buildURL()
if path != test.Expected {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.Expected, path)
}
}
}
}
func TestDeleteByQuery(t *testing.T) {
// client := setupTestClientAndCreateIndex(t, SetTraceLog(log.New(os.Stdout, "", 0)))
client := setupTestClientAndCreateIndex(t)
tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
tweet2 := tweet{User: "olivere", Message: "Another unrelated topic."}
tweet3 := tweet{User: "sandrae", Message: "Cycling is fun."}
// Add all documents
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("3").BodyJson(&tweet3).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Count documents
count, err := client.Count(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if count != 3 {
t.Fatalf("expected count = %d; got: %d", 3, count)
}
// Delete all documents by sandrae
q := NewTermQuery("user", "sandrae")
res, err := client.DeleteByQuery().
Index(testIndexName).
Query(q).
Pretty(true).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatalf("expected response != nil; got: %v", res)
}
// Refresh and check count
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
count, err = client.Count(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if count != 2 {
t.Fatalf("expected Count = %d; got: %d", 2, count)
}
}
func TestDeleteByQueryAsync(t *testing.T) {
// client := setupTestClientAndCreateIndex(t, SetTraceLog(log.New(os.Stdout, "", 0)))
client := setupTestClientAndCreateIndex(t)
tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
tweet2 := tweet{User: "olivere", Message: "Another unrelated topic."}
tweet3 := tweet{User: "sandrae", Message: "Cycling is fun."}
// Add all documents
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("3").BodyJson(&tweet3).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Count documents
count, err := client.Count(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if count != 3 {
t.Fatalf("expected count = %d; got: %d", 3, count)
}
// Delete all documents by sandrae
q := NewTermQuery("user", "sandrae")
res, err := client.DeleteByQuery().
Index(testIndexName).
Query(q).
Slices("auto").
Pretty(true).
DoAsync(context.TODO())
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected result != nil")
}
if res.TaskId == "" {
t.Errorf("expected a task id, got %+v", res)
}
tasksGetTask := client.TasksGetTask()
taskStatus, err := tasksGetTask.TaskId(res.TaskId).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if taskStatus == nil {
t.Fatal("expected task status result != nil")
}
}
================================================
FILE: delete_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestDelete(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
tweet2 := tweet{User: "olivere", Message: "Another unrelated topic."}
tweet3 := tweet{User: "sandrae", Message: "Cycling is fun."}
// Add all documents
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("3").BodyJson(&tweet3).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Count documents
count, err := client.Count(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if count != 3 {
t.Errorf("expected Count = %d; got %d", 3, count)
}
// Delete document 1
res, err := client.Delete().Index(testIndexName).Id("1").Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if want, have := "deleted", res.Result; want != have {
t.Errorf("expected Result = %q; got %q", want, have)
}
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
count, err = client.Count(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if count != 2 {
t.Errorf("expected Count = %d; got %d", 2, count)
}
// Delete non existent document 99
res, err = client.Delete().Index(testIndexName).Id("99").Refresh("true").Do(context.TODO())
if err == nil {
t.Fatal("expected error")
}
if !IsNotFound(err) {
t.Fatalf("expected 404, got: %v", err)
}
if _, ok := err.(*Error); !ok {
t.Fatalf("expected error type *Error, got: %T", err)
}
if res == nil {
t.Fatal("expected response")
}
if have, want := res.Id, "99"; have != want {
t.Errorf("expected _id = %q, got %q", have, want)
}
if have, want := res.Index, testIndexName; have != want {
t.Errorf("expected _index = %q, got %q", have, want)
}
if have, want := res.Type, "_doc"; have != want {
t.Errorf("expected _type = %q, got %q", have, want)
}
if have, want := res.Result, "not_found"; have != want {
t.Errorf("expected Result = %q, got %q", have, want)
}
count, err = client.Count(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if count != 2 {
t.Errorf("expected Count = %d; got %d", 2, count)
}
}
func TestDeleteValidate(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t)
// No index name -> fail with error
res, err := NewDeleteService(client).Id("1").Do(context.TODO())
if err == nil {
t.Fatalf("expected Delete to fail without index name")
}
if res != nil {
t.Fatalf("expected result to be == nil; got: %v", res)
}
// No id -> fail with error
res, err = NewDeleteService(client).Index(testIndexName).Do(context.TODO())
if err == nil {
t.Fatalf("expected Delete to fail without id")
}
if res != nil {
t.Fatalf("expected result to be == nil; got: %v", res)
}
}
func TestDeleteOptimistic(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t) //, SetTraceLog(log.New(os.Stdout, "", 0)))
doc, err := client.Get().
Index(testIndexName).Id("1").
Do(context.Background())
if err != nil {
t.Fatal(err)
}
if doc.SeqNo == nil {
t.Fatal("expected seq_no != nil")
}
if doc.PrimaryTerm == nil {
t.Fatal("expected primary_term != nil")
}
// Delete with seqNo != doc.SeqNo and primaryTerm != doc.PrimaryTerm
_, err = client.Delete().
Index(testIndexName).Id(doc.Id).
IfSeqNo(*doc.SeqNo + 1000).
IfPrimaryTerm(*doc.PrimaryTerm + 1000).
Do(context.Background())
if err == nil {
t.Fatal("expected error, got nil")
}
if !IsConflict(err) {
t.Fatalf("expected conflict error, got %v (%T)", err, err)
}
// Update with seqNo == doc.SeqNo and primaryTerm == doc.PrimaryTerm
res, err := client.Delete().
Index(testIndexName).Id(doc.Id).
IfSeqNo(*doc.SeqNo).
IfPrimaryTerm(*doc.PrimaryTerm).
Do(context.Background())
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected response != nil")
}
}
================================================
FILE: doc.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
/*
Package elastic provides an interface to the Elasticsearch server
(https://www.elastic.co/products/elasticsearch).
The first thing you do is to create a Client. If you have Elasticsearch
installed and running with its default settings
(i.e. available at http://127.0.0.1:9200), all you need to do is:
client, err := elastic.NewClient()
if err != nil {
// Handle error
}
If your Elasticsearch server is running on a different IP and/or port,
just provide a URL to NewClient:
// Create a client and connect to http://192.168.2.10:9201
client, err := elastic.NewClient(elastic.SetURL("http://192.168.2.10:9201"))
if err != nil {
// Handle error
}
You can pass many more configuration parameters to NewClient. Review the
documentation of NewClient for more information.
If no Elasticsearch server is available, services will fail when creating
a new request and will return ErrNoClient.
A Client provides services. The services usually come with a variety of
methods to prepare the query and a Do function to execute it against the
Elasticsearch REST interface and return a response. Here is an example
of the IndexExists service that checks if a given index already exists.
exists, err := client.IndexExists("twitter").Do(context.Background())
if err != nil {
// Handle error
}
if !exists {
// Index does not exist yet.
}
Look up the documentation for Client to get an idea of the services provided
and what kinds of responses you get when executing the Do function of a service.
Also see the wiki on Github for more details.
*/
package elastic
================================================
FILE: docker-compose.cluster.yml
================================================
services:
es1:
image: docker.elastic.co/elasticsearch/elasticsearch:${VERSION:-7.14.0}
hostname: es1
environment:
- bootstrap.memory_lock=true
- cluster.name=elasticsearch
- cluster.initial_master_nodes=es1
- discovery.seed_hosts=es1
- cluster.routing.allocation.disk.threshold_enabled=false
- node.name=es1
- logger.org.elasticsearch=warn
- xpack.security.enabled=false
- "ES_JAVA_OPTS=-Xms1g -Xmx1g"
ulimits:
nproc: 65536
nofile:
soft: 65536
hard: 65536
memlock:
soft: -1
hard: -1
# volumes:
# - ./data/elasticsearch:/usr/share/elasticsearch/data
ports:
- 9200:9200
es2:
image: docker.elastic.co/elasticsearch/elasticsearch:${VERSION:-7.14.0}
hostname: es2
environment:
- bootstrap.memory_lock=true
- cluster.name=elasticsearch
- cluster.initial_master_nodes=es1
- discovery.seed_hosts=es1
- cluster.routing.allocation.disk.threshold_enabled=false
- node.name=es2
- logger.org.elasticsearch=warn
- xpack.security.enabled=false
- "ES_JAVA_OPTS=-Xms1g -Xmx1g"
ulimits:
nproc: 65536
nofile:
soft: 65536
hard: 65536
memlock:
soft: -1
hard: -1
# volumes:
# - ./data/elasticsearch:/usr/share/elasticsearch/data
ports:
- 9201:9200
es3:
image: docker.elastic.co/elasticsearch/elasticsearch:${VERSION:-7.14.0}
hostname: es3
environment:
- bootstrap.memory_lock=true
- cluster.name=elasticsearch
- cluster.initial_master_nodes=es1
- discovery.seed_hosts=es1
- cluster.routing.allocation.disk.threshold_enabled=false
- node.name=es3
- logger.org.elasticsearch=warn
- xpack.security.enabled=false
- "ES_JAVA_OPTS=-Xms1g -Xmx1g"
ulimits:
nproc: 65536
nofile:
soft: 65536
hard: 65536
memlock:
soft: -1
hard: -1
# volumes:
# - ./data/elasticsearch:/usr/share/elasticsearch/data
ports:
- 9202:9200
================================================
FILE: docker-compose.yml
================================================
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:${VERSION:-7.17.1}
hostname: elasticsearch
environment:
- cluster.name=elasticsearch
- bootstrap.memory_lock=true
- discovery.type=single-node
# - http.publish_host=localhost
# - http.host=0.0.0.0
# - transport.host=127.0.0.1
# - network.host=_local_
- network.publish_host=127.0.0.1
- logger.org.elasticsearch=warn
- xpack.security.enabled=false
- path.repo=/usr/share/elasticsearch/backup
- "ES_JAVA_OPTS=-Xms1g -Xmx1g"
ulimits:
nproc: 65536
nofile:
soft: 65536
hard: 65536
memlock:
soft: -1
hard: -1
volumes:
- ./data/backup:/usr/share/elasticsearch/backup
ports:
- 9200:9200
platinum:
image: docker.elastic.co/elasticsearch/elasticsearch:${VERSION:-7.17.1}
hostname: elasticsearch-platinum
environment:
- cluster.name=platinum
- bootstrap.memory_lock=true
- discovery.type=single-node
- xpack.license.self_generated.type=trial
- xpack.security.enabled=true
- xpack.watcher.enabled=true
# - http.publish_host=localhost
# - http.host=0.0.0.0
# - transport.host=127.0.0.1
# - network.host=_local_
- http.port=9210
- network.publish_host=127.0.0.1
- logger.org.elasticsearch=warn
- path.repo=/usr/share/elasticsearch/backup
- "ES_JAVA_OPTS=-Xms1g -Xmx1g"
- ELASTIC_PASSWORD=elastic
ulimits:
nproc: 65536
nofile:
soft: 65536
hard: 65536
memlock:
soft: -1
hard: -1
volumes:
- ./data/backup:/usr/share/elasticsearch/backup
ports:
- 9210:9210
================================================
FILE: docvalue_field.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// DocvalueField represents a docvalue field, its name and
// its format (optional).
type DocvalueField struct {
Field string
Format string
}
// Source serializes the DocvalueField into JSON.
func (d DocvalueField) Source() (interface{}, error) {
if d.Format == "" {
return d.Field, nil
}
return map[string]interface{}{
"field": d.Field,
"format": d.Format,
}, nil
}
// DocvalueFields is a slice of DocvalueField instances.
type DocvalueFields []DocvalueField
// Source serializes the DocvalueFields into JSON.
func (d DocvalueFields) Source() (interface{}, error) {
if d == nil {
return nil, nil
}
v := make([]interface{}, 0)
for _, f := range d {
src, err := f.Source()
if err != nil {
return nil, err
}
v = append(v, src)
}
return v, nil
}
================================================
FILE: docvalue_field_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"testing"
"github.com/google/go-cmp/cmp"
)
func TestDocvalueField(t *testing.T) {
tests := []struct {
Doc DocvalueField
Want interface{}
}{
{
Doc: DocvalueField{},
Want: "",
},
{
Doc: DocvalueField{Field: "name"},
Want: "name",
},
{
Doc: DocvalueField{Field: "name", Format: "epoch_millis"},
Want: map[string]interface{}{"field": "name", "format": "epoch_millis"},
},
}
for _, tt := range tests {
have, err := tt.Doc.Source()
if err != nil {
t.Fatalf("Source(%#v): err=%v", tt.Doc, err)
}
if want := tt.Want; !cmp.Equal(want, have) {
t.Fatalf("Source(%#v): want %v, have %v", tt.Doc, want, have)
}
}
}
func TestDocvalueFields(t *testing.T) {
doc := DocvalueFields{
DocvalueField{Field: "retweets"},
DocvalueField{Field: "name", Format: "epoch_millis"},
}
have, err := doc.Source()
if err != nil {
t.Fatalf("Source(%#v): err=%v", doc, err)
}
want := []interface{}{
"retweets",
map[string]interface{}{"field": "name", "format": "epoch_millis"},
}
if !cmp.Equal(want, have) {
t.Fatalf("Source(%#v): want %v, have %v", doc, want, have)
}
}
================================================
FILE: errors.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"github.com/pkg/errors"
)
// checkResponse will return an error if the request/response indicates
// an error returned from Elasticsearch.
//
// HTTP status codes between in the range [200..299] are considered successful.
// All other errors are considered errors except they are specified in
// ignoreErrors. This is necessary because for some services, HTTP status 404
// is a valid response from Elasticsearch (e.g. the Exists service).
//
// The func tries to parse error details as returned from Elasticsearch
// and encapsulates them in type elastic.Error.
func checkResponse(req *http.Request, res *http.Response, ignoreErrors ...int) error {
// 200-299 are valid status codes
if res.StatusCode >= 200 && res.StatusCode <= 299 {
return nil
}
// Ignore certain errors?
for _, code := range ignoreErrors {
if code == res.StatusCode {
return nil
}
}
return createResponseError(res)
}
// createResponseError creates an Error structure from the HTTP response,
// its status code and the error information sent by Elasticsearch.
func createResponseError(res *http.Response) error {
if res.Body == nil {
return &Error{Status: res.StatusCode}
}
data, err := ioutil.ReadAll(res.Body)
if err != nil {
return &Error{Status: res.StatusCode}
}
errReply := new(Error)
err = json.Unmarshal(data, errReply)
if err != nil {
return &Error{Status: res.StatusCode}
}
if errReply != nil {
if errReply.Status == 0 {
errReply.Status = res.StatusCode
}
return errReply
}
return &Error{Status: res.StatusCode}
}
// Error encapsulates error details as returned from Elasticsearch.
type Error struct {
Status int `json:"status"`
Details *ErrorDetails `json:"error,omitempty"`
}
// ErrorDetails encapsulate error details from Elasticsearch.
// It is used in e.g. elastic.Error and elastic.BulkResponseItem.
type ErrorDetails struct {
Type string `json:"type"`
Reason string `json:"reason"`
ResourceType string `json:"resource.type,omitempty"`
ResourceId string `json:"resource.id,omitempty"`
Index string `json:"index,omitempty"`
Phase string `json:"phase,omitempty"`
Grouped bool `json:"grouped,omitempty"`
CausedBy map[string]interface{} `json:"caused_by,omitempty"`
RootCause []*ErrorDetails `json:"root_cause,omitempty"`
Suppressed []*ErrorDetails `json:"suppressed,omitempty"`
FailedShards []map[string]interface{} `json:"failed_shards,omitempty"`
Header map[string]interface{} `json:"header,omitempty"`
// ScriptException adds the information in the following block.
ScriptStack []string `json:"script_stack,omitempty"` // from ScriptException
Script string `json:"script,omitempty"` // from ScriptException
Lang string `json:"lang,omitempty"` // from ScriptException
Position *ScriptErrorPosition `json:"position,omitempty"` // from ScriptException (7.7+)
}
// ScriptErrorPosition specifies the position of the error
// in a script. It is used in ErrorDetails for scripting errors.
type ScriptErrorPosition struct {
Offset int `json:"offset"`
Start int `json:"start"`
End int `json:"end"`
}
// Error returns a string representation of the error.
func (e *Error) Error() string {
if e.Details != nil && e.Details.Reason != "" {
return fmt.Sprintf("elastic: Error %d (%s): %s [type=%s]", e.Status, http.StatusText(e.Status), e.Details.Reason, e.Details.Type)
}
return fmt.Sprintf("elastic: Error %d (%s)", e.Status, http.StatusText(e.Status))
}
// ErrorReason returns the reason of an error that Elasticsearch reported,
// if err is of kind Error and has ErrorDetails with a Reason. Any other
// value of err will return an empty string.
func ErrorReason(err error) string {
if err == nil {
return ""
}
e, ok := err.(*Error)
if !ok || e == nil || e.Details == nil {
return ""
}
return e.Details.Reason
}
// IsContextErr returns true if the error is from a context that was canceled or deadline exceeded
func IsContextErr(err error) bool {
if err == context.Canceled || err == context.DeadlineExceeded {
return true
}
// This happens e.g. on redirect errors, see https://golang.org/src/net/http/client_test.go#L329
if ue, ok := err.(*url.Error); ok {
if ue.Temporary() {
return true
}
// Use of an AWS Signing Transport can result in a wrapped url.Error
return IsContextErr(ue.Err)
}
return false
}
// IsConnErr returns true if the error indicates that Elastic could not
// find an Elasticsearch host to connect to.
func IsConnErr(err error) bool {
return err == ErrNoClient || errors.Cause(err) == ErrNoClient
}
// IsNotFound returns true if the given error indicates that Elasticsearch
// returned HTTP status 404. The err parameter can be of type *elastic.Error,
// elastic.Error, *http.Response or int (indicating the HTTP status code).
func IsNotFound(err interface{}) bool {
return IsStatusCode(err, http.StatusNotFound)
}
// IsTimeout returns true if the given error indicates that Elasticsearch
// returned HTTP status 408. The err parameter can be of type *elastic.Error,
// elastic.Error, *http.Response or int (indicating the HTTP status code).
func IsTimeout(err interface{}) bool {
return IsStatusCode(err, http.StatusRequestTimeout)
}
// IsConflict returns true if the given error indicates that the Elasticsearch
// operation resulted in a version conflict. This can occur in operations like
// `update` or `index` with `op_type=create`. The err parameter can be of
// type *elastic.Error, elastic.Error, *http.Response or int (indicating the
// HTTP status code).
func IsConflict(err interface{}) bool {
return IsStatusCode(err, http.StatusConflict)
}
// IsUnauthorized returns true if the given error indicates that
// Elasticsearch returned HTTP status 401. This happens e.g. when the
// cluster is configured to require HTTP Basic Auth.
// The err parameter can be of type *elastic.Error, elastic.Error,
// *http.Response or int (indicating the HTTP status code).
func IsUnauthorized(err interface{}) bool {
return IsStatusCode(err, http.StatusUnauthorized)
}
// IsForbidden returns true if the given error indicates that Elasticsearch
// returned HTTP status 403. This happens e.g. due to a missing license.
// The err parameter can be of type *elastic.Error, elastic.Error,
// *http.Response or int (indicating the HTTP status code).
func IsForbidden(err interface{}) bool {
return IsStatusCode(err, http.StatusForbidden)
}
// IsStatusCode returns true if the given error indicates that the Elasticsearch
// operation returned the specified HTTP status code. The err parameter can be of
// type *http.Response, *Error, Error, or int (indicating the HTTP status code).
func IsStatusCode(err interface{}, code int) bool {
switch e := err.(type) {
case *http.Response:
return e.StatusCode == code
case *Error:
return e.Status == code
case Error:
return e.Status == code
case int:
return e == code
}
return false
}
// -- General errors --
// ShardsInfo represents information from a shard.
type ShardsInfo struct {
Total int `json:"total"`
Successful int `json:"successful"`
Failed int `json:"failed"`
Failures []*ShardOperationFailedException `json:"failures,omitempty"`
Skipped int `json:"skipped,omitempty"`
}
type ShardOperationFailedException struct {
Shard int `json:"shard,omitempty"`
Index string `json:"index,omitempty"`
Status string `json:"status,omitempty"`
Reason map[string]interface{} `json:"reason,omitempty"`
// TODO(oe) Do we still have those?
Node string `json:"_node,omitempty"`
// TODO(oe) Do we still have those?
Primary bool `json:"primary,omitempty"`
}
type BroadcastResponse struct {
Shards *ShardsInfo `json:"_shards,omitempty"`
Total int `json:"total"`
Successful int `json:"successful"`
Failed int `json:"failed"`
Failures []*ShardOperationFailedException `json:"failures,omitempty"`
}
// FailedNodeException returns an error on the node level.
type FailedNodeException struct {
*ErrorDetails
NodeId string `json:"node_id"`
}
================================================
FILE: errors_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"bufio"
"fmt"
"net/http"
"strings"
"testing"
)
func TestErrorReason(t *testing.T) {
if want, have := "", ErrorReason(nil); want != have {
t.Fatalf("want %q, have %q", want, have)
}
if want, have := "", ErrorReason(&Error{}); want != have {
t.Fatalf("want %q, have %q", want, have)
}
if want, have := "", ErrorReason(&Error{Details: &ErrorDetails{}}); want != have {
t.Fatalf("want %q, have %q", want, have)
}
if want, have := "no such index", ErrorReason(&Error{Details: &ErrorDetails{Reason: "no such index"}}); want != have {
t.Fatalf("want %q, have %q", want, have)
}
}
func TestResponseError(t *testing.T) {
raw := "HTTP/1.1 404 Not Found\r\n" +
"\r\n" +
`{"error":{"root_cause":[{"type":"index_missing_exception","reason":"no such index","index":"elastic-test"}],"type":"index_missing_exception","reason":"no such index","index":"elastic-test"},"status":404}` + "\r\n"
r := bufio.NewReader(strings.NewReader(raw))
req, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
resp, err := http.ReadResponse(r, nil)
if err != nil {
t.Fatal(err)
}
err = checkResponse(req, resp)
if err == nil {
t.Fatalf("expected error; got: %v", err)
}
// Check for correct error message
expected := fmt.Sprintf("elastic: Error %d (%s): no such index [type=index_missing_exception]", resp.StatusCode, http.StatusText(resp.StatusCode))
got := err.Error()
if got != expected {
t.Fatalf("expected %q; got: %q", expected, got)
}
// Check ErrorReason
if expected, got := "no such index", ErrorReason(err); expected != got {
t.Fatalf("expected %q; got: %q", expected, got)
}
// Check that error is of type *elastic.Error, which contains additional information
e, ok := err.(*Error)
if !ok {
t.Fatal("expected error to be of type *elastic.Error")
}
if e.Status != resp.StatusCode {
t.Fatalf("expected status code %d; got: %d", resp.StatusCode, e.Status)
}
if e.Details == nil {
t.Fatalf("expected error details; got: %v", e.Details)
}
if got, want := e.Details.Index, "elastic-test"; got != want {
t.Fatalf("expected error details index %q; got: %q", want, got)
}
if got, want := e.Details.Type, "index_missing_exception"; got != want {
t.Fatalf("expected error details type %q; got: %q", want, got)
}
if got, want := e.Details.Reason, "no such index"; got != want {
t.Fatalf("expected error details reason %q; got: %q", want, got)
}
if got, want := len(e.Details.RootCause), 1; got != want {
t.Fatalf("expected %d error details root causes; got: %d", want, got)
}
if got, want := e.Details.RootCause[0].Index, "elastic-test"; got != want {
t.Fatalf("expected root cause index %q; got: %q", want, got)
}
if got, want := e.Details.RootCause[0].Type, "index_missing_exception"; got != want {
t.Fatalf("expected root cause type %q; got: %q", want, got)
}
if got, want := e.Details.RootCause[0].Reason, "no such index"; got != want {
t.Fatalf("expected root cause reason %q; got: %q", want, got)
}
}
func TestResponseErrorHTML(t *testing.T) {
raw := "HTTP/1.1 413 Request Entity Too Large\r\n" +
"\r\n" +
`
413 Request Entity Too Large
413 Request Entity Too Large
nginx/1.6.2
` + "\r\n"
r := bufio.NewReader(strings.NewReader(raw))
req, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
resp, err := http.ReadResponse(r, nil)
if err != nil {
t.Fatal(err)
}
err = checkResponse(req, resp)
if err == nil {
t.Fatalf("expected error; got: %v", err)
}
// Check for correct error message
expected := fmt.Sprintf("elastic: Error %d (%s)", http.StatusRequestEntityTooLarge, http.StatusText(http.StatusRequestEntityTooLarge))
got := err.Error()
if got != expected {
t.Fatalf("expected %q; got: %q", expected, got)
}
}
func TestResponseErrorWithIgnore(t *testing.T) {
raw := "HTTP/1.1 404 Not Found\r\n" +
"\r\n" +
`{"some":"response"}` + "\r\n"
r := bufio.NewReader(strings.NewReader(raw))
req, err := http.NewRequest("HEAD", "/", nil)
if err != nil {
t.Fatal(err)
}
resp, err := http.ReadResponse(r, nil)
if err != nil {
t.Fatal(err)
}
err = checkResponse(req, resp)
if err == nil {
t.Fatalf("expected error; got: %v", err)
}
err = checkResponse(req, resp, 404) // ignore 404 errors
if err != nil {
t.Fatalf("expected no error; got: %v", err)
}
}
func TestIsNotFound(t *testing.T) {
if got, want := IsNotFound(nil), false; got != want {
t.Errorf("expected %v; got: %v", want, got)
}
if got, want := IsNotFound(""), false; got != want {
t.Errorf("expected %v; got: %v", want, got)
}
if got, want := IsNotFound(200), false; got != want {
t.Errorf("expected %v; got: %v", want, got)
}
if got, want := IsNotFound(404), true; got != want {
t.Errorf("expected %v; got: %v", want, got)
}
if got, want := IsNotFound(&Error{Status: 404}), true; got != want {
t.Errorf("expected %v; got: %v", want, got)
}
if got, want := IsNotFound(&Error{Status: 200}), false; got != want {
t.Errorf("expected %v; got: %v", want, got)
}
if got, want := IsNotFound(Error{Status: 404}), true; got != want {
t.Errorf("expected %v; got: %v", want, got)
}
if got, want := IsNotFound(Error{Status: 200}), false; got != want {
t.Errorf("expected %v; got: %v", want, got)
}
if got, want := IsNotFound(&http.Response{StatusCode: 404}), true; got != want {
t.Errorf("expected %v; got: %v", want, got)
}
if got, want := IsNotFound(&http.Response{StatusCode: 200}), false; got != want {
t.Errorf("expected %v; got: %v", want, got)
}
}
func TestIsTimeout(t *testing.T) {
if got, want := IsTimeout(nil), false; got != want {
t.Errorf("expected %v; got: %v", want, got)
}
if got, want := IsTimeout(""), false; got != want {
t.Errorf("expected %v; got: %v", want, got)
}
if got, want := IsTimeout(200), false; got != want {
t.Errorf("expected %v; got: %v", want, got)
}
if got, want := IsTimeout(408), true; got != want {
t.Errorf("expected %v; got: %v", want, got)
}
if got, want := IsTimeout(&Error{Status: 408}), true; got != want {
t.Errorf("expected %v; got: %v", want, got)
}
if got, want := IsTimeout(&Error{Status: 200}), false; got != want {
t.Errorf("expected %v; got: %v", want, got)
}
if got, want := IsTimeout(Error{Status: 408}), true; got != want {
t.Errorf("expected %v; got: %v", want, got)
}
if got, want := IsTimeout(Error{Status: 200}), false; got != want {
t.Errorf("expected %v; got: %v", want, got)
}
if got, want := IsTimeout(&http.Response{StatusCode: 408}), true; got != want {
t.Errorf("expected %v; got: %v", want, got)
}
if got, want := IsTimeout(&http.Response{StatusCode: 200}), false; got != want {
t.Errorf("expected %v; got: %v", want, got)
}
}
func TestIsConflict(t *testing.T) {
if got, want := IsConflict(nil), false; got != want {
t.Errorf("expected %v; got: %v", want, got)
}
if got, want := IsConflict(""), false; got != want {
t.Errorf("expected %v; got: %v", want, got)
}
if got, want := IsConflict(200), false; got != want {
t.Errorf("expected %v; got: %v", want, got)
}
if got, want := IsConflict(http.StatusConflict), true; got != want {
t.Errorf("expected %v; got: %v", want, got)
}
if got, want := IsConflict(&Error{Status: 409}), true; got != want {
t.Errorf("expected %v; got: %v", want, got)
}
if got, want := IsConflict(&Error{Status: 200}), false; got != want {
t.Errorf("expected %v; got: %v", want, got)
}
if got, want := IsConflict(Error{Status: 409}), true; got != want {
t.Errorf("expected %v; got: %v", want, got)
}
if got, want := IsConflict(Error{Status: 200}), false; got != want {
t.Errorf("expected %v; got: %v", want, got)
}
if got, want := IsConflict(&http.Response{StatusCode: 409}), true; got != want {
t.Errorf("expected %v; got: %v", want, got)
}
if got, want := IsConflict(&http.Response{StatusCode: 200}), false; got != want {
t.Errorf("expected %v; got: %v", want, got)
}
}
func TestIsStatusCode(t *testing.T) {
tests := []struct {
Error interface{}
Code int
Want bool
}{
// #0
{
Error: nil,
Code: 200,
Want: false,
},
// #1
{
Error: "",
Code: 200,
Want: false,
},
// #2
{
Error: http.StatusConflict,
Code: 409,
Want: true,
},
// #3
{
Error: http.StatusConflict,
Code: http.StatusInternalServerError,
Want: false,
},
// #4
{
Error: &Error{Status: http.StatusConflict},
Code: 409,
Want: true,
},
// #5
{
Error: Error{Status: http.StatusConflict},
Code: 409,
Want: true,
},
// #6
{
Error: &http.Response{StatusCode: http.StatusConflict},
Code: 409,
Want: true,
},
}
for i, tt := range tests {
if have, want := IsStatusCode(tt.Error, tt.Code), tt.Want; have != want {
t.Errorf("#%d: have %v, want %v", i, have, want)
}
}
}
================================================
FILE: example_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic_test
import (
"context"
"encoding/json"
"fmt"
"log"
"os"
"reflect"
"time"
"github.com/olivere/elastic/v7"
)
type Tweet struct {
User string `json:"user"`
Message string `json:"message"`
Retweets int `json:"retweets"`
Image string `json:"image,omitempty"`
Created time.Time `json:"created,omitempty"`
Tags []string `json:"tags,omitempty"`
Location string `json:"location,omitempty"`
Suggest *elastic.SuggestField `json:"suggest_field,omitempty"`
}
func Example() {
errorlog := log.New(os.Stdout, "APP ", log.LstdFlags)
// Obtain a client. You can also provide your own HTTP client here.
client, err := elastic.NewClient(elastic.SetErrorLog(errorlog))
// Trace request and response details like this
// client, err := elastic.NewClient(elastic.SetTraceLog(log.New(os.Stdout, "", 0)))
if err != nil {
// Handle error
panic(err)
}
// Ping the Elasticsearch server to get e.g. the version number
info, code, err := client.Ping("http://127.0.0.1:9200").Do(context.Background())
if err != nil {
// Handle error
panic(err)
}
fmt.Printf("Elasticsearch returned with code %d and version %s\n", code, info.Version.Number)
// Getting the ES version number is quite common, so there's a shortcut
esversion, err := client.ElasticsearchVersion("http://127.0.0.1:9200")
if err != nil {
// Handle error
panic(err)
}
fmt.Printf("Elasticsearch version %s\n", esversion)
// Use the IndexExists service to check if a specified index exists.
exists, err := client.IndexExists("twitter").Do(context.Background())
if err != nil {
// Handle error
panic(err)
}
if !exists {
// Create a new index.
mapping := `
{
"settings":{
"number_of_shards":1,
"number_of_replicas":0
},
"mappings":{
"doc":{
"properties":{
"user":{
"type":"keyword"
},
"message":{
"type":"text",
"store": true,
"fielddata": true
},
"retweets":{
"type":"long"
},
"tags":{
"type":"keyword"
},
"location":{
"type":"geo_point"
},
"suggest_field":{
"type":"completion"
}
}
}
}
}
`
createIndex, err := client.CreateIndex("twitter").Body(mapping).Do(context.Background())
if err != nil {
// Handle error
panic(err)
}
if !createIndex.Acknowledged {
// Not acknowledged
}
}
// Index a tweet (using JSON serialization)
tweet1 := Tweet{User: "olivere", Message: "Take Five", Retweets: 0}
put1, err := client.Index().
Index("twitter").
Id("1").
BodyJson(tweet1).
Do(context.Background())
if err != nil {
// Handle error
panic(err)
}
fmt.Printf("Indexed tweet %s to index %s, type %s\n", put1.Id, put1.Index, put1.Type)
// Index a second tweet (by string)
tweet2 := `{"user" : "olivere", "message" : "It's a Raggy Waltz"}`
put2, err := client.Index().
Index("twitter").
Id("2").
BodyString(tweet2).
Do(context.Background())
if err != nil {
// Handle error
panic(err)
}
fmt.Printf("Indexed tweet %s to index %s, type %s\n", put2.Id, put2.Index, put2.Type)
// Get tweet with specified ID
get1, err := client.Get().
Index("twitter").
Id("1").
Do(context.Background())
if err != nil {
switch {
case elastic.IsNotFound(err):
panic(fmt.Sprintf("Document not found: %v", err))
case elastic.IsTimeout(err):
panic(fmt.Sprintf("Timeout retrieving document: %v", err))
case elastic.IsConnErr(err):
panic(fmt.Sprintf("Connection problem: %v", err))
default:
// Some other kind of error
panic(err)
}
}
fmt.Printf("Got document %s in version %d from index %s, type %s\n", get1.Id, get1.Version, get1.Index, get1.Type)
// Refresh to make sure the documents are searchable.
_, err = client.Refresh().Index("twitter").Do(context.Background())
if err != nil {
panic(err)
}
// Search with a term query
termQuery := elastic.NewTermQuery("user", "olivere")
searchResult, err := client.Search().
Index("twitter"). // search in index "twitter"
Query(termQuery). // specify the query
Sort("user", true). // sort by "user" field, ascending
From(0).Size(10). // take documents 0-9
Pretty(true). // pretty print request and response JSON
Do(context.Background()) // execute
if err != nil {
// Handle error
panic(err)
}
// searchResult is of type SearchResult and returns hits, suggestions,
// and all kinds of other information from Elasticsearch.
fmt.Printf("Query took %d milliseconds\n", searchResult.TookInMillis)
// Each is a convenience function that iterates over hits in a search result.
// It makes sure you don't need to check for nil values in the response.
// However, it ignores errors in serialization. If you want full control
// over iterating the hits, see below.
var ttyp Tweet
for _, item := range searchResult.Each(reflect.TypeOf(ttyp)) {
t := item.(Tweet)
fmt.Printf("Tweet by %s: %s\n", t.User, t.Message)
}
// TotalHits is another convenience function that works even when something goes wrong.
fmt.Printf("Found a total of %d tweets\n", searchResult.TotalHits())
// Here's how you iterate through results with full control over each step.
if searchResult.TotalHits() > 0 {
fmt.Printf("Found a total of %d tweets\n", searchResult.TotalHits())
// Iterate through results
for _, hit := range searchResult.Hits.Hits {
// hit.Index contains the name of the index
// Deserialize hit.Source into a Tweet (could also be just a map[string]interface{}).
var t Tweet
err := json.Unmarshal(hit.Source, &t)
if err != nil {
// Deserialization failed
}
// Work with tweet
fmt.Printf("Tweet by %s: %s\n", t.User, t.Message)
}
} else {
// No hits
fmt.Print("Found no tweets\n")
}
// Update a tweet by the update API of Elasticsearch.
// We just increment the number of retweets.
script := elastic.NewScript("ctx._source.retweets += params.num").Param("num", 1)
update, err := client.Update().Index("twitter").Id("1").
Script(script).
Upsert(map[string]interface{}{"retweets": 0}).
Do(context.Background())
if err != nil {
// Handle error
panic(err)
}
fmt.Printf("New version of tweet %q is now %d", update.Id, update.Version)
// ...
// Delete an index.
deleteIndex, err := client.DeleteIndex("twitter").Do(context.Background())
if err != nil {
// Handle error
panic(err)
}
if !deleteIndex.Acknowledged {
// Not acknowledged
}
}
func ExampleNewClient_default() {
// Obtain a client to the Elasticsearch instance on http://127.0.0.1:9200.
client, err := elastic.NewClient()
if err != nil {
// Handle error
fmt.Printf("connection failed: %v\n", err)
} else {
fmt.Println("connected")
}
_ = client
// Output:
// connected
}
func ExampleNewClient_cluster() {
// Obtain a client for an Elasticsearch cluster of two nodes,
// running on 10.0.1.1 and 10.0.1.2.
client, err := elastic.NewClient(elastic.SetURL("http://10.0.1.1:9200", "http://10.0.1.2:9200"))
if err != nil {
// Handle error
panic(err)
}
_ = client
}
func ExampleNewClient_manyOptions() {
// Obtain a client for an Elasticsearch cluster of two nodes,
// running on 10.0.1.1 and 10.0.1.2. Do not run the sniffer.
// Set the healthcheck interval to 10s. When requests fail,
// retry 5 times. Print error messages to os.Stderr and informational
// messages to os.Stdout.
client, err := elastic.NewClient(
elastic.SetURL("http://10.0.1.1:9200", "http://10.0.1.2:9200"),
elastic.SetSniff(false),
elastic.SetHealthcheckInterval(10*time.Second),
elastic.SetMaxRetries(5),
elastic.SetErrorLog(log.New(os.Stderr, "ELASTIC ", log.LstdFlags)),
elastic.SetInfoLog(log.New(os.Stdout, "", log.LstdFlags)))
if err != nil {
// Handle error
panic(err)
}
_ = client
}
func ExampleIndicesExistsService() {
// Get a client to the local Elasticsearch instance.
client, err := elastic.NewClient()
if err != nil {
// Handle error
panic(err)
}
// Use the IndexExists service to check if the index "twitter" exists.
exists, err := client.IndexExists("twitter").Do(context.Background())
if err != nil {
// Handle error
panic(err)
}
if exists {
// ...
}
}
func ExampleIndicesCreateService() {
// Get a client to the local Elasticsearch instance.
client, err := elastic.NewClient()
if err != nil {
// Handle error
panic(err)
}
// Create a new index.
createIndex, err := client.CreateIndex("twitter").Do(context.Background())
if err != nil {
// Handle error
panic(err)
}
if !createIndex.Acknowledged {
// Not acknowledged
}
}
func ExampleIndicesDeleteService() {
// Get a client to the local Elasticsearch instance.
client, err := elastic.NewClient()
if err != nil {
// Handle error
panic(err)
}
// Delete an index.
deleteIndex, err := client.DeleteIndex("twitter").Do(context.Background())
if err != nil {
// Handle error
panic(err)
}
if !deleteIndex.Acknowledged {
// Not acknowledged
}
}
func ExampleSearchService() {
// Get a client to the local Elasticsearch instance.
client, err := elastic.NewClient()
if err != nil {
// Handle error
panic(err)
}
// Search with a term query
termQuery := elastic.NewTermQuery("user", "olivere")
searchResult, err := client.Search().
Index("twitter"). // search in index "twitter"
Query(termQuery). // specify the query
Sort("user", true). // sort by "user" field, ascending
From(0).Size(10). // take documents 0-9
Pretty(true). // pretty print request and response JSON
Do(context.Background()) // execute
if err != nil {
// Handle error
panic(err)
}
// searchResult is of type SearchResult and returns hits, suggestions,
// and all kinds of other information from Elasticsearch.
fmt.Printf("Query took %d milliseconds\n", searchResult.TookInMillis)
// Number of hits
if searchResult.TotalHits() > 0 {
fmt.Printf("Found a total of %d tweets\n", searchResult.TotalHits())
// Iterate through results
for _, hit := range searchResult.Hits.Hits {
// hit.Index contains the name of the index
// Deserialize hit.Source into a Tweet (could also be just a map[string]interface{}).
var t Tweet
err := json.Unmarshal(hit.Source, &t)
if err != nil {
// Deserialization failed
}
// Work with tweet
fmt.Printf("Tweet by %s: %s\n", t.User, t.Message)
}
} else {
// No hits
fmt.Print("Found no tweets\n")
}
}
func ExampleAggregations() {
// Get a client to the local Elasticsearch instance.
client, err := elastic.NewClient()
if err != nil {
// Handle error
panic(err)
}
// Create an aggregation for users and a sub-aggregation for a date histogram of tweets (per year).
timeline := elastic.NewTermsAggregation().Field("user").Size(10).OrderByCountDesc()
histogram := elastic.NewDateHistogramAggregation().Field("created").CalendarInterval("year")
timeline = timeline.SubAggregation("history", histogram)
// Search with a term query
searchResult, err := client.Search().
Index("twitter"). // search in index "twitter"
Query(elastic.NewMatchAllQuery()). // return all results, but ...
SearchType("count"). // ... do not return hits, just the count
Aggregation("timeline", timeline). // add our aggregation to the query
Pretty(true). // pretty print request and response JSON
Do(context.Background()) // execute
if err != nil {
// Handle error
panic(err)
}
// Access "timeline" aggregate in search result.
agg, found := searchResult.Aggregations.Terms("timeline")
if !found {
log.Fatalf("we should have a terms aggregation called %q", "timeline")
}
for _, userBucket := range agg.Buckets {
// Every bucket should have the user field as key.
user := userBucket.Key
// The sub-aggregation history should have the number of tweets per year.
histogram, found := userBucket.DateHistogram("history")
if found {
for _, year := range histogram.Buckets {
var key string
if s := year.KeyAsString; s != nil {
key = *s
}
fmt.Printf("user %q has %d tweets in %q\n", user, year.DocCount, key)
}
}
}
}
func ExampleSearchResult() {
client, err := elastic.NewClient()
if err != nil {
panic(err)
}
// Do a search
searchResult, err := client.Search().Index("twitter").Query(elastic.NewMatchAllQuery()).Do(context.Background())
if err != nil {
panic(err)
}
// searchResult is of type SearchResult and returns hits, suggestions,
// and all kinds of other information from Elasticsearch.
fmt.Printf("Query took %d milliseconds\n", searchResult.TookInMillis)
// Each is a utility function that iterates over hits in a search result.
// It makes sure you don't need to check for nil values in the response.
// However, it ignores errors in serialization. If you want full control
// over iterating the hits, see below.
var ttyp Tweet
for _, item := range searchResult.Each(reflect.TypeOf(ttyp)) {
t := item.(Tweet)
fmt.Printf("Tweet by %s: %s\n", t.User, t.Message)
}
fmt.Printf("Found a total of %d tweets\n", searchResult.TotalHits())
// Here's how you iterate hits with full control.
if searchResult.TotalHits() > 0 {
fmt.Printf("Found a total of %d tweets\n", searchResult.TotalHits())
// Iterate through results
for _, hit := range searchResult.Hits.Hits {
// hit.Index contains the name of the index
// Deserialize hit.Source into a Tweet (could also be just a map[string]interface{}).
var t Tweet
err := json.Unmarshal(hit.Source, &t)
if err != nil {
// Deserialization failed
}
// Work with tweet
fmt.Printf("Tweet by %s: %s\n", t.User, t.Message)
}
} else {
// No hits
fmt.Print("Found no tweets\n")
}
}
func ExampleClusterHealthService() {
client, err := elastic.NewClient()
if err != nil {
panic(err)
}
// Get cluster health
res, err := client.ClusterHealth().Index("twitter").Do(context.Background())
if err != nil {
panic(err)
}
if res == nil {
panic(err)
}
fmt.Printf("Cluster status is %q\n", res.Status)
}
func ExampleClusterHealthService_WaitForStatus() {
client, err := elastic.NewClient()
if err != nil {
panic(err)
}
// Wait for status green
res, err := client.ClusterHealth().WaitForStatus("green").Timeout("15s").Do(context.Background())
if err != nil {
panic(err)
}
if res.TimedOut {
fmt.Printf("time out waiting for cluster status %q\n", "green")
} else {
fmt.Printf("cluster status is %q\n", res.Status)
}
}
func ExampleClusterStateService() {
client, err := elastic.NewClient()
if err != nil {
panic(err)
}
// Get cluster state
res, err := client.ClusterState().Metric("version").Do(context.Background())
if err != nil {
panic(err)
}
fmt.Printf("Cluster %q has version %d", res.ClusterName, res.Version)
}
================================================
FILE: exists.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// ExistsService checks for the existence of a document using HEAD.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-get.html
// for details.
type ExistsService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
id string
index string
typ string
preference string
realtime *bool
refresh string
routing string
parent string
}
// NewExistsService creates a new ExistsService.
func NewExistsService(client *Client) *ExistsService {
return &ExistsService{
client: client,
typ: "_doc",
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *ExistsService) Pretty(pretty bool) *ExistsService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *ExistsService) Human(human bool) *ExistsService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *ExistsService) ErrorTrace(errorTrace bool) *ExistsService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *ExistsService) FilterPath(filterPath ...string) *ExistsService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *ExistsService) Header(name string, value string) *ExistsService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *ExistsService) Headers(headers http.Header) *ExistsService {
s.headers = headers
return s
}
// Id is the document ID.
func (s *ExistsService) Id(id string) *ExistsService {
s.id = id
return s
}
// Index is the name of the index.
func (s *ExistsService) Index(index string) *ExistsService {
s.index = index
return s
}
// Type is the type of the document (use `_all` to fetch the first document
// matching the ID across all types).
func (s *ExistsService) Type(typ string) *ExistsService {
s.typ = typ
return s
}
// Preference specifies the node or shard the operation should be performed on (default: random).
func (s *ExistsService) Preference(preference string) *ExistsService {
s.preference = preference
return s
}
// Realtime specifies whether to perform the operation in realtime or search mode.
func (s *ExistsService) Realtime(realtime bool) *ExistsService {
s.realtime = &realtime
return s
}
// Refresh the shard containing the document before performing the operation.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-refresh.html
// for details.
func (s *ExistsService) Refresh(refresh string) *ExistsService {
s.refresh = refresh
return s
}
// Routing is a specific routing value.
func (s *ExistsService) Routing(routing string) *ExistsService {
s.routing = routing
return s
}
// Parent is the ID of the parent document.
func (s *ExistsService) Parent(parent string) *ExistsService {
s.parent = parent
return s
}
// buildURL builds the URL for the operation.
func (s *ExistsService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/{index}/{type}/{id}", map[string]string{
"id": s.id,
"index": s.index,
"type": s.typ,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.realtime != nil {
params.Set("realtime", fmt.Sprint(*s.realtime))
}
if s.refresh != "" {
params.Set("refresh", s.refresh)
}
if s.routing != "" {
params.Set("routing", s.routing)
}
if s.parent != "" {
params.Set("parent", s.parent)
}
if s.preference != "" {
params.Set("preference", s.preference)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *ExistsService) Validate() error {
var invalid []string
if s.id == "" {
invalid = append(invalid, "Id")
}
if s.index == "" {
invalid = append(invalid, "Index")
}
if s.typ == "" {
invalid = append(invalid, "Type")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *ExistsService) Do(ctx context.Context) (bool, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return false, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return false, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "HEAD",
Path: path,
Params: params,
IgnoreErrors: []int{404},
Headers: s.headers,
})
if err != nil {
return false, err
}
// Return operation response
switch res.StatusCode {
case http.StatusOK:
return true, nil
case http.StatusNotFound:
return false, nil
default:
return false, fmt.Errorf("elastic: got HTTP code %d when it should have been either 200 or 404", res.StatusCode)
}
}
================================================
FILE: exists_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestExists(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t) //, SetTraceLog(log.New(os.Stdout, "", 0)))
exists, err := client.Exists().Index(testIndexName).Id("1").Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if !exists {
t.Fatal("expected document to exist")
}
}
func TestExistsValidate(t *testing.T) {
client := setupTestClient(t)
// No index -> fail with error
res, err := NewExistsService(client).Id("1").Do(context.TODO())
if err == nil {
t.Fatalf("expected Delete to fail without index name")
}
if res != false {
t.Fatalf("expected result to be false; got: %v", res)
}
// No id -> fail with error
res, err = NewExistsService(client).Index(testIndexName).Do(context.TODO())
if err == nil {
t.Fatalf("expected Delete to fail without index name")
}
if res != false {
t.Fatalf("expected result to be false; got: %v", res)
}
}
================================================
FILE: explain.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// ExplainService computes a score explanation for a query and
// a specific document.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-explain.html.
type ExplainService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
id string
index string
typ string
q string
routing string
lenient *bool
analyzer string
df string
fields []string
lowercaseExpandedTerms *bool
xSourceInclude []string
analyzeWildcard *bool
parent string
preference string
xSource []string
defaultOperator string
xSourceExclude []string
source string
bodyJson interface{}
bodyString string
}
// NewExplainService creates a new ExplainService.
func NewExplainService(client *Client) *ExplainService {
return &ExplainService{
client: client,
typ: "_doc",
xSource: make([]string, 0),
xSourceExclude: make([]string, 0),
fields: make([]string, 0),
xSourceInclude: make([]string, 0),
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *ExplainService) Pretty(pretty bool) *ExplainService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *ExplainService) Human(human bool) *ExplainService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *ExplainService) ErrorTrace(errorTrace bool) *ExplainService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *ExplainService) FilterPath(filterPath ...string) *ExplainService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *ExplainService) Header(name string, value string) *ExplainService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *ExplainService) Headers(headers http.Header) *ExplainService {
s.headers = headers
return s
}
// Id is the document ID.
func (s *ExplainService) Id(id string) *ExplainService {
s.id = id
return s
}
// Index is the name of the index.
func (s *ExplainService) Index(index string) *ExplainService {
s.index = index
return s
}
// Type is the type of the document.
//
// Deprecated: Types are in the process of being removed.
func (s *ExplainService) Type(typ string) *ExplainService {
s.typ = typ
return s
}
// Source is the URL-encoded query definition (instead of using the request body).
func (s *ExplainService) Source(source string) *ExplainService {
s.source = source
return s
}
// XSourceExclude is a list of fields to exclude from the returned _source field.
func (s *ExplainService) XSourceExclude(xSourceExclude ...string) *ExplainService {
s.xSourceExclude = append(s.xSourceExclude, xSourceExclude...)
return s
}
// Lenient specifies whether format-based query failures
// (such as providing text to a numeric field) should be ignored.
func (s *ExplainService) Lenient(lenient bool) *ExplainService {
s.lenient = &lenient
return s
}
// Query in the Lucene query string syntax.
func (s *ExplainService) Q(q string) *ExplainService {
s.q = q
return s
}
// Routing sets a specific routing value.
func (s *ExplainService) Routing(routing string) *ExplainService {
s.routing = routing
return s
}
// AnalyzeWildcard specifies whether wildcards and prefix queries
// in the query string query should be analyzed (default: false).
func (s *ExplainService) AnalyzeWildcard(analyzeWildcard bool) *ExplainService {
s.analyzeWildcard = &analyzeWildcard
return s
}
// Analyzer is the analyzer for the query string query.
func (s *ExplainService) Analyzer(analyzer string) *ExplainService {
s.analyzer = analyzer
return s
}
// Df is the default field for query string query (default: _all).
func (s *ExplainService) Df(df string) *ExplainService {
s.df = df
return s
}
// Fields is a list of fields to return in the response.
func (s *ExplainService) Fields(fields ...string) *ExplainService {
s.fields = append(s.fields, fields...)
return s
}
// LowercaseExpandedTerms specifies whether query terms should be lowercased.
func (s *ExplainService) LowercaseExpandedTerms(lowercaseExpandedTerms bool) *ExplainService {
s.lowercaseExpandedTerms = &lowercaseExpandedTerms
return s
}
// XSourceInclude is a list of fields to extract and return from the _source field.
func (s *ExplainService) XSourceInclude(xSourceInclude ...string) *ExplainService {
s.xSourceInclude = append(s.xSourceInclude, xSourceInclude...)
return s
}
// DefaultOperator is the default operator for query string query (AND or OR).
func (s *ExplainService) DefaultOperator(defaultOperator string) *ExplainService {
s.defaultOperator = defaultOperator
return s
}
// Parent is the ID of the parent document.
func (s *ExplainService) Parent(parent string) *ExplainService {
s.parent = parent
return s
}
// Preference specifies the node or shard the operation should be performed on (default: random).
func (s *ExplainService) Preference(preference string) *ExplainService {
s.preference = preference
return s
}
// XSource is true or false to return the _source field or not, or a list of fields to return.
func (s *ExplainService) XSource(xSource ...string) *ExplainService {
s.xSource = append(s.xSource, xSource...)
return s
}
// Query sets a query definition using the Query DSL.
func (s *ExplainService) Query(query Query) *ExplainService {
src, err := query.Source()
if err != nil {
// Do nothing in case of an error
return s
}
body := make(map[string]interface{})
body["query"] = src
s.bodyJson = body
return s
}
// BodyJson sets the query definition using the Query DSL.
func (s *ExplainService) BodyJson(body interface{}) *ExplainService {
s.bodyJson = body
return s
}
// BodyString sets the query definition using the Query DSL as a string.
func (s *ExplainService) BodyString(body string) *ExplainService {
s.bodyString = body
return s
}
// buildURL builds the URL for the operation.
func (s *ExplainService) buildURL() (string, url.Values, error) {
// Build URL
var path string
var err error
if s.typ == "" || s.typ == "_doc" {
path, err = uritemplates.Expand("/{index}/_explain/{id}", map[string]string{
"id": s.id,
"index": s.index,
})
} else {
path, err = uritemplates.Expand("/{index}/{type}/{id}/_explain", map[string]string{
"id": s.id,
"index": s.index,
"type": s.typ,
})
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if len(s.xSource) > 0 {
params.Set("_source", strings.Join(s.xSource, ","))
}
if s.defaultOperator != "" {
params.Set("default_operator", s.defaultOperator)
}
if s.parent != "" {
params.Set("parent", s.parent)
}
if s.preference != "" {
params.Set("preference", s.preference)
}
if s.source != "" {
params.Set("source", s.source)
}
if len(s.xSourceExclude) > 0 {
params.Set("_source_excludes", strings.Join(s.xSourceExclude, ","))
}
if s.lenient != nil {
params.Set("lenient", fmt.Sprintf("%v", *s.lenient))
}
if s.q != "" {
params.Set("q", s.q)
}
if s.routing != "" {
params.Set("routing", s.routing)
}
if len(s.fields) > 0 {
params.Set("fields", strings.Join(s.fields, ","))
}
if s.lowercaseExpandedTerms != nil {
params.Set("lowercase_expanded_terms", fmt.Sprintf("%v", *s.lowercaseExpandedTerms))
}
if len(s.xSourceInclude) > 0 {
params.Set("_source_includes", strings.Join(s.xSourceInclude, ","))
}
if s.analyzeWildcard != nil {
params.Set("analyze_wildcard", fmt.Sprintf("%v", *s.analyzeWildcard))
}
if s.analyzer != "" {
params.Set("analyzer", s.analyzer)
}
if s.df != "" {
params.Set("df", s.df)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *ExplainService) Validate() error {
var invalid []string
if s.index == "" {
invalid = append(invalid, "Index")
}
if s.typ == "" {
invalid = append(invalid, "Type")
}
if s.id == "" {
invalid = append(invalid, "Id")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *ExplainService) Do(ctx context.Context) (*ExplainResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Setup HTTP request body
var body interface{}
if s.bodyJson != nil {
body = s.bodyJson
} else {
body = s.bodyString
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Body: body,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(ExplainResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// ExplainResponse is the response of ExplainService.Do.
type ExplainResponse struct {
Index string `json:"_index"`
Type string `json:"_type"`
Id string `json:"_id"`
Matched bool `json:"matched"`
Explanation map[string]interface{} `json:"explanation"`
}
================================================
FILE: explain_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestExplain(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
// Add a document
indexResult, err := client.Index().
Index(testIndexName).
Id("1").
BodyJson(&tweet1).
Refresh("true").
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if indexResult == nil {
t.Errorf("expected result to be != nil; got: %v", indexResult)
}
// Explain
query := NewTermQuery("user", "olivere")
expl, err := client.Explain(testIndexName, "_doc", "1").Query(query).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if expl == nil {
t.Fatal("expected to return an explanation")
}
if !expl.Matched {
t.Errorf("expected matched to be %v; got: %v", true, expl.Matched)
}
}
================================================
FILE: fetch_source_context.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"net/url"
"strings"
)
// FetchSourceContext enables source filtering, i.e. it allows control
// over how the _source field is returned with every hit. It is used
// with various endpoints, e.g. when searching for documents, retrieving
// individual documents, or even updating documents.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-request-source-filtering.html
// for details.
type FetchSourceContext struct {
fetchSource bool
includes []string
excludes []string
}
// NewFetchSourceContext returns a new FetchSourceContext.
func NewFetchSourceContext(fetchSource bool) *FetchSourceContext {
return &FetchSourceContext{
fetchSource: fetchSource,
includes: make([]string, 0),
excludes: make([]string, 0),
}
}
// FetchSource indicates whether to return the _source.
func (fsc *FetchSourceContext) FetchSource() bool {
return fsc.fetchSource
}
// SetFetchSource specifies whether to return the _source.
func (fsc *FetchSourceContext) SetFetchSource(fetchSource bool) {
fsc.fetchSource = fetchSource
}
// Include indicates to return specific parts of the _source.
// Wildcards are allowed here.
func (fsc *FetchSourceContext) Include(includes ...string) *FetchSourceContext {
fsc.includes = append(fsc.includes, includes...)
return fsc
}
// Exclude indicates to exclude specific parts of the _source.
// Wildcards are allowed here.
func (fsc *FetchSourceContext) Exclude(excludes ...string) *FetchSourceContext {
fsc.excludes = append(fsc.excludes, excludes...)
return fsc
}
// Source returns the JSON-serializable data to be used in a body.
func (fsc *FetchSourceContext) Source() (interface{}, error) {
if !fsc.fetchSource {
return false, nil
}
if len(fsc.includes) == 0 && len(fsc.excludes) == 0 {
return true, nil
}
src := make(map[string]interface{})
if len(fsc.includes) > 0 {
src["includes"] = fsc.includes
}
if len(fsc.excludes) > 0 {
src["excludes"] = fsc.excludes
}
return src, nil
}
// Query returns the parameters in a form suitable for a URL query string.
func (fsc *FetchSourceContext) Query() url.Values {
params := url.Values{}
if fsc.fetchSource {
if len(fsc.includes) > 0 {
params.Add("_source_includes", strings.Join(fsc.includes, ","))
}
if len(fsc.excludes) > 0 {
params.Add("_source_excludes", strings.Join(fsc.excludes, ","))
}
} else {
params.Add("_source", "false")
}
return params
}
================================================
FILE: fetch_source_context_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestFetchSourceContextNoFetchSource(t *testing.T) {
builder := NewFetchSourceContext(false)
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `false`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestFetchSourceContextNoFetchSourceIgnoreIncludesAndExcludes(t *testing.T) {
builder := NewFetchSourceContext(false).Include("a", "b").Exclude("c")
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `false`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestFetchSourceContextFetchSource(t *testing.T) {
builder := NewFetchSourceContext(true)
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `true`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestFetchSourceContextFetchSourceWithIncludesOnly(t *testing.T) {
builder := NewFetchSourceContext(true).Include("a", "b")
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"includes":["a","b"]}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestFetchSourceContextFetchSourceWithIncludesAndExcludes(t *testing.T) {
builder := NewFetchSourceContext(true).Include("a", "b").Exclude("c")
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"excludes":["c"],"includes":["a","b"]}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestFetchSourceContextQueryDefaults(t *testing.T) {
builder := NewFetchSourceContext(true)
values := builder.Query()
got := values.Encode()
expected := ""
if got != expected {
t.Errorf("expected %q; got: %q", expected, got)
}
}
func TestFetchSourceContextQueryNoFetchSource(t *testing.T) {
builder := NewFetchSourceContext(false)
values := builder.Query()
got := values.Encode()
expected := "_source=false"
if got != expected {
t.Errorf("expected %q; got: %q", expected, got)
}
}
func TestFetchSourceContextQueryFetchSourceWithIncludesAndExcludes(t *testing.T) {
builder := NewFetchSourceContext(true).Include("a", "b").Exclude("c")
values := builder.Query()
got := values.Encode()
expected := "_source_excludes=c&_source_includes=a%2Cb"
if got != expected {
t.Errorf("expected %q; got: %q", expected, got)
}
}
================================================
FILE: field_caps.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// FieldCapsService allows retrieving the capabilities of fields among multiple indices.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-field-caps.html
// for details
type FieldCapsService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
index []string
allowNoIndices *bool
expandWildcards string
fields []string
ignoreUnavailable *bool
includeUnmapped *bool
bodyJson interface{}
bodyString string
}
// NewFieldCapsService creates a new FieldCapsService
func NewFieldCapsService(client *Client) *FieldCapsService {
return &FieldCapsService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *FieldCapsService) Pretty(pretty bool) *FieldCapsService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *FieldCapsService) Human(human bool) *FieldCapsService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *FieldCapsService) ErrorTrace(errorTrace bool) *FieldCapsService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *FieldCapsService) FilterPath(filterPath ...string) *FieldCapsService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *FieldCapsService) Header(name string, value string) *FieldCapsService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *FieldCapsService) Headers(headers http.Header) *FieldCapsService {
s.headers = headers
return s
}
// Index is a list of index names; use `_all` or empty string to perform
// the operation on all indices.
func (s *FieldCapsService) Index(index ...string) *FieldCapsService {
s.index = append(s.index, index...)
return s
}
// AllowNoIndices indicates whether to ignore if a wildcard indices expression
// resolves into no concrete indices.
// (This includes `_all` string or when no indices have been specified).
func (s *FieldCapsService) AllowNoIndices(allowNoIndices bool) *FieldCapsService {
s.allowNoIndices = &allowNoIndices
return s
}
// ExpandWildcards indicates whether to expand wildcard expression to
// concrete indices that are open, closed or both.
func (s *FieldCapsService) ExpandWildcards(expandWildcards string) *FieldCapsService {
s.expandWildcards = expandWildcards
return s
}
// Fields is a list of fields for to get field capabilities.
func (s *FieldCapsService) Fields(fields ...string) *FieldCapsService {
s.fields = append(s.fields, fields...)
return s
}
// IgnoreUnavailable is documented as: Whether specified concrete indices should be ignored when unavailable (missing or closed).
func (s *FieldCapsService) IgnoreUnavailable(ignoreUnavailable bool) *FieldCapsService {
s.ignoreUnavailable = &ignoreUnavailable
return s
}
// IncludeUnmapped specifies whether unmapped fields whould be included in the response.
func (s *FieldCapsService) IncludeUnmapped(includeUnmapped bool) *FieldCapsService {
s.includeUnmapped = &includeUnmapped
return s
}
// BodyJson is documented as: Field json objects containing the name and optionally a range to filter out indices result, that have results outside the defined bounds.
func (s *FieldCapsService) BodyJson(body interface{}) *FieldCapsService {
s.bodyJson = body
return s
}
// BodyString is documented as: Field json objects containing the name and optionally a range to filter out indices result, that have results outside the defined bounds.
func (s *FieldCapsService) BodyString(body string) *FieldCapsService {
s.bodyString = body
return s
}
// buildURL builds the URL for the operation.
func (s *FieldCapsService) buildURL() (string, url.Values, error) {
// Build URL
var err error
var path string
if len(s.index) > 0 {
path, err = uritemplates.Expand("/{index}/_field_caps", map[string]string{
"index": strings.Join(s.index, ","),
})
} else {
path = "/_field_caps"
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.allowNoIndices != nil {
params.Set("allow_no_indices", fmt.Sprint(*s.allowNoIndices))
}
if s.expandWildcards != "" {
params.Set("expand_wildcards", s.expandWildcards)
}
if len(s.fields) > 0 {
params.Set("fields", strings.Join(s.fields, ","))
}
if s.ignoreUnavailable != nil {
params.Set("ignore_unavailable", fmt.Sprint(*s.ignoreUnavailable))
}
if s.includeUnmapped != nil {
params.Set("include_unmapped", fmt.Sprint(*s.includeUnmapped))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *FieldCapsService) Validate() error {
return nil
}
// Do executes the operation.
func (s *FieldCapsService) Do(ctx context.Context) (*FieldCapsResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Setup HTTP request body
var body interface{}
if s.bodyJson != nil {
body = s.bodyJson
} else {
body = s.bodyString
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "POST",
Path: path,
Params: params,
Body: body,
IgnoreErrors: []int{http.StatusNotFound},
Headers: s.headers,
})
if err != nil {
return nil, err
}
// TODO(oe): Is 404 really a valid response here?
if res.StatusCode == http.StatusNotFound {
return &FieldCapsResponse{}, nil
}
// Return operation response
ret := new(FieldCapsResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// -- Request --
// FieldCapsRequest can be used to set up the body to be used in the
// Field Capabilities API.
type FieldCapsRequest struct {
Fields []string `json:"fields"` // list of fields to retrieve
IndexFilter Query `json:"index_filter,omitempty"`
RuntimeMappings RuntimeMappings `json:"runtime_mappings,omitempty"`
}
// -- Response --
// FieldCapsResponse contains field capabilities.
type FieldCapsResponse struct {
Indices []string `json:"indices,omitempty"` // list of index names
Fields map[string]FieldCapsType `json:"fields,omitempty"` // Name -> type -> caps
}
// FieldCapsType represents a mapping from type (e.g. keyword)
// to capabilities.
type FieldCapsType map[string]FieldCaps // type -> caps
// FieldCaps contains capabilities of an individual field.
type FieldCaps struct {
Type string `json:"type"`
MetadataField bool `json:"metadata_field"`
Searchable bool `json:"searchable"`
Aggregatable bool `json:"aggregatable"`
Indices []string `json:"indices,omitempty"`
NonSearchableIndices []string `json:"non_searchable_indices,omitempty"`
NonAggregatableIndices []string `json:"non_aggregatable_indices,omitempty"`
Meta map[string]interface{} `json:"meta,omitempty"`
}
================================================
FILE: field_caps_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"net/url"
"reflect"
"sort"
"testing"
)
func TestFieldCapsURLs(t *testing.T) {
tests := []struct {
Service *FieldCapsService
ExpectedPath string
ExpectedParams url.Values
}{
{
Service: &FieldCapsService{},
ExpectedPath: "/_field_caps",
ExpectedParams: url.Values{},
},
{
Service: &FieldCapsService{
index: []string{"index1", "index2"},
},
ExpectedPath: "/index1%2Cindex2/_field_caps",
ExpectedParams: url.Values{},
},
{
Service: &FieldCapsService{
index: []string{"index_*"},
pretty: boolPtr(true),
},
ExpectedPath: "/index_%2A/_field_caps",
ExpectedParams: url.Values{"pretty": []string{"true"}},
},
}
for _, test := range tests {
gotPath, gotParams, err := test.Service.buildURL()
if err != nil {
t.Fatalf("expected no error; got: %v", err)
}
if gotPath != test.ExpectedPath {
t.Errorf("expected URL path = %q; got: %q", test.ExpectedPath, gotPath)
}
if gotParams.Encode() != test.ExpectedParams.Encode() {
t.Errorf("expected URL params = %v; got: %v", test.ExpectedParams, gotParams)
}
}
}
func TestFieldCapsRequestSerialize(t *testing.T) {
req := &FieldCapsRequest{
Fields: []string{"creation_date", "answer_count"},
}
data, err := json.Marshal(req)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"fields":["creation_date","answer_count"]}`
if got != expected {
t.Fatalf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestFieldCapsRequestDeserialize(t *testing.T) {
body := `{
"fields" : ["creation_date", "answer_count"]
}`
var request FieldCapsRequest
if err := json.Unmarshal([]byte(body), &request); err != nil {
t.Fatalf("unexpected error during unmarshalling: %v", err)
}
sort.Sort(lexicographically{request.Fields})
expectedFields := []string{"answer_count", "creation_date"}
if !reflect.DeepEqual(request.Fields, expectedFields) {
t.Fatalf("expected fields to be %v, got %v", expectedFields, request.Fields)
}
}
func TestFieldCapsResponse(t *testing.T) {
body := `{
"_shards": {
"total": 1,
"successful": 1,
"failed": 0
},
"fields": {
"rating": {
"long": {
"searchable": true,
"aggregatable": false,
"indices": ["index1", "index2"],
"non_aggregatable_indices": ["index1"]
},
"keyword": {
"searchable": false,
"aggregatable": true,
"indices": ["index3", "index4"],
"non_searchable_indices": ["index4"]
}
},
"title": {
"text": {
"searchable": true,
"aggregatable": false
}
}
}
}`
var resp FieldCapsResponse
if err := json.Unmarshal([]byte(body), &resp); err != nil {
t.Errorf("unexpected error during unmarshalling: %v", err)
}
field, ok := resp.Fields["rating"]
if !ok {
t.Errorf("expected rating to be in the fields map, didn't find it")
}
{
caps, ok := field["long"]
if !ok {
t.Errorf("expected rating.long caps to be found")
}
if want, have := true, caps.Searchable; want != have {
t.Errorf("expected rating.long.searchable to be %v, got %v", want, have)
}
if want, have := false, caps.Aggregatable; want != have {
t.Errorf("expected rating.long.aggregatable to be %v, got %v", want, have)
}
if want, have := []string{"index1", "index2"}, caps.Indices; !reflect.DeepEqual(want, have) {
t.Errorf("expected rating.long.indices to be %v, got %v", want, have)
}
if want, have := []string{"index1"}, caps.NonAggregatableIndices; !reflect.DeepEqual(want, have) {
t.Errorf("expected rating.long.non_aggregatable_indices to be %v, got %v", want, have)
}
if want, have := 0, len(caps.NonSearchableIndices); want != have {
t.Errorf("expected rating.keyword.non_searchable_indices to be %v, got %v", want, have)
}
}
{
caps, ok := field["keyword"]
if !ok {
t.Errorf("expected rating.keyword caps to be found")
}
if want, have := false, caps.Searchable; want != have {
t.Errorf("expected rating.keyword.searchable to be %v, got %v", want, have)
}
if want, have := true, caps.Aggregatable; want != have {
t.Errorf("expected rating.keyword.aggregatable to be %v, got %v", want, have)
}
if want, have := []string{"index3", "index4"}, caps.Indices; !reflect.DeepEqual(want, have) {
t.Errorf("expected rating.keyword.indices to be %v, got %v", want, have)
}
if want, have := 0, len(caps.NonAggregatableIndices); want != have {
t.Errorf("expected rating.keyword.non_aggregatable_indices to be %v, got %v", want, have)
}
if want, have := []string{"index4"}, caps.NonSearchableIndices; !reflect.DeepEqual(want, have) {
t.Errorf("expected rating.keyword.non_searchable_indices to be %v, got %v", want, have)
}
}
}
func TestFieldCapsIntegrationTest(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t) //, SetTraceLog(log.New(os.Stdout, "", 0)))
res, err := client.FieldCaps("_all").Fields("user", "message", "retweets", "created").Pretty(true).Do(context.TODO())
if err != nil {
t.Fatalf("expected no error; got: %v", err)
}
if res == nil {
t.Fatalf("expected response; got: %v", res)
}
}
================================================
FILE: geo_point.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"fmt"
"strconv"
"strings"
)
// GeoPoint is a geographic position described via latitude and longitude.
type GeoPoint struct {
Lat float64 `json:"lat"`
Lon float64 `json:"lon"`
}
// Source returns the object to be serialized in Elasticsearch DSL.
func (pt *GeoPoint) Source() map[string]float64 {
return map[string]float64{
"lat": pt.Lat,
"lon": pt.Lon,
}
}
// MarshalJSON encodes the GeoPoint to JSON.
func (pt *GeoPoint) MarshalJSON() ([]byte, error) {
return json.Marshal(pt.Source())
}
// GeoPointFromLatLon initializes a new GeoPoint by latitude and longitude.
func GeoPointFromLatLon(lat, lon float64) *GeoPoint {
return &GeoPoint{Lat: lat, Lon: lon}
}
// GeoPointFromString initializes a new GeoPoint by a string that is
// formatted as "{latitude},{longitude}", e.g. "40.10210,-70.12091".
func GeoPointFromString(latLon string) (*GeoPoint, error) {
latlon := strings.SplitN(latLon, ",", 2)
if len(latlon) != 2 {
return nil, fmt.Errorf("elastic: %s is not a valid geo point string", latLon)
}
lat, err := strconv.ParseFloat(latlon[0], 64)
if err != nil {
return nil, err
}
lon, err := strconv.ParseFloat(latlon[1], 64)
if err != nil {
return nil, err
}
return &GeoPoint{Lat: lat, Lon: lon}, nil
}
================================================
FILE: geo_point_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"testing"
)
func TestGeoPointSource(t *testing.T) {
pt := GeoPoint{Lat: 40, Lon: -70}
data, err := json.Marshal(pt.Source())
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"lat":40,"lon":-70}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestGeoPointMarshalJSON(t *testing.T) {
pt := GeoPoint{Lat: 40, Lon: -70}
data, err := json.Marshal(pt)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"lat":40,"lon":-70}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestGeoPointIndexAndSearch(t *testing.T) {
client := setupTestClient(t) // , SetTraceLog(log.New(os.Stdout, "", 0)))
// Create index
mapping := `
{
"settings":{
"number_of_shards":1,
"number_of_replicas":0
},
"mappings":{
"properties":{
"name":{
"type":"keyword"
},
"location":{
"type":"geo_point"
}
}
}
}
`
createIndex, err := client.CreateIndex(testIndexName).Body(mapping).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if createIndex == nil {
t.Errorf("expected result to be != nil; got: %v", createIndex)
}
// Add document
type City struct {
Name string `json:"name"`
Location *GeoPoint `json:"location"`
}
munich := &City{
Name: "München",
Location: GeoPointFromLatLon(48.137154, 11.576124),
}
_, err = client.Index().Index(testIndexName).Id("1").BodyJson(&munich).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Refresh
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Get document
q := NewGeoDistanceQuery("location")
q = q.GeoPoint(GeoPointFromLatLon(48, 11))
q = q.Distance("50km")
res, err := client.
Search(testIndexName).
Query(q).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if want, have := int64(1), res.TotalHits(); want != have {
t.Fatalf("TotalHits: want %d, have %d", want, have)
}
var doc City
if err := json.Unmarshal(res.Hits.Hits[0].Source, &doc); err != nil {
t.Fatal(err)
}
if want, have := munich.Name, doc.Name; want != have {
t.Fatalf("Name: want %q, have %q", want, have)
}
if want, have := munich.Location.Lat, doc.Location.Lat; want != have {
t.Fatalf("Lat: want %v, have %v", want, have)
}
if want, have := munich.Location.Lon, doc.Location.Lon; want != have {
t.Fatalf("Lon: want %v, have %v", want, have)
}
}
================================================
FILE: get.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// GetService allows to get a typed JSON document from the index based
// on its id.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-get.html
// for details.
type GetService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
index string
typ string
id string
routing string
preference string
storedFields []string
refresh string
realtime *bool
fsc *FetchSourceContext
version interface{}
versionType string
parent string
ignoreErrorsOnGeneratedFields *bool
}
// NewGetService creates a new GetService.
func NewGetService(client *Client) *GetService {
return &GetService{
client: client,
typ: "_doc",
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *GetService) Pretty(pretty bool) *GetService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *GetService) Human(human bool) *GetService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *GetService) ErrorTrace(errorTrace bool) *GetService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *GetService) FilterPath(filterPath ...string) *GetService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *GetService) Header(name string, value string) *GetService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *GetService) Headers(headers http.Header) *GetService {
s.headers = headers
return s
}
// Index is the name of the index.
func (s *GetService) Index(index string) *GetService {
s.index = index
return s
}
// Type is the type of the document
//
// Deprecated: Types are in the process of being removed.
func (s *GetService) Type(typ string) *GetService {
s.typ = typ
return s
}
// Id is the document ID.
func (s *GetService) Id(id string) *GetService {
s.id = id
return s
}
// Parent is the ID of the parent document.
func (s *GetService) Parent(parent string) *GetService {
s.parent = parent
return s
}
// Routing is the specific routing value.
func (s *GetService) Routing(routing string) *GetService {
s.routing = routing
return s
}
// Preference specifies the node or shard the operation should be performed on (default: random).
func (s *GetService) Preference(preference string) *GetService {
s.preference = preference
return s
}
// StoredFields is a list of fields to return in the response.
func (s *GetService) StoredFields(storedFields ...string) *GetService {
s.storedFields = append(s.storedFields, storedFields...)
return s
}
func (s *GetService) FetchSource(fetchSource bool) *GetService {
if s.fsc == nil {
s.fsc = NewFetchSourceContext(fetchSource)
} else {
s.fsc.SetFetchSource(fetchSource)
}
return s
}
func (s *GetService) FetchSourceContext(fetchSourceContext *FetchSourceContext) *GetService {
s.fsc = fetchSourceContext
return s
}
// Refresh the shard containing the document before performing the operation.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-refresh.html
// for details.
func (s *GetService) Refresh(refresh string) *GetService {
s.refresh = refresh
return s
}
// Realtime specifies whether to perform the operation in realtime or search mode.
func (s *GetService) Realtime(realtime bool) *GetService {
s.realtime = &realtime
return s
}
// VersionType is the specific version type.
func (s *GetService) VersionType(versionType string) *GetService {
s.versionType = versionType
return s
}
// Version is an explicit version number for concurrency control.
func (s *GetService) Version(version interface{}) *GetService {
s.version = version
return s
}
// IgnoreErrorsOnGeneratedFields indicates whether to ignore fields that
// are generated if the transaction log is accessed.
func (s *GetService) IgnoreErrorsOnGeneratedFields(ignore bool) *GetService {
s.ignoreErrorsOnGeneratedFields = &ignore
return s
}
// Validate checks if the operation is valid.
func (s *GetService) Validate() error {
var invalid []string
if s.id == "" {
invalid = append(invalid, "Id")
}
if s.index == "" {
invalid = append(invalid, "Index")
}
if s.typ == "" {
invalid = append(invalid, "Type")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// buildURL builds the URL for the operation.
func (s *GetService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/{index}/{type}/{id}", map[string]string{
"id": s.id,
"index": s.index,
"type": s.typ,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.routing != "" {
params.Set("routing", s.routing)
}
if s.parent != "" {
params.Set("parent", s.parent)
}
if s.preference != "" {
params.Set("preference", s.preference)
}
if len(s.storedFields) > 0 {
params.Set("stored_fields", strings.Join(s.storedFields, ","))
}
if s.refresh != "" {
params.Set("refresh", s.refresh)
}
if s.version != nil {
params.Set("version", fmt.Sprintf("%v", s.version))
}
if s.versionType != "" {
params.Set("version_type", s.versionType)
}
if s.realtime != nil {
params.Set("realtime", fmt.Sprintf("%v", *s.realtime))
}
if s.ignoreErrorsOnGeneratedFields != nil {
params.Add("ignore_errors_on_generated_fields", fmt.Sprintf("%v", *s.ignoreErrorsOnGeneratedFields))
}
if s.fsc != nil {
for k, values := range s.fsc.Query() {
params.Add(k, strings.Join(values, ","))
}
}
return path, params, nil
}
// Do executes the operation.
func (s *GetService) Do(ctx context.Context) (*GetResult, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(GetResult)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// -- Result of a get request.
// GetResult is the outcome of GetService.Do.
type GetResult struct {
Index string `json:"_index"` // index meta field
Type string `json:"_type"` // type meta field
Id string `json:"_id"` // id meta field
Uid string `json:"_uid"` // uid meta field (see MapperService.java for all meta fields)
Routing string `json:"_routing"` // routing meta field
Parent string `json:"_parent"` // parent meta field
Version *int64 `json:"_version"` // version number, when Version is set to true in SearchService
SeqNo *int64 `json:"_seq_no"`
PrimaryTerm *int64 `json:"_primary_term"`
Source json.RawMessage `json:"_source,omitempty"`
Found bool `json:"found,omitempty"`
Fields map[string]interface{} `json:"fields,omitempty"`
//Error string `json:"error,omitempty"` // used only in MultiGet
// TODO double-check that MultiGet now returns details error information
Error *ErrorDetails `json:"error,omitempty"` // only used in MultiGet
}
================================================
FILE: get_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"testing"
)
func TestGet(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Get document 1
res, err := client.Get().Index(testIndexName).Id("1").Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if res.Found != true {
t.Errorf("expected Found = true; got %v", res.Found)
}
if res.Source == nil {
t.Errorf("expected Source != nil; got %v", res.Source)
}
// Get non existent document 99
res, err = client.Get().Index(testIndexName).Id("99").Do(context.TODO())
if err == nil {
t.Fatalf("expected error; got: %v", err)
}
if !IsNotFound(err) {
t.Errorf("expected NotFound error; got: %v", err)
}
if res != nil {
t.Errorf("expected no response; got: %v", res)
}
}
func TestGetWithSourceFiltering(t *testing.T) {
client := setupTestClientAndCreateIndex(t) // , SetTraceLog(log.New(os.Stdout, "", 0)))
tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Get document 1, without source
res, err := client.Get().Index(testIndexName).Id("1").FetchSource(false).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if res.Found != true {
t.Errorf("expected Found = true; got %v", res.Found)
}
if res.Source != nil {
t.Errorf("expected Source == nil; got %v", res.Source)
}
// Get document 1, exclude Message field
fsc := NewFetchSourceContext(true).Exclude("message")
res, err = client.Get().Index(testIndexName).Id("1").FetchSourceContext(fsc).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if res.Found != true {
t.Errorf("expected Found = true; got %v", res.Found)
}
if res.Source == nil {
t.Errorf("expected Source != nil; got %v", res.Source)
}
var tw tweet
err = json.Unmarshal(res.Source, &tw)
if err != nil {
t.Fatal(err)
}
if tw.User != "olivere" {
t.Errorf("expected user %q; got: %q", "olivere", tw.User)
}
if tw.Message != "" {
t.Errorf("expected message %q; got: %q", "", tw.Message)
}
}
func TestGetWithFields(t *testing.T) {
client := setupTestClientAndCreateIndex(t) //, SetTraceLog(log.New(os.Stdout, "", 0)))
tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Get document 1, specifying fields
res, err := client.Get().Index(testIndexName).Id("1").StoredFields("message").Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if res.Found != true {
t.Errorf("expected Found = true; got: %v", res.Found)
}
// We must NOT have the "user" field
_, ok := res.Fields["user"]
if ok {
t.Fatalf("expected no field %q in document", "user")
}
// We must have the "message" field
messageField, ok := res.Fields["message"]
if !ok {
t.Fatalf("expected field %q in document", "message")
}
// Depending on the version of elasticsearch the message field will be returned
// as a string or a slice of strings. This test works in both cases.
messageString, ok := messageField.(string)
if !ok {
messageArray, ok := messageField.([]interface{})
if !ok {
t.Fatalf("expected field %q to be a string or a slice of strings; got: %T", "message", messageField)
} else {
messageString, ok = messageArray[0].(string)
if !ok {
t.Fatalf("expected field %q to be a string or a slice of strings; got: %T", "message", messageField)
}
}
}
if messageString != tweet1.Message {
t.Errorf("expected message %q; got: %q", tweet1.Message, messageString)
}
}
func TestGetValidate(t *testing.T) {
// Mitigate against http://stackoverflow.com/questions/27491738/elasticsearch-go-index-failures-no-feature-for-name
client := setupTestClientAndCreateIndex(t)
if _, err := client.Get().Do(context.TODO()); err == nil {
t.Fatal("expected Get to fail")
}
if _, err := client.Get().Index(testIndexName).Do(context.TODO()); err == nil {
t.Fatal("expected Get to fail")
}
if _, err := client.Get().Do(context.TODO()); err == nil {
t.Fatal("expected Get to fail")
}
if _, err := client.Get().Id("1").Do(context.TODO()); err == nil {
t.Fatal("expected Get to fail")
}
if _, err := client.Get().Index(testIndexName).Do(context.TODO()); err == nil {
t.Fatal("expected Get to fail")
}
if _, err := client.Get().Id("1").Do(context.TODO()); err == nil {
t.Fatal("expected Get to fail")
}
}
================================================
FILE: go.mod
================================================
module github.com/olivere/elastic/v7
go 1.17
require (
github.com/aws/aws-sdk-go v1.43.21
github.com/fortytw2/leaktest v1.3.0
github.com/google/go-cmp v0.5.7
github.com/mailru/easyjson v0.7.7
github.com/opentracing/opentracing-go v1.2.0
github.com/pkg/errors v0.9.1
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9
go.opencensus.io v0.23.0
go.opentelemetry.io/otel v1.5.0
)
require (
github.com/go-logr/logr v1.2.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/smartystreets/assertions v1.1.1 // indirect
github.com/smartystreets/gunit v1.4.2 // indirect
go.opentelemetry.io/otel/trace v1.5.0 // indirect
)
================================================
FILE: highlight.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// Highlight allows highlighting search results on one or more fields.
// For details, see:
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-request-highlighting.html
type Highlight struct {
fields []*HighlighterField
tagsSchema *string
highlightFilter *bool
fragmentSize *int
numOfFragments *int
preTags []string
postTags []string
order *string
encoder *string
requireFieldMatch *bool
maxAnalyzedOffset *int
boundaryMaxScan *int
boundaryChars *string
boundaryScannerType *string
boundaryScannerLocale *string
highlighterType *string
fragmenter *string
highlightQuery Query
noMatchSize *int
phraseLimit *int
options map[string]interface{}
forceSource *bool
useExplicitFieldOrder bool
}
func NewHighlight() *Highlight {
hl := &Highlight{
options: make(map[string]interface{}),
}
return hl
}
func (hl *Highlight) Fields(fields ...*HighlighterField) *Highlight {
hl.fields = append(hl.fields, fields...)
return hl
}
func (hl *Highlight) Field(name string) *Highlight {
field := NewHighlighterField(name)
hl.fields = append(hl.fields, field)
return hl
}
func (hl *Highlight) TagsSchema(schemaName string) *Highlight {
hl.tagsSchema = &schemaName
return hl
}
func (hl *Highlight) HighlightFilter(highlightFilter bool) *Highlight {
hl.highlightFilter = &highlightFilter
return hl
}
func (hl *Highlight) FragmentSize(fragmentSize int) *Highlight {
hl.fragmentSize = &fragmentSize
return hl
}
func (hl *Highlight) NumOfFragments(numOfFragments int) *Highlight {
hl.numOfFragments = &numOfFragments
return hl
}
func (hl *Highlight) Encoder(encoder string) *Highlight {
hl.encoder = &encoder
return hl
}
func (hl *Highlight) PreTags(preTags ...string) *Highlight {
hl.preTags = append(hl.preTags, preTags...)
return hl
}
func (hl *Highlight) PostTags(postTags ...string) *Highlight {
hl.postTags = append(hl.postTags, postTags...)
return hl
}
func (hl *Highlight) Order(order string) *Highlight {
hl.order = &order
return hl
}
func (hl *Highlight) RequireFieldMatch(requireFieldMatch bool) *Highlight {
hl.requireFieldMatch = &requireFieldMatch
return hl
}
func (hl *Highlight) MaxAnalyzedOffset(maxAnalyzedOffset int) *Highlight {
hl.maxAnalyzedOffset = &maxAnalyzedOffset
return hl
}
func (hl *Highlight) BoundaryMaxScan(boundaryMaxScan int) *Highlight {
hl.boundaryMaxScan = &boundaryMaxScan
return hl
}
func (hl *Highlight) BoundaryChars(boundaryChars string) *Highlight {
hl.boundaryChars = &boundaryChars
return hl
}
func (hl *Highlight) BoundaryScannerType(boundaryScannerType string) *Highlight {
hl.boundaryScannerType = &boundaryScannerType
return hl
}
func (hl *Highlight) BoundaryScannerLocale(boundaryScannerLocale string) *Highlight {
hl.boundaryScannerLocale = &boundaryScannerLocale
return hl
}
func (hl *Highlight) HighlighterType(highlighterType string) *Highlight {
hl.highlighterType = &highlighterType
return hl
}
func (hl *Highlight) Fragmenter(fragmenter string) *Highlight {
hl.fragmenter = &fragmenter
return hl
}
func (hl *Highlight) HighlightQuery(highlightQuery Query) *Highlight {
hl.highlightQuery = highlightQuery
return hl
}
func (hl *Highlight) NoMatchSize(noMatchSize int) *Highlight {
hl.noMatchSize = &noMatchSize
return hl
}
func (hl *Highlight) Options(options map[string]interface{}) *Highlight {
hl.options = options
return hl
}
func (hl *Highlight) ForceSource(forceSource bool) *Highlight {
hl.forceSource = &forceSource
return hl
}
func (hl *Highlight) UseExplicitFieldOrder(useExplicitFieldOrder bool) *Highlight {
hl.useExplicitFieldOrder = useExplicitFieldOrder
return hl
}
// Creates the query source for the bool query.
func (hl *Highlight) Source() (interface{}, error) {
// Returns the map inside of "highlight":
// "highlight":{
// ... this ...
// }
source := make(map[string]interface{})
if hl.tagsSchema != nil {
source["tags_schema"] = *hl.tagsSchema
}
if hl.preTags != nil && len(hl.preTags) > 0 {
source["pre_tags"] = hl.preTags
}
if hl.postTags != nil && len(hl.postTags) > 0 {
source["post_tags"] = hl.postTags
}
if hl.order != nil {
source["order"] = *hl.order
}
if hl.highlightFilter != nil {
source["highlight_filter"] = *hl.highlightFilter
}
if hl.fragmentSize != nil {
source["fragment_size"] = *hl.fragmentSize
}
if hl.numOfFragments != nil {
source["number_of_fragments"] = *hl.numOfFragments
}
if hl.encoder != nil {
source["encoder"] = *hl.encoder
}
if hl.requireFieldMatch != nil {
source["require_field_match"] = *hl.requireFieldMatch
}
if hl.maxAnalyzedOffset != nil {
source["max_analyzed_offset"] = *hl.maxAnalyzedOffset
}
if hl.boundaryMaxScan != nil {
source["boundary_max_scan"] = *hl.boundaryMaxScan
}
if hl.boundaryChars != nil {
source["boundary_chars"] = *hl.boundaryChars
}
if hl.boundaryScannerType != nil {
source["boundary_scanner"] = *hl.boundaryScannerType
}
if hl.boundaryScannerLocale != nil {
source["boundary_scanner_locale"] = *hl.boundaryScannerLocale
}
if hl.highlighterType != nil {
source["type"] = *hl.highlighterType
}
if hl.fragmenter != nil {
source["fragmenter"] = *hl.fragmenter
}
if hl.highlightQuery != nil {
src, err := hl.highlightQuery.Source()
if err != nil {
return nil, err
}
source["highlight_query"] = src
}
if hl.noMatchSize != nil {
source["no_match_size"] = *hl.noMatchSize
}
if hl.phraseLimit != nil {
source["phrase_limit"] = *hl.phraseLimit
}
if hl.options != nil && len(hl.options) > 0 {
source["options"] = hl.options
}
if hl.forceSource != nil {
source["force_source"] = *hl.forceSource
}
if hl.fields != nil && len(hl.fields) > 0 {
if hl.useExplicitFieldOrder {
// Use a slice for the fields
var fields []map[string]interface{}
for _, field := range hl.fields {
src, err := field.Source()
if err != nil {
return nil, err
}
fmap := make(map[string]interface{})
fmap[field.Name] = src
fields = append(fields, fmap)
}
source["fields"] = fields
} else {
// Use a map for the fields
fields := make(map[string]interface{})
for _, field := range hl.fields {
src, err := field.Source()
if err != nil {
return nil, err
}
fields[field.Name] = src
}
source["fields"] = fields
}
}
return source, nil
}
// HighlighterField specifies a highlighted field.
type HighlighterField struct {
Name string
preTags []string
postTags []string
fragmentSize int
fragmentOffset int
numOfFragments int
highlightFilter *bool
order *string
requireFieldMatch *bool
boundaryMaxScan int
boundaryChars []rune
highlighterType *string
fragmenter *string
highlightQuery Query
noMatchSize *int
matchedFields []string
phraseLimit *int
options map[string]interface{}
forceSource *bool
/*
Name string
preTags []string
postTags []string
fragmentSize int
numOfFragments int
fragmentOffset int
highlightFilter *bool
order string
requireFieldMatch *bool
boundaryMaxScan int
boundaryChars []rune
highlighterType string
fragmenter string
highlightQuery Query
noMatchSize *int
matchedFields []string
options map[string]interface{}
forceSource *bool
*/
}
func NewHighlighterField(name string) *HighlighterField {
return &HighlighterField{
Name: name,
preTags: make([]string, 0),
postTags: make([]string, 0),
fragmentSize: -1,
fragmentOffset: -1,
numOfFragments: -1,
boundaryMaxScan: -1,
boundaryChars: make([]rune, 0),
matchedFields: make([]string, 0),
options: make(map[string]interface{}),
}
}
func (f *HighlighterField) PreTags(preTags ...string) *HighlighterField {
f.preTags = append(f.preTags, preTags...)
return f
}
func (f *HighlighterField) PostTags(postTags ...string) *HighlighterField {
f.postTags = append(f.postTags, postTags...)
return f
}
func (f *HighlighterField) FragmentSize(fragmentSize int) *HighlighterField {
f.fragmentSize = fragmentSize
return f
}
func (f *HighlighterField) FragmentOffset(fragmentOffset int) *HighlighterField {
f.fragmentOffset = fragmentOffset
return f
}
func (f *HighlighterField) NumOfFragments(numOfFragments int) *HighlighterField {
f.numOfFragments = numOfFragments
return f
}
func (f *HighlighterField) HighlightFilter(highlightFilter bool) *HighlighterField {
f.highlightFilter = &highlightFilter
return f
}
func (f *HighlighterField) Order(order string) *HighlighterField {
f.order = &order
return f
}
func (f *HighlighterField) RequireFieldMatch(requireFieldMatch bool) *HighlighterField {
f.requireFieldMatch = &requireFieldMatch
return f
}
func (f *HighlighterField) BoundaryMaxScan(boundaryMaxScan int) *HighlighterField {
f.boundaryMaxScan = boundaryMaxScan
return f
}
func (f *HighlighterField) BoundaryChars(boundaryChars ...rune) *HighlighterField {
f.boundaryChars = append(f.boundaryChars, boundaryChars...)
return f
}
func (f *HighlighterField) HighlighterType(highlighterType string) *HighlighterField {
f.highlighterType = &highlighterType
return f
}
func (f *HighlighterField) Fragmenter(fragmenter string) *HighlighterField {
f.fragmenter = &fragmenter
return f
}
func (f *HighlighterField) HighlightQuery(highlightQuery Query) *HighlighterField {
f.highlightQuery = highlightQuery
return f
}
func (f *HighlighterField) NoMatchSize(noMatchSize int) *HighlighterField {
f.noMatchSize = &noMatchSize
return f
}
func (f *HighlighterField) Options(options map[string]interface{}) *HighlighterField {
f.options = options
return f
}
func (f *HighlighterField) MatchedFields(matchedFields ...string) *HighlighterField {
f.matchedFields = append(f.matchedFields, matchedFields...)
return f
}
func (f *HighlighterField) PhraseLimit(phraseLimit int) *HighlighterField {
f.phraseLimit = &phraseLimit
return f
}
func (f *HighlighterField) ForceSource(forceSource bool) *HighlighterField {
f.forceSource = &forceSource
return f
}
func (f *HighlighterField) Source() (interface{}, error) {
source := make(map[string]interface{})
if f.preTags != nil && len(f.preTags) > 0 {
source["pre_tags"] = f.preTags
}
if f.postTags != nil && len(f.postTags) > 0 {
source["post_tags"] = f.postTags
}
if f.fragmentSize != -1 {
source["fragment_size"] = f.fragmentSize
}
if f.numOfFragments != -1 {
source["number_of_fragments"] = f.numOfFragments
}
if f.fragmentOffset != -1 {
source["fragment_offset"] = f.fragmentOffset
}
if f.highlightFilter != nil {
source["highlight_filter"] = *f.highlightFilter
}
if f.order != nil {
source["order"] = *f.order
}
if f.requireFieldMatch != nil {
source["require_field_match"] = *f.requireFieldMatch
}
if f.boundaryMaxScan != -1 {
source["boundary_max_scan"] = f.boundaryMaxScan
}
if f.boundaryChars != nil && len(f.boundaryChars) > 0 {
source["boundary_chars"] = f.boundaryChars
}
if f.highlighterType != nil {
source["type"] = *f.highlighterType
}
if f.fragmenter != nil {
source["fragmenter"] = *f.fragmenter
}
if f.highlightQuery != nil {
src, err := f.highlightQuery.Source()
if err != nil {
return nil, err
}
source["highlight_query"] = src
}
if f.noMatchSize != nil {
source["no_match_size"] = *f.noMatchSize
}
if f.matchedFields != nil && len(f.matchedFields) > 0 {
source["matched_fields"] = f.matchedFields
}
if f.phraseLimit != nil {
source["phrase_limit"] = *f.phraseLimit
}
if f.options != nil && len(f.options) > 0 {
source["options"] = f.options
}
if f.forceSource != nil {
source["force_source"] = *f.forceSource
}
return source, nil
}
================================================
FILE: highlight_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"testing"
)
func TestHighlighterField(t *testing.T) {
field := NewHighlighterField("grade")
src, err := field.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestHighlighterFieldWithOptions(t *testing.T) {
field := NewHighlighterField("grade").FragmentSize(2).NumOfFragments(1)
src, err := field.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"fragment_size":2,"number_of_fragments":1}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestHighlightWithStringField(t *testing.T) {
builder := NewHighlight().Field("grade")
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"fields":{"grade":{}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestHighlightWithFields(t *testing.T) {
gradeField := NewHighlighterField("grade")
builder := NewHighlight().Fields(gradeField)
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"fields":{"grade":{}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestHighlightWithMultipleFields(t *testing.T) {
gradeField := NewHighlighterField("grade")
colorField := NewHighlighterField("color")
builder := NewHighlight().Fields(gradeField, colorField)
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"fields":{"color":{},"grade":{}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestHighlighterWithExplicitFieldOrder(t *testing.T) {
gradeField := NewHighlighterField("grade").FragmentSize(2)
colorField := NewHighlighterField("color").FragmentSize(2).NumOfFragments(1)
builder := NewHighlight().Fields(gradeField, colorField).UseExplicitFieldOrder(true)
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"fields":[{"grade":{"fragment_size":2}},{"color":{"fragment_size":2,"number_of_fragments":1}}]}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestHighlightWithBoundarySettings(t *testing.T) {
builder := NewHighlight().
BoundaryChars(" \t\r").
BoundaryScannerType("word")
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"boundary_chars":" \t\r","boundary_scanner":"word"}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestHighlightWithTermQuery(t *testing.T) {
client := setupTestClientAndCreateIndex(t) //, SetTraceLog(log.New(os.Stdout, "", 0)))
tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
tweet2 := tweet{User: "olivere", Message: "Another unrelated topic."}
tweet3 := tweet{User: "sandrae", Message: "Cycling is fun to do."}
// Add all documents
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("3").BodyJson(&tweet3).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Specify highlighter
hl := NewHighlight()
hl = hl.Fields(NewHighlighterField("message"))
hl = hl.PreTags("").PostTags("")
// Match all should return all documents
query := NewPrefixQuery("message", "golang")
searchResult, err := client.Search().
Index(testIndexName).
Highlight(hl).
Query(query).
Pretty(true).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.Hits == nil {
t.Fatalf("expected SearchResult.Hits != nil; got nil")
}
if searchResult.TotalHits() != 1 {
t.Fatalf("expected SearchResult.TotalHits() = %d; got %d", 1, searchResult.TotalHits())
}
if len(searchResult.Hits.Hits) != 1 {
t.Fatalf("expected len(SearchResult.Hits.Hits) = %d; got %d", 1, len(searchResult.Hits.Hits))
}
hit := searchResult.Hits.Hits[0]
var tw tweet
if err := json.Unmarshal(hit.Source, &tw); err != nil {
t.Fatal(err)
}
if hit.Highlight == nil || len(hit.Highlight) == 0 {
t.Fatal("expected hit to have a highlight; got nil")
}
if hl, found := hit.Highlight["message"]; found {
if len(hl) != 1 {
t.Fatalf("expected to have one highlight for field \"message\"; got %d", len(hl))
}
expected := "Welcome to Golang and Elasticsearch."
if hl[0] != expected {
t.Errorf("expected to have highlight \"%s\"; got \"%s\"", expected, hl[0])
}
} else {
t.Fatal("expected to have a highlight on field \"message\"; got none")
}
}
================================================
FILE: index.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// IndexService adds or updates a typed JSON document in a specified index,
// making it searchable.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-index_.html
// for details.
type IndexService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
id string
index string
typ string
parent string
routing string
timeout string
timestamp string
ttl string
version interface{}
opType string
versionType string
refresh string
waitForActiveShards string
pipeline string
ifSeqNo *int64
ifPrimaryTerm *int64
bodyJson interface{}
bodyString string
}
// NewIndexService creates a new IndexService.
func NewIndexService(client *Client) *IndexService {
return &IndexService{
client: client,
typ: "_doc",
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *IndexService) Pretty(pretty bool) *IndexService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *IndexService) Human(human bool) *IndexService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *IndexService) ErrorTrace(errorTrace bool) *IndexService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *IndexService) FilterPath(filterPath ...string) *IndexService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *IndexService) Header(name string, value string) *IndexService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *IndexService) Headers(headers http.Header) *IndexService {
s.headers = headers
return s
}
// Id is the document ID.
func (s *IndexService) Id(id string) *IndexService {
s.id = id
return s
}
// Index is the name of the index.
func (s *IndexService) Index(index string) *IndexService {
s.index = index
return s
}
// Type is the type of the document.
//
// Deprecated: Types are in the process of being removed.
func (s *IndexService) Type(typ string) *IndexService {
s.typ = typ
return s
}
// WaitForActiveShards sets the number of shard copies that must be active
// before proceeding with the index operation. Defaults to 1, meaning the
// primary shard only. Set to `all` for all shard copies, otherwise set to
// any non-negative value less than or equal to the total number of copies
// for the shard (number of replicas + 1).
func (s *IndexService) WaitForActiveShards(waitForActiveShards string) *IndexService {
s.waitForActiveShards = waitForActiveShards
return s
}
// Pipeline specifies the pipeline id to preprocess incoming documents with.
func (s *IndexService) Pipeline(pipeline string) *IndexService {
s.pipeline = pipeline
return s
}
// Refresh the index after performing the operation.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-refresh.html
// for details.
func (s *IndexService) Refresh(refresh string) *IndexService {
s.refresh = refresh
return s
}
// Ttl is an expiration time for the document.
func (s *IndexService) Ttl(ttl string) *IndexService {
s.ttl = ttl
return s
}
// TTL is an expiration time for the document (alias for Ttl).
func (s *IndexService) TTL(ttl string) *IndexService {
s.ttl = ttl
return s
}
// Version is an explicit version number for concurrency control.
func (s *IndexService) Version(version interface{}) *IndexService {
s.version = version
return s
}
// OpType is an explicit operation type, i.e. "create" or "index" (default).
func (s *IndexService) OpType(opType string) *IndexService {
s.opType = opType
return s
}
// Parent is the ID of the parent document.
func (s *IndexService) Parent(parent string) *IndexService {
s.parent = parent
return s
}
// Routing is a specific routing value.
func (s *IndexService) Routing(routing string) *IndexService {
s.routing = routing
return s
}
// Timeout is an explicit operation timeout.
func (s *IndexService) Timeout(timeout string) *IndexService {
s.timeout = timeout
return s
}
// Timestamp is an explicit timestamp for the document.
func (s *IndexService) Timestamp(timestamp string) *IndexService {
s.timestamp = timestamp
return s
}
// VersionType is a specific version type.
func (s *IndexService) VersionType(versionType string) *IndexService {
s.versionType = versionType
return s
}
// IfSeqNo indicates to only perform the index operation if the last
// operation that has changed the document has the specified sequence number.
func (s *IndexService) IfSeqNo(seqNo int64) *IndexService {
s.ifSeqNo = &seqNo
return s
}
// IfPrimaryTerm indicates to only perform the index operation if the
// last operation that has changed the document has the specified primary term.
func (s *IndexService) IfPrimaryTerm(primaryTerm int64) *IndexService {
s.ifPrimaryTerm = &primaryTerm
return s
}
// BodyJson is the document as a serializable JSON interface.
func (s *IndexService) BodyJson(body interface{}) *IndexService {
s.bodyJson = body
return s
}
// BodyString is the document encoded as a string.
func (s *IndexService) BodyString(body string) *IndexService {
s.bodyString = body
return s
}
// buildURL builds the URL for the operation.
func (s *IndexService) buildURL() (string, string, url.Values, error) {
var err error
var method, path string
if s.id != "" {
// Create document with manual id
method = "PUT"
path, err = uritemplates.Expand("/{index}/{type}/{id}", map[string]string{
"id": s.id,
"index": s.index,
"type": s.typ,
})
} else {
// Automatic ID generation
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-index_.html#index-creation
method = "POST"
path, err = uritemplates.Expand("/{index}/{type}/", map[string]string{
"index": s.index,
"type": s.typ,
})
}
if err != nil {
return "", "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.waitForActiveShards != "" {
params.Set("wait_for_active_shards", s.waitForActiveShards)
}
if s.refresh != "" {
params.Set("refresh", s.refresh)
}
if s.opType != "" {
params.Set("op_type", s.opType)
}
if s.parent != "" {
params.Set("parent", s.parent)
}
if s.pipeline != "" {
params.Set("pipeline", s.pipeline)
}
if s.routing != "" {
params.Set("routing", s.routing)
}
if s.timeout != "" {
params.Set("timeout", s.timeout)
}
if s.timestamp != "" {
params.Set("timestamp", s.timestamp)
}
if s.ttl != "" {
params.Set("ttl", s.ttl)
}
if s.version != nil {
params.Set("version", fmt.Sprintf("%v", s.version))
}
if s.versionType != "" {
params.Set("version_type", s.versionType)
}
if v := s.ifSeqNo; v != nil {
params.Set("if_seq_no", fmt.Sprintf("%d", *v))
}
if v := s.ifPrimaryTerm; v != nil {
params.Set("if_primary_term", fmt.Sprintf("%d", *v))
}
return method, path, params, nil
}
// Validate checks if the operation is valid.
func (s *IndexService) Validate() error {
var invalid []string
if s.index == "" {
invalid = append(invalid, "Index")
}
if s.typ == "" {
invalid = append(invalid, "Type")
}
if s.bodyString == "" && s.bodyJson == nil {
invalid = append(invalid, "BodyJson")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *IndexService) Do(ctx context.Context) (*IndexResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
method, path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Setup HTTP request body
var body interface{}
if s.bodyJson != nil {
body = s.bodyJson
} else {
body = s.bodyString
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: method,
Path: path,
Params: params,
Body: body,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(IndexResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// IndexResponse is the result of indexing a document in Elasticsearch.
type IndexResponse struct {
Index string `json:"_index,omitempty"`
Type string `json:"_type,omitempty"`
Id string `json:"_id,omitempty"`
Version int64 `json:"_version,omitempty"`
Result string `json:"result,omitempty"`
Shards *ShardsInfo `json:"_shards,omitempty"`
SeqNo int64 `json:"_seq_no,omitempty"`
PrimaryTerm int64 `json:"_primary_term,omitempty"`
Status int `json:"status,omitempty"`
ForcedRefresh bool `json:"forced_refresh,omitempty"`
}
================================================
FILE: index_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"net/http"
"testing"
)
func TestIndexLifecycle(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
// Add a document
indexResult, err := client.Index().
Index(testIndexName).
Id("1").
BodyJson(&tweet1).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if indexResult == nil {
t.Errorf("expected result to be != nil; got: %v", indexResult)
}
// Exists
exists, err := client.Exists().Index(testIndexName).Id("1").Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if !exists {
t.Errorf("expected exists %v; got %v", true, exists)
}
// Get document
getResult, err := client.Get().
Index(testIndexName).
Id("1").
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if getResult.Index != testIndexName {
t.Errorf("expected GetResult.Index %q; got %q", testIndexName, getResult.Index)
}
if getResult.Type != "_doc" {
t.Errorf("expected GetResult.Type %q; got %q", "_doc", getResult.Type)
}
if getResult.Id != "1" {
t.Errorf("expected GetResult.Id %q; got %q", "1", getResult.Id)
}
if getResult.Source == nil {
t.Errorf("expected GetResult.Source to be != nil; got nil")
}
// Decode the Source field
var tweetGot tweet
err = json.Unmarshal(getResult.Source, &tweetGot)
if err != nil {
t.Fatal(err)
}
if tweetGot.User != tweet1.User {
t.Errorf("expected Tweet.User to be %q; got %q", tweet1.User, tweetGot.User)
}
if tweetGot.Message != tweet1.Message {
t.Errorf("expected Tweet.Message to be %q; got %q", tweet1.Message, tweetGot.Message)
}
// Delete document again
deleteResult, err := client.Delete().Index(testIndexName).Id("1").Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if deleteResult == nil {
t.Errorf("expected result to be != nil; got: %v", deleteResult)
}
// Exists
exists, err = client.Exists().Index(testIndexName).Id("1").Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if exists {
t.Errorf("expected exists %v; got %v", false, exists)
}
}
func TestIndexLifecycleWithAutomaticIDGeneration(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
// Add a document
indexResult, err := client.Index().
Index(testIndexName).
BodyJson(&tweet1).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if indexResult == nil {
t.Errorf("expected result to be != nil; got: %v", indexResult)
}
if indexResult.Id == "" {
t.Fatalf("expected Es to generate an automatic ID, got: %v", indexResult.Id)
}
id := indexResult.Id
// Exists
exists, err := client.Exists().Index(testIndexName).Id(id).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if !exists {
t.Errorf("expected exists %v; got %v", true, exists)
}
// Get document
getResult, err := client.Get().
Index(testIndexName).
Id(id).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if getResult.Index != testIndexName {
t.Errorf("expected GetResult.Index %q; got %q", testIndexName, getResult.Index)
}
if getResult.Type != "_doc" {
t.Errorf("expected GetResult.Type %q; got %q", "_doc", getResult.Type)
}
if getResult.Id != id {
t.Errorf("expected GetResult.Id %q; got %q", id, getResult.Id)
}
if getResult.Source == nil {
t.Errorf("expected GetResult.Source to be != nil; got nil")
}
// Decode the Source field
var tweetGot tweet
err = json.Unmarshal(getResult.Source, &tweetGot)
if err != nil {
t.Fatal(err)
}
if tweetGot.User != tweet1.User {
t.Errorf("expected Tweet.User to be %q; got %q", tweet1.User, tweetGot.User)
}
if tweetGot.Message != tweet1.Message {
t.Errorf("expected Tweet.Message to be %q; got %q", tweet1.Message, tweetGot.Message)
}
// Delete document again
deleteResult, err := client.Delete().Index(testIndexName).Id(id).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if deleteResult == nil {
t.Errorf("expected result to be != nil; got: %v", deleteResult)
}
// Exists
exists, err = client.Exists().Index(testIndexName).Id(id).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if exists {
t.Errorf("expected exists %v; got %v", false, exists)
}
}
func TestIndexValidate(t *testing.T) {
client := setupTestClient(t)
tweet := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
// No index name -> fail with error
res, err := NewIndexService(client).Id("1").BodyJson(&tweet).Do(context.TODO())
if err == nil {
t.Fatalf("expected Index to fail without index name")
}
if res != nil {
t.Fatalf("expected result to be == nil; got: %v", res)
}
}
func TestIndexCreateExistsOpenCloseDelete(t *testing.T) {
// TODO: Find out how to make these test robust
t.Skip("test fails regularly with 409 (Conflict): " +
"IndexPrimaryShardNotAllocatedException[[elastic-test] " +
"primary not allocated post api... skipping")
client := setupTestClient(t)
// Create index
createIndex, err := client.CreateIndex(testIndexName).Body(testMapping).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if createIndex == nil {
t.Fatalf("expected response; got: %v", createIndex)
}
if !createIndex.Acknowledged {
t.Errorf("expected ack for creating index; got: %v", createIndex.Acknowledged)
}
// Exists
indexExists, err := client.IndexExists(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if !indexExists {
t.Fatalf("expected index exists=%v; got %v", true, indexExists)
}
// Refresh
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Close index
closeIndex, err := client.CloseIndex(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if closeIndex == nil {
t.Fatalf("expected response; got: %v", closeIndex)
}
if !closeIndex.Acknowledged {
t.Errorf("expected ack for closing index; got: %v", closeIndex.Acknowledged)
}
// Open index
openIndex, err := client.OpenIndex(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if openIndex == nil {
t.Fatalf("expected response; got: %v", openIndex)
}
if !openIndex.Acknowledged {
t.Errorf("expected ack for opening index; got: %v", openIndex.Acknowledged)
}
// Refresh
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Delete index
deleteIndex, err := client.DeleteIndex(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if deleteIndex == nil {
t.Fatalf("expected response; got: %v", deleteIndex)
}
if !deleteIndex.Acknowledged {
t.Errorf("expected ack for deleting index; got %v", deleteIndex.Acknowledged)
}
}
func TestIndexOptimistic(t *testing.T) {
client := setupTestClientAndCreateIndex(t) //, SetTraceLog(log.New(os.Stdout, "", 0)))
tw := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
// Add a document
doc, err := client.Index().
Index(testIndexName).Id("1").
BodyJson(&tw).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if doc == nil {
t.Errorf("expected result to be != nil; got: %v", doc)
}
tw.Retweets++
// Index with seqNo != doc.SeqNo and primaryTerm != doc.PrimaryTerm
_, err = client.Index().
Index(testIndexName).Id(doc.Id).
IfSeqNo(doc.SeqNo + 1000).
IfPrimaryTerm(doc.PrimaryTerm + 1000).
BodyJson(&tw).
Do(context.Background())
if err == nil {
t.Fatal("expected error, got nil")
}
if !IsConflict(err) {
t.Fatalf("expected conflict error, got %v (%T)", err, err)
}
// Index with seqNo == doc.SeqNo and primaryTerm == doc.PrimaryTerm
res, err := client.Index().
Index(testIndexName).Id(doc.Id).
IfSeqNo(doc.SeqNo).
IfPrimaryTerm(doc.PrimaryTerm).
BodyJson(&tw).
Do(context.Background())
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected response != nil")
}
if want, have := res.SeqNo, doc.SeqNo; want == have {
t.Fatalf("expected SeqNo to change (%d == %d)", want, have)
}
}
func TestIndexOnReadOnlyIndex(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
//client := setupTestClientAndCreateIndexAndLog(t)
// Change index to read-only
{
_, err := client.IndexPutSettings(testIndexName).
BodyString(`{
"index": {
"blocks": {
"read_only_allow_delete": true
}
}
}`).Pretty(true).Do(context.Background())
if err != nil {
t.Fatalf("unable to set index into read-only mode: %v", err)
}
}
// Index something
tweet := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
resp, err := client.Index().
Index(testIndexName).Id("1").
BodyJson(tweet).
Pretty(true).
Do(context.Background())
if err == nil {
t.Fatal("expected an error")
}
elasticErr, ok := err.(*Error)
if !ok {
t.Fatalf("expected an Error type, got %T", err)
}
if want, have := http.StatusTooManyRequests, elasticErr.Status; want != have {
t.Fatalf("expected HTTP status code %d, got %d", want, have)
}
if resp != nil {
t.Fatal("expected response to be nil")
}
}
================================================
FILE: indices_analyze.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// IndicesAnalyzeService performs the analysis process on a text and returns
// the tokens breakdown of the text.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/indices-analyze.html
// for detail.
type IndicesAnalyzeService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
index string
request *IndicesAnalyzeRequest
format string
preferLocal *bool
bodyJson interface{}
bodyString string
}
// NewIndicesAnalyzeService creates a new IndicesAnalyzeService.
func NewIndicesAnalyzeService(client *Client) *IndicesAnalyzeService {
return &IndicesAnalyzeService{
client: client,
request: new(IndicesAnalyzeRequest),
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *IndicesAnalyzeService) Pretty(pretty bool) *IndicesAnalyzeService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *IndicesAnalyzeService) Human(human bool) *IndicesAnalyzeService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *IndicesAnalyzeService) ErrorTrace(errorTrace bool) *IndicesAnalyzeService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *IndicesAnalyzeService) FilterPath(filterPath ...string) *IndicesAnalyzeService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *IndicesAnalyzeService) Header(name string, value string) *IndicesAnalyzeService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *IndicesAnalyzeService) Headers(headers http.Header) *IndicesAnalyzeService {
s.headers = headers
return s
}
// Index is the name of the index to scope the operation.
func (s *IndicesAnalyzeService) Index(index string) *IndicesAnalyzeService {
s.index = index
return s
}
// Format of the output.
func (s *IndicesAnalyzeService) Format(format string) *IndicesAnalyzeService {
s.format = format
return s
}
// PreferLocal, when true, specifies that a local shard should be used
// if available. When false, a random shard is used (default: true).
func (s *IndicesAnalyzeService) PreferLocal(preferLocal bool) *IndicesAnalyzeService {
s.preferLocal = &preferLocal
return s
}
// Request passes the analyze request to use.
func (s *IndicesAnalyzeService) Request(request *IndicesAnalyzeRequest) *IndicesAnalyzeService {
if request == nil {
s.request = new(IndicesAnalyzeRequest)
} else {
s.request = request
}
return s
}
// Analyzer is the name of the analyzer to use.
func (s *IndicesAnalyzeService) Analyzer(analyzer string) *IndicesAnalyzeService {
s.request.Analyzer = analyzer
return s
}
// Attributes is a list of token attributes to output; this parameter works
// only with explain=true.
func (s *IndicesAnalyzeService) Attributes(attributes ...string) *IndicesAnalyzeService {
s.request.Attributes = attributes
return s
}
// CharFilter is a list of character filters to use for the analysis.
func (s *IndicesAnalyzeService) CharFilter(charFilter ...string) *IndicesAnalyzeService {
s.request.CharFilter = charFilter
return s
}
// Explain, when true, outputs more advanced details (default: false).
func (s *IndicesAnalyzeService) Explain(explain bool) *IndicesAnalyzeService {
s.request.Explain = explain
return s
}
// Field specifies to use a specific analyzer configured for this field (instead of passing the analyzer name).
func (s *IndicesAnalyzeService) Field(field string) *IndicesAnalyzeService {
s.request.Field = field
return s
}
// Filter is a list of filters to use for the analysis.
func (s *IndicesAnalyzeService) Filter(filter ...string) *IndicesAnalyzeService {
s.request.Filter = filter
return s
}
// Text is the text on which the analysis should be performed (when request body is not used).
func (s *IndicesAnalyzeService) Text(text ...string) *IndicesAnalyzeService {
s.request.Text = text
return s
}
// Tokenizer is the name of the tokenizer to use for the analysis.
func (s *IndicesAnalyzeService) Tokenizer(tokenizer string) *IndicesAnalyzeService {
s.request.Tokenizer = tokenizer
return s
}
// BodyJson is the text on which the analysis should be performed.
func (s *IndicesAnalyzeService) BodyJson(body interface{}) *IndicesAnalyzeService {
s.bodyJson = body
return s
}
// BodyString is the text on which the analysis should be performed.
func (s *IndicesAnalyzeService) BodyString(body string) *IndicesAnalyzeService {
s.bodyString = body
return s
}
// buildURL builds the URL for the operation.
func (s *IndicesAnalyzeService) buildURL() (string, url.Values, error) {
// Build URL
var err error
var path string
if s.index == "" {
path = "/_analyze"
} else {
path, err = uritemplates.Expand("/{index}/_analyze", map[string]string{
"index": s.index,
})
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.format != "" {
params.Set("format", s.format)
}
if s.preferLocal != nil {
params.Set("prefer_local", fmt.Sprintf("%v", *s.preferLocal))
}
return path, params, nil
}
// Do will execute the request with the given context.
func (s *IndicesAnalyzeService) Do(ctx context.Context) (*IndicesAnalyzeResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Setup HTTP request body
var body interface{}
if s.bodyJson != nil {
body = s.bodyJson
} else if s.bodyString != "" {
body = s.bodyString
} else {
// Request parameters are deprecated in 5.1.1, and we must use a JSON
// structure in the body to pass the parameters.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/indices-analyze.html
body = s.request
}
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "POST",
Path: path,
Params: params,
Body: body,
Headers: s.headers,
})
if err != nil {
return nil, err
}
ret := new(IndicesAnalyzeResponse)
if err = s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
func (s *IndicesAnalyzeService) Validate() error {
var invalid []string
if s.bodyJson == nil && s.bodyString == "" {
if len(s.request.Text) == 0 {
invalid = append(invalid, "Text")
}
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// IndicesAnalyzeRequest specifies the parameters of the analyze request.
type IndicesAnalyzeRequest struct {
Text []string `json:"text,omitempty"`
Analyzer string `json:"analyzer,omitempty"`
Tokenizer string `json:"tokenizer,omitempty"`
Filter []string `json:"filter,omitempty"`
CharFilter []string `json:"char_filter,omitempty"`
Field string `json:"field,omitempty"`
Explain bool `json:"explain,omitempty"`
Attributes []string `json:"attributes,omitempty"`
}
type IndicesAnalyzeResponse struct {
Tokens []AnalyzeToken `json:"tokens"` // json part for normal message
Detail IndicesAnalyzeResponseDetail `json:"detail"` // json part for verbose message of explain request
}
type AnalyzeTokenList struct {
Name string `json:"name"`
Tokens []AnalyzeToken `json:"tokens,omitempty"`
}
type AnalyzeToken struct {
Token string `json:"token"`
Type string `json:"type"` // e.g. ""
StartOffset int `json:"start_offset"`
EndOffset int `json:"end_offset"`
Bytes string `json:"bytes"` // e.g. "[67 75 79]"
Position int `json:"position"`
PositionLength int `json:"positionLength"` // seems to be wrong in 7.2+ (no snake_case), see https://github.com/elastic/elasticsearch/blob/7.2/server/src/main/java/org/elasticsearch/action/admin/indices/analyze/AnalyzeResponse.java
TermFrequency int `json:"termFrequency"`
Keyword bool `json:"keyword"`
}
type CharFilteredText struct {
Name string `json:"name"`
FilteredText []string `json:"filtered_text"`
}
type IndicesAnalyzeResponseDetail struct {
CustomAnalyzer bool `json:"custom_analyzer"`
Analyzer *AnalyzeTokenList `json:"analyzer,omitempty"`
Charfilters []*CharFilteredText `json:"charfilters,omitempty"`
Tokenizer *AnalyzeTokenList `json:"tokenizer,omitempty"`
TokenFilters []*AnalyzeTokenList `json:"tokenfilters,omitempty"`
}
================================================
FILE: indices_analyze_test.go
================================================
package elastic
import (
"context"
"testing"
)
func TestIndicesAnalyzeURL(t *testing.T) {
client := setupTestClient(t)
tests := []struct {
Index string
Expected string
}{
{
"",
"/_analyze",
},
{
"tweets",
"/tweets/_analyze",
},
}
for _, test := range tests {
path, _, err := client.IndexAnalyze().Index(test.Index).buildURL()
if err != nil {
t.Fatal(err)
}
if path != test.Expected {
t.Errorf("expected %q; got: %q", test.Expected, path)
}
}
}
func TestIndicesAnalyze(t *testing.T) {
client := setupTestClient(t)
// client := setupTestClientAndCreateIndexAndLog(t, SetTraceLog(log.New(os.Stdout, "", 0)))
res, err := client.IndexAnalyze().Text("hello hi guy").Do(context.TODO())
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if len(res.Tokens) != 3 {
t.Fatalf("expected %d, got %d (%+v)", 3, len(res.Tokens), res.Tokens)
}
}
func TestIndicesAnalyzeDetail(t *testing.T) {
client := setupTestClient(t)
// client := setupTestClientAndCreateIndexAndLog(t, SetTraceLog(log.New(os.Stdout, "", 0)))
res, err := client.IndexAnalyze().Text("hello hi guy").Explain(true).Pretty(true).Do(context.TODO())
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if len(res.Detail.Analyzer.Tokens) != 3 {
t.Fatalf("expected %d tokens, got %d (%+v)", 3, len(res.Detail.Tokenizer.Tokens), res.Detail.Tokenizer.Tokens)
}
}
func TestIndicesAnalyzeWithIndex(t *testing.T) {
client := setupTestClient(t)
_, err := client.IndexAnalyze().Index("foo").Text("hello hi guy").Do(context.TODO())
if err == nil {
t.Fatal("expected error, got nil")
}
if want, have := "elastic: Error 404 (Not Found): no such index [foo] [type=index_not_found_exception]", err.Error(); want != have {
t.Fatalf("expected error %q, got %q", want, have)
}
}
func TestIndicesAnalyzeValidate(t *testing.T) {
client := setupTestClient(t)
_, err := client.IndexAnalyze().Do(context.TODO())
if err == nil {
t.Fatal("expected error, got nil")
}
if want, have := "missing required fields: [Text]", err.Error(); want != have {
t.Fatalf("expected error %q, got %q", want, have)
}
}
================================================
FILE: indices_clear_cache.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// IndicesClearCacheService allows to clear either all caches or specific cached associated
// with one or more indices.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.6/indices-clearcache.html
// for details.
type IndicesClearCacheService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
index []string
ignoreUnavailable *bool
allowNoIndices *bool
expandWildcards string
fieldData *bool
fields string
query *bool
request *bool
}
// NewIndicesClearCacheService initializes a new instance of
// IndicesClearCacheService.
func NewIndicesClearCacheService(client *Client) *IndicesClearCacheService {
return &IndicesClearCacheService{client: client}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *IndicesClearCacheService) Pretty(pretty bool) *IndicesClearCacheService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *IndicesClearCacheService) Human(human bool) *IndicesClearCacheService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *IndicesClearCacheService) ErrorTrace(errorTrace bool) *IndicesClearCacheService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *IndicesClearCacheService) FilterPath(filterPath ...string) *IndicesClearCacheService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *IndicesClearCacheService) Header(name string, value string) *IndicesClearCacheService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *IndicesClearCacheService) Headers(headers http.Header) *IndicesClearCacheService {
s.headers = headers
return s
}
// Index is the comma-separated list or wildcard expression of index names used to clear cache.
func (s *IndicesClearCacheService) Index(indices ...string) *IndicesClearCacheService {
s.index = append(s.index, indices...)
return s
}
// IgnoreUnavailable indicates whether specified concrete indices should be
// ignored when unavailable (missing or closed).
func (s *IndicesClearCacheService) IgnoreUnavailable(ignoreUnavailable bool) *IndicesClearCacheService {
s.ignoreUnavailable = &ignoreUnavailable
return s
}
// AllowNoIndices indicates whether to ignore if a wildcard indices
// expression resolves into no concrete indices. (This includes `_all` string or when no indices
// have been specified).
func (s *IndicesClearCacheService) AllowNoIndices(allowNoIndices bool) *IndicesClearCacheService {
s.allowNoIndices = &allowNoIndices
return s
}
// ExpandWildcards indicates whether to expand wildcard expression to
// concrete indices that are open, closed or both.
func (s *IndicesClearCacheService) ExpandWildcards(expandWildcards string) *IndicesClearCacheService {
s.expandWildcards = expandWildcards
return s
}
// FieldData indicates whether to clear the fields cache.
// Use the fields parameter to clear the cache of specific fields only.
func (s *IndicesClearCacheService) FieldData(fieldData bool) *IndicesClearCacheService {
s.fieldData = &fieldData
return s
}
// Fields indicates comma-separated list of field names used to limit the fielddata parameter.
// Defaults to all fields.
func (s *IndicesClearCacheService) Fields(fields string) *IndicesClearCacheService {
s.fields = fields
return s
}
// Query indicates whether to clear only query cache.
func (s *IndicesClearCacheService) Query(queryCache bool) *IndicesClearCacheService {
s.query = &queryCache
return s
}
// Request indicates whether to clear only request cache.
func (s *IndicesClearCacheService) Request(requestCache bool) *IndicesClearCacheService {
s.request = &requestCache
return s
}
// buildURL builds the URL for the operation.
func (s *IndicesClearCacheService) buildURL() (string, url.Values, error) {
// Build URL
var path string
var err error
if len(s.index) > 0 {
path, err = uritemplates.Expand("/{index}/_cache/clear", map[string]string{
"index": strings.Join(s.index, ","),
})
} else {
path = "/_cache/clear"
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if v := s.allowNoIndices; v != nil {
params.Set("allow_no_indices", fmt.Sprint(*v))
}
if v := s.expandWildcards; v != "" {
params.Set("expand_wildcards", v)
}
if v := s.ignoreUnavailable; v != nil {
params.Set("ignore_unavailable", fmt.Sprint(*v))
}
if len(s.index) > 0 {
params.Set("index", fmt.Sprintf("%v", s.index))
}
if v := s.ignoreUnavailable; v != nil {
params.Set("fielddata", fmt.Sprint(*v))
}
if len(s.fields) > 0 {
params.Set("fields", fmt.Sprintf("%v", s.fields))
}
if v := s.query; v != nil {
params.Set("query", fmt.Sprint(*v))
}
if s.request != nil {
params.Set("request", fmt.Sprintf("%v", *s.request))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *IndicesClearCacheService) Validate() error {
return nil
}
// Do executes the operation.
func (s *IndicesClearCacheService) Do(ctx context.Context) (*IndicesClearCacheResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "POST",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(IndicesClearCacheResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// IndicesClearCacheResponse is the response of IndicesClearCacheService.Do.
type IndicesClearCacheResponse struct {
Shards *ShardsInfo `json:"_shards"`
}
================================================
FILE: indices_clear_cache_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestIndicesClearCache(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
res, err := client.ClearCache().Do(context.Background())
if err != nil {
t.Fatalf("expected ClearCache to succeed, got: %v", err)
}
if res == nil {
t.Fatalf("expected result to be != nil; got: %v", res)
}
}
func TestIndicesClearCacheBuildURL(t *testing.T) {
client := setupTestClient(t)
tests := []struct {
Indices []string
Expected string
}{
{
[]string{},
"/_cache/clear",
},
{
[]string{"index1"},
"/index1/_cache/clear",
},
{
[]string{"index1", "index2"},
"/index1%2Cindex2/_cache/clear",
},
}
for i, test := range tests {
path, _, err := client.ClearCache().Index(test.Indices...).buildURL()
if err != nil {
t.Errorf("case #%d: %v", i+1, err)
continue
}
if path != test.Expected {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.Expected, path)
}
}
}
================================================
FILE: indices_close.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// IndicesCloseService closes an index.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/indices-open-close.html
// for details.
type IndicesCloseService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
index string
timeout string
masterTimeout string
ignoreUnavailable *bool
allowNoIndices *bool
expandWildcards string
}
// NewIndicesCloseService creates and initializes a new IndicesCloseService.
func NewIndicesCloseService(client *Client) *IndicesCloseService {
return &IndicesCloseService{client: client}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *IndicesCloseService) Pretty(pretty bool) *IndicesCloseService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *IndicesCloseService) Human(human bool) *IndicesCloseService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *IndicesCloseService) ErrorTrace(errorTrace bool) *IndicesCloseService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *IndicesCloseService) FilterPath(filterPath ...string) *IndicesCloseService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *IndicesCloseService) Header(name string, value string) *IndicesCloseService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *IndicesCloseService) Headers(headers http.Header) *IndicesCloseService {
s.headers = headers
return s
}
// Index is the name of the index to close.
func (s *IndicesCloseService) Index(index string) *IndicesCloseService {
s.index = index
return s
}
// Timeout is an explicit operation timeout.
func (s *IndicesCloseService) Timeout(timeout string) *IndicesCloseService {
s.timeout = timeout
return s
}
// MasterTimeout specifies the timeout for connection to master.
func (s *IndicesCloseService) MasterTimeout(masterTimeout string) *IndicesCloseService {
s.masterTimeout = masterTimeout
return s
}
// IgnoreUnavailable indicates whether specified concrete indices should be
// ignored when unavailable (missing or closed).
func (s *IndicesCloseService) IgnoreUnavailable(ignoreUnavailable bool) *IndicesCloseService {
s.ignoreUnavailable = &ignoreUnavailable
return s
}
// AllowNoIndices indicates whether to ignore if a wildcard indices
// expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified).
func (s *IndicesCloseService) AllowNoIndices(allowNoIndices bool) *IndicesCloseService {
s.allowNoIndices = &allowNoIndices
return s
}
// ExpandWildcards indicates whether to expand wildcard expression to
// concrete indices that are open, closed or both.
func (s *IndicesCloseService) ExpandWildcards(expandWildcards string) *IndicesCloseService {
s.expandWildcards = expandWildcards
return s
}
// buildURL builds the URL for the operation.
func (s *IndicesCloseService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/{index}/_close", map[string]string{
"index": s.index,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.allowNoIndices != nil {
params.Set("allow_no_indices", fmt.Sprintf("%v", *s.allowNoIndices))
}
if s.expandWildcards != "" {
params.Set("expand_wildcards", s.expandWildcards)
}
if s.timeout != "" {
params.Set("timeout", s.timeout)
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
if s.ignoreUnavailable != nil {
params.Set("ignore_unavailable", fmt.Sprintf("%v", *s.ignoreUnavailable))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *IndicesCloseService) Validate() error {
var invalid []string
if s.index == "" {
invalid = append(invalid, "Index")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *IndicesCloseService) Do(ctx context.Context) (*IndicesCloseResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "POST",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(IndicesCloseResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// IndicesCloseResponse is the response of IndicesCloseService.Do.
type IndicesCloseResponse struct {
Acknowledged bool `json:"acknowledged"`
ShardsAcknowledged bool `json:"shards_acknowledged"`
Index string `json:"index,omitempty"`
}
================================================
FILE: indices_close_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
// TODO(oe): Find out why this test fails on Travis CI.
/*
func TestIndicesOpenAndClose(t *testing.T) {
client := setupTestClient(t)
// Create index
createIndex, err := client.CreateIndex(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if !createIndex.Acknowledged {
t.Errorf("expected CreateIndexResult.Acknowledged %v; got %v", true, createIndex.Acknowledged)
}
defer func() {
// Delete index
deleteIndex, err := client.DeleteIndex(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if !deleteIndex.Acknowledged {
t.Errorf("expected DeleteIndexResult.Acknowledged %v; got %v", true, deleteIndex.Acknowledged)
}
}()
waitForYellow := func() {
// Wait for status yellow
res, err := client.ClusterHealth().WaitForStatus("yellow").Timeout("15s").Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if res != nil && res.TimedOut {
t.Fatalf("cluster time out waiting for status %q", "yellow")
}
}
// Wait for cluster
waitForYellow()
// Close index
cresp, err := client.CloseIndex(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if !cresp.Acknowledged {
t.Fatalf("expected close index of %q to be acknowledged\n", testIndexName)
}
// Wait for cluster
waitForYellow()
// Open index again
oresp, err := client.OpenIndex(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if !oresp.Acknowledged {
t.Fatalf("expected open index of %q to be acknowledged\n", testIndexName)
}
}
*/
func TestIndicesCloseValidate(t *testing.T) {
client := setupTestClient(t)
// No index name -> fail with error
res, err := NewIndicesCloseService(client).Do(context.TODO())
if err == nil {
t.Fatalf("expected IndicesClose to fail without index name")
}
if res != nil {
t.Fatalf("expected result to be == nil; got: %v", res)
}
}
================================================
FILE: indices_component_templates_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestComponentTemplatesLifecycle(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
const templateName = "template_1"
// Always make sure the component template is deleted
defer func() {
_, _ = client.IndexDeleteComponentTemplate(templateName).Pretty(true).Do(context.Background())
}()
// Create an component template
{
resp, err := client.IndexPutComponentTemplate(templateName).Pretty(true).BodyString(`{
"template": {
"settings": {
"number_of_shards": 2,
"number_of_replicas": 0
},
"mappings": {
"_source": { "enabled": true }
}
}
}`).Do(context.Background())
if err != nil {
t.Fatalf("expected to successfully create component template, got %v", err)
}
if resp == nil {
t.Fatal("expected response on creating component template")
}
if want, have := true, resp.Acknowledged; want != have {
t.Errorf("expected Acknowledged=%v, got %v", want, have)
}
}
// Get the component template
{
resp, err := client.IndexGetComponentTemplate(templateName).Pretty(true).Do(context.Background())
if err != nil {
t.Fatalf("expected to successfully get component template, got %v", err)
}
if resp == nil {
t.Fatal("expected response on getting component template")
}
}
// Delete the component template
{
resp, err := client.IndexDeleteComponentTemplate(templateName).Pretty(true).Do(context.Background())
if err != nil {
t.Fatalf("expected to successfully delete component template, got %v", err)
}
if resp == nil {
t.Fatal("expected response on deleting component template")
}
}
}
================================================
FILE: indices_create.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"errors"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// IndicesCreateService creates a new index.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/indices-create-index.html
// for details.
type IndicesCreateService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
index string
timeout string
masterTimeout string
includeTypeName *bool
bodyJson interface{}
bodyString string
}
// NewIndicesCreateService returns a new IndicesCreateService.
func NewIndicesCreateService(client *Client) *IndicesCreateService {
return &IndicesCreateService{client: client}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *IndicesCreateService) Pretty(pretty bool) *IndicesCreateService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *IndicesCreateService) Human(human bool) *IndicesCreateService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *IndicesCreateService) ErrorTrace(errorTrace bool) *IndicesCreateService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *IndicesCreateService) FilterPath(filterPath ...string) *IndicesCreateService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *IndicesCreateService) Header(name string, value string) *IndicesCreateService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *IndicesCreateService) Headers(headers http.Header) *IndicesCreateService {
s.headers = headers
return s
}
// Index is the name of the index to create.
func (s *IndicesCreateService) Index(index string) *IndicesCreateService {
s.index = index
return s
}
// Timeout the explicit operation timeout, e.g. "5s".
func (s *IndicesCreateService) Timeout(timeout string) *IndicesCreateService {
s.timeout = timeout
return s
}
// MasterTimeout specifies the timeout for connection to master.
func (s *IndicesCreateService) MasterTimeout(masterTimeout string) *IndicesCreateService {
s.masterTimeout = masterTimeout
return s
}
// IncludeTypeName indicates whether a type should be expected in the body of the mappings.
func (s *IndicesCreateService) IncludeTypeName(includeTypeName bool) *IndicesCreateService {
s.includeTypeName = &includeTypeName
return s
}
// Body specifies the configuration of the index as a string.
// It is an alias for BodyString.
func (s *IndicesCreateService) Body(body string) *IndicesCreateService {
s.bodyString = body
return s
}
// BodyString specifies the configuration of the index as a string.
func (s *IndicesCreateService) BodyString(body string) *IndicesCreateService {
s.bodyString = body
return s
}
// BodyJson specifies the configuration of the index. The interface{} will
// be serializes as a JSON document, so use a map[string]interface{}.
func (s *IndicesCreateService) BodyJson(body interface{}) *IndicesCreateService {
s.bodyJson = body
return s
}
// Do executes the operation.
func (s *IndicesCreateService) Do(ctx context.Context) (*IndicesCreateResult, error) {
if s.index == "" {
return nil, errors.New("missing index name")
}
// Build url
path, err := uritemplates.Expand("/{index}", map[string]string{
"index": s.index,
})
if err != nil {
return nil, err
}
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
if s.timeout != "" {
params.Set("timeout", s.timeout)
}
if v := s.includeTypeName; v != nil {
params.Set("include_type_name", fmt.Sprint(*v))
}
// Setup HTTP request body
var body interface{}
if s.bodyJson != nil {
body = s.bodyJson
} else {
body = s.bodyString
}
// Get response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "PUT",
Path: path,
Params: params,
Body: body,
Headers: s.headers,
})
if err != nil {
return nil, err
}
ret := new(IndicesCreateResult)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// -- Result of a create index request.
// IndicesCreateResult is the outcome of creating a new index.
type IndicesCreateResult struct {
Acknowledged bool `json:"acknowledged"`
ShardsAcknowledged bool `json:"shards_acknowledged"`
Index string `json:"index,omitempty"`
}
================================================
FILE: indices_create_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestIndicesLifecycle(t *testing.T) {
client := setupTestClient(t)
// Create index
createIndex, err := client.CreateIndex(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if !createIndex.Acknowledged {
t.Errorf("expected IndicesCreateResult.Acknowledged %v; got %v", true, createIndex.Acknowledged)
}
// Check if index exists
indexExists, err := client.IndexExists(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if !indexExists {
t.Fatalf("index %s should exist, but doesn't\n", testIndexName)
}
// Delete index
deleteIndex, err := client.DeleteIndex(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if !deleteIndex.Acknowledged {
t.Errorf("expected DeleteIndexResult.Acknowledged %v; got %v", true, deleteIndex.Acknowledged)
}
// Check if index exists
indexExists, err = client.IndexExists(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if indexExists {
t.Fatalf("index %s should not exist, but does\n", testIndexName)
}
}
func TestIndicesCreateValidate(t *testing.T) {
client := setupTestClient(t)
// No index name -> fail with error
res, err := NewIndicesCreateService(client).Body(testMapping).Do(context.TODO())
if err == nil {
t.Fatalf("expected IndicesCreate to fail without index name")
}
if res != nil {
t.Fatalf("expected result to be == nil; got: %v", res)
}
}
================================================
FILE: indices_delete.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// IndicesDeleteService allows to delete existing indices.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/indices-delete-index.html
// for details.
type IndicesDeleteService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
index []string
timeout string
masterTimeout string
ignoreUnavailable *bool
allowNoIndices *bool
expandWildcards string
}
// NewIndicesDeleteService creates and initializes a new IndicesDeleteService.
func NewIndicesDeleteService(client *Client) *IndicesDeleteService {
return &IndicesDeleteService{
client: client,
index: make([]string, 0),
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *IndicesDeleteService) Pretty(pretty bool) *IndicesDeleteService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *IndicesDeleteService) Human(human bool) *IndicesDeleteService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *IndicesDeleteService) ErrorTrace(errorTrace bool) *IndicesDeleteService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *IndicesDeleteService) FilterPath(filterPath ...string) *IndicesDeleteService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *IndicesDeleteService) Header(name string, value string) *IndicesDeleteService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *IndicesDeleteService) Headers(headers http.Header) *IndicesDeleteService {
s.headers = headers
return s
}
// Index adds the list of indices to delete.
// Use `_all` or `*` string to delete all indices.
func (s *IndicesDeleteService) Index(index []string) *IndicesDeleteService {
s.index = index
return s
}
// Timeout is an explicit operation timeout.
func (s *IndicesDeleteService) Timeout(timeout string) *IndicesDeleteService {
s.timeout = timeout
return s
}
// MasterTimeout specifies the timeout for connection to master.
func (s *IndicesDeleteService) MasterTimeout(masterTimeout string) *IndicesDeleteService {
s.masterTimeout = masterTimeout
return s
}
// IgnoreUnavailable indicates whether to ignore unavailable indexes (default: false).
func (s *IndicesDeleteService) IgnoreUnavailable(ignoreUnavailable bool) *IndicesDeleteService {
s.ignoreUnavailable = &ignoreUnavailable
return s
}
// AllowNoIndices indicates whether to ignore if a wildcard expression
// resolves to no concrete indices (default: false).
func (s *IndicesDeleteService) AllowNoIndices(allowNoIndices bool) *IndicesDeleteService {
s.allowNoIndices = &allowNoIndices
return s
}
// ExpandWildcards indicates whether wildcard expressions should get
// expanded to open or closed indices (default: open).
func (s *IndicesDeleteService) ExpandWildcards(expandWildcards string) *IndicesDeleteService {
s.expandWildcards = expandWildcards
return s
}
// buildURL builds the URL for the operation.
func (s *IndicesDeleteService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/{index}", map[string]string{
"index": strings.Join(s.index, ","),
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.timeout != "" {
params.Set("timeout", s.timeout)
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
if s.ignoreUnavailable != nil {
params.Set("ignore_unavailable", fmt.Sprintf("%v", *s.ignoreUnavailable))
}
if s.allowNoIndices != nil {
params.Set("allow_no_indices", fmt.Sprintf("%v", *s.allowNoIndices))
}
if s.expandWildcards != "" {
params.Set("expand_wildcards", s.expandWildcards)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *IndicesDeleteService) Validate() error {
var invalid []string
if len(s.index) == 0 {
invalid = append(invalid, "Index")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *IndicesDeleteService) Do(ctx context.Context) (*IndicesDeleteResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "DELETE",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(IndicesDeleteResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// -- Result of a delete index request.
// IndicesDeleteResponse is the response of IndicesDeleteService.Do.
type IndicesDeleteResponse struct {
Acknowledged bool `json:"acknowledged"`
}
================================================
FILE: indices_delete_component_template.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// IndicesDeleteComponentTemplateService deletes component templates.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.10/indices-delete-component-template.html
// for more details.
type IndicesDeleteComponentTemplateService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
name string
timeout string
masterTimeout string
}
// NewIndicesDeleteComponentTemplateService creates a new IndicesDeleteComponentTemplateService.
func NewIndicesDeleteComponentTemplateService(client *Client) *IndicesDeleteComponentTemplateService {
return &IndicesDeleteComponentTemplateService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *IndicesDeleteComponentTemplateService) Pretty(pretty bool) *IndicesDeleteComponentTemplateService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *IndicesDeleteComponentTemplateService) Human(human bool) *IndicesDeleteComponentTemplateService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *IndicesDeleteComponentTemplateService) ErrorTrace(errorTrace bool) *IndicesDeleteComponentTemplateService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *IndicesDeleteComponentTemplateService) FilterPath(filterPath ...string) *IndicesDeleteComponentTemplateService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *IndicesDeleteComponentTemplateService) Header(name string, value string) *IndicesDeleteComponentTemplateService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *IndicesDeleteComponentTemplateService) Headers(headers http.Header) *IndicesDeleteComponentTemplateService {
s.headers = headers
return s
}
// Name is the name of the template.
func (s *IndicesDeleteComponentTemplateService) Name(name string) *IndicesDeleteComponentTemplateService {
s.name = name
return s
}
// Timeout is an explicit operation timeout.
func (s *IndicesDeleteComponentTemplateService) Timeout(timeout string) *IndicesDeleteComponentTemplateService {
s.timeout = timeout
return s
}
// MasterTimeout specifies the timeout for connection to master.
func (s *IndicesDeleteComponentTemplateService) MasterTimeout(masterTimeout string) *IndicesDeleteComponentTemplateService {
s.masterTimeout = masterTimeout
return s
}
// buildURL builds the URL for the operation.
func (s *IndicesDeleteComponentTemplateService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/_component_template/{name}", map[string]string{
"name": s.name,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.timeout != "" {
params.Set("timeout", s.timeout)
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *IndicesDeleteComponentTemplateService) Validate() error {
var invalid []string
if s.name == "" {
invalid = append(invalid, "Name")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *IndicesDeleteComponentTemplateService) Do(ctx context.Context) (*IndicesDeleteComponentTemplateResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "DELETE",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(IndicesDeleteComponentTemplateResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// IndicesDeleteComponentTemplateResponse is the response of IndicesDeleteComponentTemplateService.Do.
type IndicesDeleteComponentTemplateResponse struct {
Acknowledged bool `json:"acknowledged"`
ShardsAcknowledged bool `json:"shards_acknowledged"`
Index string `json:"index,omitempty"`
}
================================================
FILE: indices_delete_index_template.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// IndicesDeleteIndexTemplateService deletes index templates.
//
// Index templates have changed during in 7.8 update of Elasticsearch.
// This service implements the new version (7.8 or later). If you want
// the old version, please use the IndicesDeleteTemplateService.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.9/indices-delete-template.html
// for more details.
type IndicesDeleteIndexTemplateService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
name string
timeout string
masterTimeout string
}
// NewIndicesDeleteIndexTemplateService creates a new IndicesDeleteIndexTemplateService.
func NewIndicesDeleteIndexTemplateService(client *Client) *IndicesDeleteIndexTemplateService {
return &IndicesDeleteIndexTemplateService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *IndicesDeleteIndexTemplateService) Pretty(pretty bool) *IndicesDeleteIndexTemplateService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *IndicesDeleteIndexTemplateService) Human(human bool) *IndicesDeleteIndexTemplateService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *IndicesDeleteIndexTemplateService) ErrorTrace(errorTrace bool) *IndicesDeleteIndexTemplateService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *IndicesDeleteIndexTemplateService) FilterPath(filterPath ...string) *IndicesDeleteIndexTemplateService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *IndicesDeleteIndexTemplateService) Header(name string, value string) *IndicesDeleteIndexTemplateService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *IndicesDeleteIndexTemplateService) Headers(headers http.Header) *IndicesDeleteIndexTemplateService {
s.headers = headers
return s
}
// Name is the name of the template.
func (s *IndicesDeleteIndexTemplateService) Name(name string) *IndicesDeleteIndexTemplateService {
s.name = name
return s
}
// Timeout is an explicit operation timeout.
func (s *IndicesDeleteIndexTemplateService) Timeout(timeout string) *IndicesDeleteIndexTemplateService {
s.timeout = timeout
return s
}
// MasterTimeout specifies the timeout for connection to master.
func (s *IndicesDeleteIndexTemplateService) MasterTimeout(masterTimeout string) *IndicesDeleteIndexTemplateService {
s.masterTimeout = masterTimeout
return s
}
// buildURL builds the URL for the operation.
func (s *IndicesDeleteIndexTemplateService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/_index_template/{name}", map[string]string{
"name": s.name,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.timeout != "" {
params.Set("timeout", s.timeout)
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *IndicesDeleteIndexTemplateService) Validate() error {
var invalid []string
if s.name == "" {
invalid = append(invalid, "Name")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *IndicesDeleteIndexTemplateService) Do(ctx context.Context) (*IndicesDeleteIndexTemplateResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "DELETE",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(IndicesDeleteIndexTemplateResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// IndicesDeleteIndexTemplateResponse is the response of IndicesDeleteIndexTemplateService.Do.
type IndicesDeleteIndexTemplateResponse struct {
Acknowledged bool `json:"acknowledged"`
ShardsAcknowledged bool `json:"shards_acknowledged"`
Index string `json:"index,omitempty"`
}
================================================
FILE: indices_delete_integration_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestIndicesDeleteIntegration(t *testing.T) {
client := setupTestClientAndCreateIndex(t) //, SetTraceLog(log.New(os.Stdout, "", 0)))
before, err := client.IndexNames()
if err != nil {
t.Fatal(err)
}
_, err = client.DeleteIndex(testIndexName, testIndexNameEmpty).Do(context.Background())
if err != nil {
t.Fatal(err)
}
after, err := client.IndexNames()
if err != nil {
t.Fatal(err)
}
if want, have := len(after), len(before)-2; want != have {
t.Fatalf("expected %d indices, got %d", want, have)
}
}
================================================
FILE: indices_delete_template.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// IndicesDeleteTemplateService deletes templates.
//
// Index templates have changed during in 7.8 update of Elasticsearch.
// This service implements the legacy version (7.7 or lower). If you want
// the new version, please use the IndicesDeleteIndexTemplateService.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.9/indices-delete-template-v1.html
// for more details.
//
// Deprecated: Legacy index templates are deprecated in favor of composable templates.
type IndicesDeleteTemplateService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
name string
timeout string
masterTimeout string
}
// NewIndicesDeleteTemplateService creates a new IndicesDeleteTemplateService.
func NewIndicesDeleteTemplateService(client *Client) *IndicesDeleteTemplateService {
return &IndicesDeleteTemplateService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *IndicesDeleteTemplateService) Pretty(pretty bool) *IndicesDeleteTemplateService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *IndicesDeleteTemplateService) Human(human bool) *IndicesDeleteTemplateService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *IndicesDeleteTemplateService) ErrorTrace(errorTrace bool) *IndicesDeleteTemplateService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *IndicesDeleteTemplateService) FilterPath(filterPath ...string) *IndicesDeleteTemplateService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *IndicesDeleteTemplateService) Header(name string, value string) *IndicesDeleteTemplateService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *IndicesDeleteTemplateService) Headers(headers http.Header) *IndicesDeleteTemplateService {
s.headers = headers
return s
}
// Name is the name of the template.
func (s *IndicesDeleteTemplateService) Name(name string) *IndicesDeleteTemplateService {
s.name = name
return s
}
// Timeout is an explicit operation timeout.
func (s *IndicesDeleteTemplateService) Timeout(timeout string) *IndicesDeleteTemplateService {
s.timeout = timeout
return s
}
// MasterTimeout specifies the timeout for connection to master.
func (s *IndicesDeleteTemplateService) MasterTimeout(masterTimeout string) *IndicesDeleteTemplateService {
s.masterTimeout = masterTimeout
return s
}
// buildURL builds the URL for the operation.
func (s *IndicesDeleteTemplateService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/_template/{name}", map[string]string{
"name": s.name,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.timeout != "" {
params.Set("timeout", s.timeout)
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *IndicesDeleteTemplateService) Validate() error {
var invalid []string
if s.name == "" {
invalid = append(invalid, "Name")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
//
// Deprecated: Legacy index templates are deprecated in favor of composable templates.
func (s *IndicesDeleteTemplateService) Do(ctx context.Context) (*IndicesDeleteTemplateResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "DELETE",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(IndicesDeleteTemplateResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// IndicesDeleteTemplateResponse is the response of IndicesDeleteTemplateService.Do.
type IndicesDeleteTemplateResponse struct {
Acknowledged bool `json:"acknowledged"`
ShardsAcknowledged bool `json:"shards_acknowledged"`
Index string `json:"index,omitempty"`
}
================================================
FILE: indices_delete_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestIndicesDeleteValidate(t *testing.T) {
client := setupTestClient(t)
// No index name -> fail with error
res, err := NewIndicesDeleteService(client).Do(context.TODO())
if err == nil {
t.Fatalf("expected IndicesDelete to fail without index name")
}
if res != nil {
t.Fatalf("expected result to be == nil; got: %v", res)
}
}
================================================
FILE: indices_exists.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// IndicesExistsService checks if an index or indices exist or not.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/indices-exists.html
// for details.
type IndicesExistsService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
index []string
ignoreUnavailable *bool
allowNoIndices *bool
expandWildcards string
local *bool
}
// NewIndicesExistsService creates and initializes a new IndicesExistsService.
func NewIndicesExistsService(client *Client) *IndicesExistsService {
return &IndicesExistsService{
client: client,
index: make([]string, 0),
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *IndicesExistsService) Pretty(pretty bool) *IndicesExistsService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *IndicesExistsService) Human(human bool) *IndicesExistsService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *IndicesExistsService) ErrorTrace(errorTrace bool) *IndicesExistsService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *IndicesExistsService) FilterPath(filterPath ...string) *IndicesExistsService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *IndicesExistsService) Header(name string, value string) *IndicesExistsService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *IndicesExistsService) Headers(headers http.Header) *IndicesExistsService {
s.headers = headers
return s
}
// Index is a list of one or more indices to check.
func (s *IndicesExistsService) Index(index []string) *IndicesExistsService {
s.index = index
return s
}
// AllowNoIndices indicates whether to ignore if a wildcard indices expression
// resolves into no concrete indices. (This includes `_all` string or
// when no indices have been specified).
func (s *IndicesExistsService) AllowNoIndices(allowNoIndices bool) *IndicesExistsService {
s.allowNoIndices = &allowNoIndices
return s
}
// ExpandWildcards indicates whether to expand wildcard expression to
// concrete indices that are open, closed or both.
func (s *IndicesExistsService) ExpandWildcards(expandWildcards string) *IndicesExistsService {
s.expandWildcards = expandWildcards
return s
}
// Local, when set, returns local information and does not retrieve the state
// from master node (default: false).
func (s *IndicesExistsService) Local(local bool) *IndicesExistsService {
s.local = &local
return s
}
// IgnoreUnavailable indicates whether specified concrete indices should be
// ignored when unavailable (missing or closed).
func (s *IndicesExistsService) IgnoreUnavailable(ignoreUnavailable bool) *IndicesExistsService {
s.ignoreUnavailable = &ignoreUnavailable
return s
}
// buildURL builds the URL for the operation.
func (s *IndicesExistsService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/{index}", map[string]string{
"index": strings.Join(s.index, ","),
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.local != nil {
params.Set("local", fmt.Sprintf("%v", *s.local))
}
if s.ignoreUnavailable != nil {
params.Set("ignore_unavailable", fmt.Sprintf("%v", *s.ignoreUnavailable))
}
if s.allowNoIndices != nil {
params.Set("allow_no_indices", fmt.Sprintf("%v", *s.allowNoIndices))
}
if s.expandWildcards != "" {
params.Set("expand_wildcards", s.expandWildcards)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *IndicesExistsService) Validate() error {
var invalid []string
if len(s.index) == 0 {
invalid = append(invalid, "Index")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *IndicesExistsService) Do(ctx context.Context) (bool, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return false, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return false, err
}
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "HEAD",
Path: path,
Params: params,
IgnoreErrors: []int{404},
Headers: s.headers,
})
if err != nil {
return false, err
}
// Return operation response
switch res.StatusCode {
case http.StatusOK:
return true, nil
case http.StatusNotFound:
return false, nil
default:
return false, fmt.Errorf("elastic: got HTTP code %d when it should have been either 200 or 404", res.StatusCode)
}
}
================================================
FILE: indices_exists_template.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// IndicesExistsTemplateService checks if a given template exists.
// See http://www.elastic.co/guide/en/elasticsearch/reference/7.0/indices-templates.html#indices-templates-exists
// for documentation.
//
// Deprecated: Legacy index templates are deprecated in favor of composable templates.
type IndicesExistsTemplateService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
name string
local *bool
masterTimeout string
}
// NewIndicesExistsTemplateService creates a new IndicesExistsTemplateService.
func NewIndicesExistsTemplateService(client *Client) *IndicesExistsTemplateService {
return &IndicesExistsTemplateService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *IndicesExistsTemplateService) Pretty(pretty bool) *IndicesExistsTemplateService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *IndicesExistsTemplateService) Human(human bool) *IndicesExistsTemplateService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *IndicesExistsTemplateService) ErrorTrace(errorTrace bool) *IndicesExistsTemplateService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *IndicesExistsTemplateService) FilterPath(filterPath ...string) *IndicesExistsTemplateService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *IndicesExistsTemplateService) Header(name string, value string) *IndicesExistsTemplateService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *IndicesExistsTemplateService) Headers(headers http.Header) *IndicesExistsTemplateService {
s.headers = headers
return s
}
// Name is the name of the template.
func (s *IndicesExistsTemplateService) Name(name string) *IndicesExistsTemplateService {
s.name = name
return s
}
// Local indicates whether to return local information, i.e. do not retrieve
// the state from master node (default: false).
func (s *IndicesExistsTemplateService) Local(local bool) *IndicesExistsTemplateService {
s.local = &local
return s
}
// MasterTimeout specifies the timeout for connection to master.
func (s *IndicesExistsTemplateService) MasterTimeout(masterTimeout string) *IndicesExistsTemplateService {
s.masterTimeout = masterTimeout
return s
}
// buildURL builds the URL for the operation.
func (s *IndicesExistsTemplateService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/_template/{name}", map[string]string{
"name": s.name,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.local != nil {
params.Set("local", fmt.Sprint(*s.local))
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *IndicesExistsTemplateService) Validate() error {
var invalid []string
if s.name == "" {
invalid = append(invalid, "Name")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
//
// Deprecated: Legacy index templates are deprecated in favor of composable templates.
func (s *IndicesExistsTemplateService) Do(ctx context.Context) (bool, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return false, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return false, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "HEAD",
Path: path,
Params: params,
IgnoreErrors: []int{404},
Headers: s.headers,
})
if err != nil {
return false, err
}
// Return operation response
switch res.StatusCode {
case http.StatusOK:
return true, nil
case http.StatusNotFound:
return false, nil
default:
return false, fmt.Errorf("elastic: got HTTP code %d when it should have been either 200 or 404", res.StatusCode)
}
}
================================================
FILE: indices_exists_template_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestIndexExistsTemplate(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tmpl := `{
"index_patterns":["elastic-test*"],
"settings":{
"number_of_shards":1,
"number_of_replicas":0
},
"mappings":{
"properties":{
"tags":{
"type":"keyword"
},
"location":{
"type":"geo_point"
},
"suggest_field":{
"type":"completion"
}
}
}
}`
putres, err := client.IndexPutTemplate("elastic-template").BodyString(tmpl).Do(context.TODO())
if err != nil {
t.Fatalf("expected no error; got: %v", err)
}
if putres == nil {
t.Fatalf("expected response; got: %v", putres)
}
if !putres.Acknowledged {
t.Fatalf("expected index template to be ack'd; got: %v", putres.Acknowledged)
}
// Always delete template
defer client.IndexDeleteTemplate("elastic-template").Do(context.TODO())
// Check if template exists
exists, err := client.IndexTemplateExists("elastic-template").Do(context.TODO())
if err != nil {
t.Fatalf("expected no error; got: %v", err)
}
if !exists {
t.Fatalf("expected index template %q to exist; got: %v", "elastic-template", exists)
}
// Get template
getres, err := client.IndexGetTemplate("elastic-template").Do(context.TODO())
if err != nil {
t.Fatalf("expected no error; got: %v", err)
}
if getres == nil {
t.Fatalf("expected to get index template %q; got: %v", "elastic-template", getres)
}
}
================================================
FILE: indices_exists_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestIndicesExistsWithoutIndex(t *testing.T) {
client := setupTestClient(t)
// No index name -> fail with error
res, err := NewIndicesExistsService(client).Do(context.TODO())
if err == nil {
t.Fatalf("expected IndicesExists to fail without index name")
}
if res != false {
t.Fatalf("expected result to be false; got: %v", res)
}
}
================================================
FILE: indices_flush.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// Flush allows to flush one or more indices. The flush process of an index
// basically frees memory from the index by flushing data to the index
// storage and clearing the internal transaction log.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/indices-flush.html
// for details.
type IndicesFlushService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
index []string
force *bool
waitIfOngoing *bool
ignoreUnavailable *bool
allowNoIndices *bool
expandWildcards string
}
// NewIndicesFlushService creates a new IndicesFlushService.
func NewIndicesFlushService(client *Client) *IndicesFlushService {
return &IndicesFlushService{
client: client,
index: make([]string, 0),
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *IndicesFlushService) Pretty(pretty bool) *IndicesFlushService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *IndicesFlushService) Human(human bool) *IndicesFlushService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *IndicesFlushService) ErrorTrace(errorTrace bool) *IndicesFlushService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *IndicesFlushService) FilterPath(filterPath ...string) *IndicesFlushService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *IndicesFlushService) Header(name string, value string) *IndicesFlushService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *IndicesFlushService) Headers(headers http.Header) *IndicesFlushService {
s.headers = headers
return s
}
// Index is a list of index names; use `_all` or empty string for all indices.
func (s *IndicesFlushService) Index(indices ...string) *IndicesFlushService {
s.index = append(s.index, indices...)
return s
}
// Force indicates whether a flush should be forced even if it is not
// necessarily needed ie. if no changes will be committed to the index.
// This is useful if transaction log IDs should be incremented even if
// no uncommitted changes are present. (This setting can be considered as internal).
func (s *IndicesFlushService) Force(force bool) *IndicesFlushService {
s.force = &force
return s
}
// WaitIfOngoing, if set to true, indicates that the flush operation will
// block until the flush can be executed if another flush operation is
// already executing. The default is false and will cause an exception
// to be thrown on the shard level if another flush operation is already running..
func (s *IndicesFlushService) WaitIfOngoing(waitIfOngoing bool) *IndicesFlushService {
s.waitIfOngoing = &waitIfOngoing
return s
}
// IgnoreUnavailable indicates whether specified concrete indices should be
// ignored when unavailable (missing or closed).
func (s *IndicesFlushService) IgnoreUnavailable(ignoreUnavailable bool) *IndicesFlushService {
s.ignoreUnavailable = &ignoreUnavailable
return s
}
// AllowNoIndices indicates whether to ignore if a wildcard indices expression
// resolves into no concrete indices. (This includes `_all` string or when
// no indices have been specified).
func (s *IndicesFlushService) AllowNoIndices(allowNoIndices bool) *IndicesFlushService {
s.allowNoIndices = &allowNoIndices
return s
}
// ExpandWildcards specifies whether to expand wildcard expression to
// concrete indices that are open, closed or both..
func (s *IndicesFlushService) ExpandWildcards(expandWildcards string) *IndicesFlushService {
s.expandWildcards = expandWildcards
return s
}
// buildURL builds the URL for the operation.
func (s *IndicesFlushService) buildURL() (string, url.Values, error) {
// Build URL
var err error
var path string
if len(s.index) > 0 {
path, err = uritemplates.Expand("/{index}/_flush", map[string]string{
"index": strings.Join(s.index, ","),
})
} else {
path = "/_flush"
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.force != nil {
params.Set("force", fmt.Sprintf("%v", *s.force))
}
if s.waitIfOngoing != nil {
params.Set("wait_if_ongoing", fmt.Sprintf("%v", *s.waitIfOngoing))
}
if s.ignoreUnavailable != nil {
params.Set("ignore_unavailable", fmt.Sprintf("%v", *s.ignoreUnavailable))
}
if s.allowNoIndices != nil {
params.Set("allow_no_indices", fmt.Sprintf("%v", *s.allowNoIndices))
}
if s.expandWildcards != "" {
params.Set("expand_wildcards", s.expandWildcards)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *IndicesFlushService) Validate() error {
return nil
}
// Do executes the service.
func (s *IndicesFlushService) Do(ctx context.Context) (*IndicesFlushResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "POST",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(IndicesFlushResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// -- Result of a flush request.
type IndicesFlushResponse struct {
Shards *ShardsInfo `json:"_shards"`
}
================================================
FILE: indices_flush_synced.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// IndicesSyncedFlushService performs a normal flush, then adds a generated
// unique marked (sync_id) to all shards.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/indices-synced-flush.html
// for details.
type IndicesSyncedFlushService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
index []string
ignoreUnavailable *bool
allowNoIndices *bool
expandWildcards string
}
// NewIndicesSyncedFlushService creates a new IndicesSyncedFlushService.
func NewIndicesSyncedFlushService(client *Client) *IndicesSyncedFlushService {
return &IndicesSyncedFlushService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *IndicesSyncedFlushService) Pretty(pretty bool) *IndicesSyncedFlushService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *IndicesSyncedFlushService) Human(human bool) *IndicesSyncedFlushService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *IndicesSyncedFlushService) ErrorTrace(errorTrace bool) *IndicesSyncedFlushService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *IndicesSyncedFlushService) FilterPath(filterPath ...string) *IndicesSyncedFlushService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *IndicesSyncedFlushService) Header(name string, value string) *IndicesSyncedFlushService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *IndicesSyncedFlushService) Headers(headers http.Header) *IndicesSyncedFlushService {
s.headers = headers
return s
}
// Index is a list of index names; use `_all` or empty string for all indices.
func (s *IndicesSyncedFlushService) Index(indices ...string) *IndicesSyncedFlushService {
s.index = append(s.index, indices...)
return s
}
// IgnoreUnavailable indicates whether specified concrete indices should be
// ignored when unavailable (missing or closed).
func (s *IndicesSyncedFlushService) IgnoreUnavailable(ignoreUnavailable bool) *IndicesSyncedFlushService {
s.ignoreUnavailable = &ignoreUnavailable
return s
}
// AllowNoIndices indicates whether to ignore if a wildcard indices expression
// resolves into no concrete indices. (This includes `_all` string or when
// no indices have been specified).
func (s *IndicesSyncedFlushService) AllowNoIndices(allowNoIndices bool) *IndicesSyncedFlushService {
s.allowNoIndices = &allowNoIndices
return s
}
// ExpandWildcards specifies whether to expand wildcard expression to
// concrete indices that are open, closed or both..
func (s *IndicesSyncedFlushService) ExpandWildcards(expandWildcards string) *IndicesSyncedFlushService {
s.expandWildcards = expandWildcards
return s
}
// buildURL builds the URL for the operation.
func (s *IndicesSyncedFlushService) buildURL() (string, url.Values, error) {
// Build URL
var err error
var path string
if len(s.index) > 0 {
path, err = uritemplates.Expand("/{index}/_flush/synced", map[string]string{
"index": strings.Join(s.index, ","),
})
} else {
path = "/_flush/synced"
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.ignoreUnavailable != nil {
params.Set("ignore_unavailable", fmt.Sprintf("%v", *s.ignoreUnavailable))
}
if s.allowNoIndices != nil {
params.Set("allow_no_indices", fmt.Sprintf("%v", *s.allowNoIndices))
}
if s.expandWildcards != "" {
params.Set("expand_wildcards", s.expandWildcards)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *IndicesSyncedFlushService) Validate() error {
return nil
}
// Do executes the service.
//
// Deprecated: Synced flush is deprecated and will be removed in 8.0.
// Use flush at _/flush or /{index}/_flush instead.
func (s *IndicesSyncedFlushService) Do(ctx context.Context) (*IndicesSyncedFlushResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "POST",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(IndicesSyncedFlushResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// -- Result of a flush request.
// IndicesSyncedFlushResponse is the outcome of a synched flush call.
type IndicesSyncedFlushResponse struct {
Shards *ShardsInfo `json:"_shards"`
Index map[string]*IndicesShardsSyncedFlushResult `json:"-"`
// TODO Add information about the indices here from the root level
// It looks like this:
// {
// "_shards" : {
// "total" : 4,
// "successful" : 4,
// "failed" : 0
// },
// "elastic-test" : {
// "total" : 1,
// "successful" : 1,
// "failed" : 0
// },
// "elastic-test2" : {
// "total" : 1,
// "successful" : 1,
// "failed" : 0
// },
// "elastic-orders" : {
// "total" : 1,
// "successful" : 1,
// "failed" : 0
// },
// "elastic-nosource-test" : {
// "total" : 1,
// "successful" : 1,
// "failed" : 0
// }
// }
}
// IndicesShardsSyncedFlushResult represents synced flush information about
// a specific index.
type IndicesShardsSyncedFlushResult struct {
Total int `json:"total"`
Successful int `json:"successful"`
Failed int `json:"failed"`
Failures []IndicesShardsSyncedFlushResultFailure `json:"failures,omitempty"`
}
// IndicesShardsSyncedFlushResultFailure represents a failure of a synced
// flush operation.
type IndicesShardsSyncedFlushResultFailure struct {
Shard int `json:"shard"`
Reason string `json:"reason"`
Routing struct {
State string `json:"state"`
Primary bool `json:"primary"`
Node string `json:"node"`
RelocatingNode *string `json:"relocating_node"`
Shard int `json:"shard"`
Index string `json:"index"`
ExpectedShardSizeInBytes int64 `json:"expected_shard_size_in_bytes,omitempty"`
// recoverySource
// allocationId
// unassignedInfo
} `json:"routing"`
}
// UnmarshalJSON parses the output from Synced Flush API.
func (resp *IndicesSyncedFlushResponse) UnmarshalJSON(data []byte) error {
m := make(map[string]json.RawMessage)
err := json.Unmarshal(data, &m)
if err != nil {
return err
}
resp.Index = make(map[string]*IndicesShardsSyncedFlushResult)
for k, v := range m {
if k == "_shards" {
if err := json.Unmarshal(v, &resp.Shards); err != nil {
return err
}
} else {
ix := new(IndicesShardsSyncedFlushResult)
if err := json.Unmarshal(v, &ix); err != nil {
return err
}
resp.Index[k] = ix
}
}
return nil
}
================================================
FILE: indices_flush_synced_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"testing"
)
func TestSyncedFlush(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
//client := setupTestClientAndCreateIndexAndLog(t)
// Sync Flush all indices
res, err := client.SyncedFlush().Pretty(true).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Errorf("expected res to be != nil; got: %v", res)
}
}
func TestSyncedFlushBuildURL(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tests := []struct {
Indices []string
Expected string
ExpectValidateFailure bool
}{
{
[]string{},
"/_flush/synced",
false,
},
{
[]string{"index1"},
"/index1/_flush/synced",
false,
},
{
[]string{"index1", "index2"},
"/index1%2Cindex2/_flush/synced",
false,
},
}
for i, test := range tests {
err := NewIndicesSyncedFlushService(client).Index(test.Indices...).Validate()
if err == nil && test.ExpectValidateFailure {
t.Errorf("case #%d: expected validate to fail", i+1)
continue
}
if err != nil && !test.ExpectValidateFailure {
t.Errorf("case #%d: expected validate to succeed", i+1)
continue
}
if !test.ExpectValidateFailure {
path, _, err := NewIndicesSyncedFlushService(client).Index(test.Indices...).buildURL()
if err != nil {
t.Fatalf("case #%d: %v", i+1, err)
}
if path != test.Expected {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.Expected, path)
}
}
}
}
func TestSyncedFlushResponse(t *testing.T) {
js := `{
"_shards": {
"total": 4,
"successful": 1,
"failed": 1
},
"twitter": {
"total": 4,
"successful": 3,
"failed": 1,
"failures": [
{
"shard": 1,
"reason": "unexpected error",
"routing": {
"state": "STARTED",
"primary": false,
"node": "SZNr2J_ORxKTLUCydGX4zA",
"relocating_node": null,
"shard": 1,
"index": "twitter"
}
}
]
}
}`
var resp IndicesSyncedFlushResponse
if err := json.Unmarshal([]byte(js), &resp); err != nil {
t.Fatal(err)
}
if want, have := 4, resp.Shards.Total; want != have {
t.Fatalf("want Shards.Total = %v, have %v", want, have)
}
if want, have := 1, resp.Shards.Successful; want != have {
t.Fatalf("want Shards.Successful = %v, have %v", want, have)
}
if want, have := 1, resp.Shards.Failed; want != have {
t.Fatalf("want Shards.Failed = %v, have %v", want, have)
}
{
indexName := "twitter"
index, found := resp.Index[indexName]
if !found {
t.Fatalf("want index %q", indexName)
}
if index == nil {
t.Fatalf("want index %q", indexName)
}
if want, have := 4, index.Total; want != have {
t.Fatalf("want Index[%q].Total = %v, have %v", indexName, want, have)
}
if want, have := 3, index.Successful; want != have {
t.Fatalf("want Index[%q].Successful = %v, have %v", indexName, want, have)
}
if want, have := 1, index.Failed; want != have {
t.Fatalf("want Index[%q].Failed = %v, have %v", indexName, want, have)
}
if want, have := 1, len(index.Failures); want != have {
t.Fatalf("want len(Index[%q].Failures) = %v, have %v", indexName, want, have)
}
failure := index.Failures[0]
if want, have := 1, failure.Shard; want != have {
t.Fatalf("want Index[%q].Failures[0].Shard = %v, have %v", indexName, want, have)
}
if want, have := "unexpected error", failure.Reason; want != have {
t.Fatalf("want Index[%q].Failures[0].Reason = %q, have %q", indexName, want, have)
}
if want, have := false, failure.Routing.Primary; want != have {
t.Fatalf("want Index[%q].Failures[0].Routing.Primary = %v, have %v", indexName, want, have)
}
if want, have := "SZNr2J_ORxKTLUCydGX4zA", failure.Routing.Node; want != have {
t.Fatalf("want Index[%q].Failures[0].Routing.Node = %q, have %q", indexName, want, have)
}
if have := failure.Routing.RelocatingNode; have != nil {
t.Fatalf("want Index[%q].Failures[0].Routing.RelocatingNode = nil, have %v", indexName, have)
}
}
}
================================================
FILE: indices_flush_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestFlush(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
// Flush all indices
res, err := client.Flush().Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Errorf("expected res to be != nil; got: %v", res)
}
}
func TestFlushBuildURL(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tests := []struct {
Indices []string
Expected string
ExpectValidateFailure bool
}{
{
[]string{},
"/_flush",
false,
},
{
[]string{"index1"},
"/index1/_flush",
false,
},
{
[]string{"index1", "index2"},
"/index1%2Cindex2/_flush",
false,
},
}
for i, test := range tests {
err := NewIndicesFlushService(client).Index(test.Indices...).Validate()
if err == nil && test.ExpectValidateFailure {
t.Errorf("case #%d: expected validate to fail", i+1)
continue
}
if err != nil && !test.ExpectValidateFailure {
t.Errorf("case #%d: expected validate to succeed", i+1)
continue
}
if !test.ExpectValidateFailure {
path, _, err := NewIndicesFlushService(client).Index(test.Indices...).buildURL()
if err != nil {
t.Fatalf("case #%d: %v", i+1, err)
}
if path != test.Expected {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.Expected, path)
}
}
}
}
================================================
FILE: indices_forcemerge.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// IndicesForcemergeService allows to force merging of one or more indices.
// The merge relates to the number of segments a Lucene index holds
// within each shard. The force merge operation allows to reduce the number
// of segments by merging them.
//
// See http://www.elastic.co/guide/en/elasticsearch/reference/7.0/indices-forcemerge.html
// for more information.
type IndicesForcemergeService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
index []string
allowNoIndices *bool
expandWildcards string
flush *bool
ignoreUnavailable *bool
maxNumSegments interface{}
onlyExpungeDeletes *bool
}
// NewIndicesForcemergeService creates a new IndicesForcemergeService.
func NewIndicesForcemergeService(client *Client) *IndicesForcemergeService {
return &IndicesForcemergeService{
client: client,
index: make([]string, 0),
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *IndicesForcemergeService) Pretty(pretty bool) *IndicesForcemergeService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *IndicesForcemergeService) Human(human bool) *IndicesForcemergeService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *IndicesForcemergeService) ErrorTrace(errorTrace bool) *IndicesForcemergeService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *IndicesForcemergeService) FilterPath(filterPath ...string) *IndicesForcemergeService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *IndicesForcemergeService) Header(name string, value string) *IndicesForcemergeService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *IndicesForcemergeService) Headers(headers http.Header) *IndicesForcemergeService {
s.headers = headers
return s
}
// Index is a list of index names; use `_all` or empty string to perform
// the operation on all indices.
func (s *IndicesForcemergeService) Index(index ...string) *IndicesForcemergeService {
if s.index == nil {
s.index = make([]string, 0)
}
s.index = append(s.index, index...)
return s
}
// AllowNoIndices indicates whether to ignore if a wildcard indices
// expression resolves into no concrete indices.
// (This includes `_all` string or when no indices have been specified).
func (s *IndicesForcemergeService) AllowNoIndices(allowNoIndices bool) *IndicesForcemergeService {
s.allowNoIndices = &allowNoIndices
return s
}
// ExpandWildcards indicates whether to expand wildcard expression to
// concrete indices that are open, closed or both..
func (s *IndicesForcemergeService) ExpandWildcards(expandWildcards string) *IndicesForcemergeService {
s.expandWildcards = expandWildcards
return s
}
// Flush specifies whether the index should be flushed after performing
// the operation (default: true).
func (s *IndicesForcemergeService) Flush(flush bool) *IndicesForcemergeService {
s.flush = &flush
return s
}
// IgnoreUnavailable indicates whether specified concrete indices should
// be ignored when unavailable (missing or closed).
func (s *IndicesForcemergeService) IgnoreUnavailable(ignoreUnavailable bool) *IndicesForcemergeService {
s.ignoreUnavailable = &ignoreUnavailable
return s
}
// MaxNumSegments specifies the number of segments the index should be
// merged into (default: dynamic).
func (s *IndicesForcemergeService) MaxNumSegments(maxNumSegments interface{}) *IndicesForcemergeService {
s.maxNumSegments = maxNumSegments
return s
}
// OnlyExpungeDeletes specifies whether the operation should only expunge
// deleted documents.
func (s *IndicesForcemergeService) OnlyExpungeDeletes(onlyExpungeDeletes bool) *IndicesForcemergeService {
s.onlyExpungeDeletes = &onlyExpungeDeletes
return s
}
// buildURL builds the URL for the operation.
func (s *IndicesForcemergeService) buildURL() (string, url.Values, error) {
var err error
var path string
// Build URL
if len(s.index) > 0 {
path, err = uritemplates.Expand("/{index}/_forcemerge", map[string]string{
"index": strings.Join(s.index, ","),
})
} else {
path = "/_forcemerge"
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.allowNoIndices != nil {
params.Set("allow_no_indices", fmt.Sprintf("%v", *s.allowNoIndices))
}
if s.expandWildcards != "" {
params.Set("expand_wildcards", s.expandWildcards)
}
if s.flush != nil {
params.Set("flush", fmt.Sprintf("%v", *s.flush))
}
if s.ignoreUnavailable != nil {
params.Set("ignore_unavailable", fmt.Sprintf("%v", *s.ignoreUnavailable))
}
if s.maxNumSegments != nil {
params.Set("max_num_segments", fmt.Sprintf("%v", s.maxNumSegments))
}
if s.onlyExpungeDeletes != nil {
params.Set("only_expunge_deletes", fmt.Sprintf("%v", *s.onlyExpungeDeletes))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *IndicesForcemergeService) Validate() error {
return nil
}
// Do executes the operation.
func (s *IndicesForcemergeService) Do(ctx context.Context) (*IndicesForcemergeResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "POST",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(IndicesForcemergeResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// IndicesForcemergeResponse is the response of IndicesForcemergeService.Do.
type IndicesForcemergeResponse struct {
Shards *ShardsInfo `json:"_shards"`
}
================================================
FILE: indices_forcemerge_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestIndicesForcemergeBuildURL(t *testing.T) {
client := setupTestClient(t)
tests := []struct {
Indices []string
Expected string
}{
{
[]string{},
"/_forcemerge",
},
{
[]string{"index1"},
"/index1/_forcemerge",
},
{
[]string{"index1", "index2"},
"/index1%2Cindex2/_forcemerge",
},
}
for i, test := range tests {
path, _, err := client.Forcemerge().Index(test.Indices...).buildURL()
if err != nil {
t.Errorf("case #%d: %v", i+1, err)
continue
}
if path != test.Expected {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.Expected, path)
}
}
}
func TestIndicesForcemerge(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t)
_, err := client.Forcemerge(testIndexName).MaxNumSegments(1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
/*
if !ok {
t.Fatalf("expected forcemerge to succeed; got: %v", ok)
}
*/
}
================================================
FILE: indices_freeze.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// IndicesFreezeService freezes an index.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/freeze-index-api.html
// and https://www.elastic.co/blog/creating-frozen-indices-with-the-elasticsearch-freeze-index-api
// for details.
//
// Deprecated: Frozen indices are deprecated because they provide no benefit
// given improvements in heap memory utilization.
type IndicesFreezeService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
index string
timeout string
masterTimeout string
ignoreUnavailable *bool
allowNoIndices *bool
expandWildcards string
waitForActiveShards string
}
// NewIndicesFreezeService creates a new IndicesFreezeService.
func NewIndicesFreezeService(client *Client) *IndicesFreezeService {
return &IndicesFreezeService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *IndicesFreezeService) Pretty(pretty bool) *IndicesFreezeService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *IndicesFreezeService) Human(human bool) *IndicesFreezeService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *IndicesFreezeService) ErrorTrace(errorTrace bool) *IndicesFreezeService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *IndicesFreezeService) FilterPath(filterPath ...string) *IndicesFreezeService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *IndicesFreezeService) Header(name string, value string) *IndicesFreezeService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *IndicesFreezeService) Headers(headers http.Header) *IndicesFreezeService {
s.headers = headers
return s
}
// Index is the name of the index to freeze.
func (s *IndicesFreezeService) Index(index string) *IndicesFreezeService {
s.index = index
return s
}
// Timeout allows to specify an explicit timeout.
func (s *IndicesFreezeService) Timeout(timeout string) *IndicesFreezeService {
s.timeout = timeout
return s
}
// MasterTimeout allows to specify a timeout for connection to master.
func (s *IndicesFreezeService) MasterTimeout(masterTimeout string) *IndicesFreezeService {
s.masterTimeout = masterTimeout
return s
}
// IgnoreUnavailable indicates whether specified concrete indices should be
// ignored when unavailable (missing or closed).
func (s *IndicesFreezeService) IgnoreUnavailable(ignoreUnavailable bool) *IndicesFreezeService {
s.ignoreUnavailable = &ignoreUnavailable
return s
}
// AllowNoIndices indicates whether to ignore if a wildcard indices expression
// resolves into no concrete indices. (This includes `_all` string or when
// no indices have been specified).
func (s *IndicesFreezeService) AllowNoIndices(allowNoIndices bool) *IndicesFreezeService {
s.allowNoIndices = &allowNoIndices
return s
}
// ExpandWildcards specifies whether to expand wildcard expression to
// concrete indices that are open, closed or both..
func (s *IndicesFreezeService) ExpandWildcards(expandWildcards string) *IndicesFreezeService {
s.expandWildcards = expandWildcards
return s
}
// WaitForActiveShards sets the number of active shards to wait for
// before the operation returns.
func (s *IndicesFreezeService) WaitForActiveShards(numShards string) *IndicesFreezeService {
s.waitForActiveShards = numShards
return s
}
// buildURL builds the URL for the operation.
func (s *IndicesFreezeService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/{index}/_freeze", map[string]string{
"index": s.index,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.timeout != "" {
params.Set("timeout", s.timeout)
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
if s.expandWildcards != "" {
params.Set("expand_wildcards", s.expandWildcards)
}
if s.ignoreUnavailable != nil {
params.Set("ignore_unavailable", fmt.Sprintf("%v", *s.ignoreUnavailable))
}
if s.allowNoIndices != nil {
params.Set("allow_no_indices", fmt.Sprintf("%v", *s.allowNoIndices))
}
if s.expandWildcards != "" {
params.Set("expand_wildcards", s.expandWildcards)
}
if s.waitForActiveShards != "" {
params.Set("wait_for_active_shards", s.waitForActiveShards)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *IndicesFreezeService) Validate() error {
var invalid []string
if s.index == "" {
invalid = append(invalid, "Index")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the service.
//
// Deprecated: Frozen indices are deprecated because they provide no benefit
// given improvements in heap memory utilization.
func (s *IndicesFreezeService) Do(ctx context.Context) (*IndicesFreezeResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "POST",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(IndicesFreezeResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// IndicesFreezeResponse is the outcome of freezing an index.
type IndicesFreezeResponse struct {
Shards *ShardsInfo `json:"_shards"`
}
================================================
FILE: indices_freeze_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"testing"
)
func TestIndicesFreezeBuildURL(t *testing.T) {
client := setupTestClient(t)
tests := []struct {
Index string
Expected string
}{
{
"index1",
"/index1/_freeze",
},
}
for i, test := range tests {
path, _, err := client.FreezeIndex(test.Index).buildURL()
if err != nil {
t.Errorf("case #%d: %v", i+1, err)
continue
}
if path != test.Expected {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.Expected, path)
}
}
}
================================================
FILE: indices_get.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// IndicesGetService retrieves information about one or more indices.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/indices-get-index.html
// for more details.
type IndicesGetService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
index []string
feature []string
local *bool
ignoreUnavailable *bool
allowNoIndices *bool
expandWildcards string
flatSettings *bool
}
// NewIndicesGetService creates a new IndicesGetService.
func NewIndicesGetService(client *Client) *IndicesGetService {
return &IndicesGetService{
client: client,
index: make([]string, 0),
feature: make([]string, 0),
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *IndicesGetService) Pretty(pretty bool) *IndicesGetService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *IndicesGetService) Human(human bool) *IndicesGetService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *IndicesGetService) ErrorTrace(errorTrace bool) *IndicesGetService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *IndicesGetService) FilterPath(filterPath ...string) *IndicesGetService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *IndicesGetService) Header(name string, value string) *IndicesGetService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *IndicesGetService) Headers(headers http.Header) *IndicesGetService {
s.headers = headers
return s
}
// Index is a list of index names.
func (s *IndicesGetService) Index(indices ...string) *IndicesGetService {
s.index = append(s.index, indices...)
return s
}
// Feature is a list of features.
func (s *IndicesGetService) Feature(features ...string) *IndicesGetService {
s.feature = append(s.feature, features...)
return s
}
// Local indicates whether to return local information, i.e. do not retrieve
// the state from master node (default: false).
func (s *IndicesGetService) Local(local bool) *IndicesGetService {
s.local = &local
return s
}
// IgnoreUnavailable indicates whether to ignore unavailable indexes (default: false).
func (s *IndicesGetService) IgnoreUnavailable(ignoreUnavailable bool) *IndicesGetService {
s.ignoreUnavailable = &ignoreUnavailable
return s
}
// AllowNoIndices indicates whether to ignore if a wildcard expression
// resolves to no concrete indices (default: false).
func (s *IndicesGetService) AllowNoIndices(allowNoIndices bool) *IndicesGetService {
s.allowNoIndices = &allowNoIndices
return s
}
// ExpandWildcards indicates whether wildcard expressions should get
// expanded to open or closed indices (default: open).
func (s *IndicesGetService) ExpandWildcards(expandWildcards string) *IndicesGetService {
s.expandWildcards = expandWildcards
return s
}
// buildURL builds the URL for the operation.
func (s *IndicesGetService) buildURL() (string, url.Values, error) {
var err error
var path string
var index []string
if len(s.index) > 0 {
index = s.index
} else {
index = []string{"_all"}
}
if len(s.feature) > 0 {
// Build URL
path, err = uritemplates.Expand("/{index}/{feature}", map[string]string{
"index": strings.Join(index, ","),
"feature": strings.Join(s.feature, ","),
})
} else {
// Build URL
path, err = uritemplates.Expand("/{index}", map[string]string{
"index": strings.Join(index, ","),
})
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.expandWildcards != "" {
params.Set("expand_wildcards", s.expandWildcards)
}
if s.flatSettings != nil {
params.Set("flat_settings", fmt.Sprintf("%v", *s.flatSettings))
}
if s.local != nil {
params.Set("local", fmt.Sprintf("%v", *s.local))
}
if s.ignoreUnavailable != nil {
params.Set("ignore_unavailable", fmt.Sprintf("%v", *s.ignoreUnavailable))
}
if s.allowNoIndices != nil {
params.Set("allow_no_indices", fmt.Sprintf("%v", *s.allowNoIndices))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *IndicesGetService) Validate() error {
var invalid []string
if len(s.index) == 0 {
invalid = append(invalid, "Index")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *IndicesGetService) Do(ctx context.Context) (map[string]*IndicesGetResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
var ret map[string]*IndicesGetResponse
if err := s.client.decoder.Decode(res.Body, &ret); err != nil {
return nil, err
}
return ret, nil
}
// IndicesGetResponse is part of the response of IndicesGetService.Do.
type IndicesGetResponse struct {
Aliases map[string]interface{} `json:"aliases"`
Mappings map[string]interface{} `json:"mappings"`
Settings map[string]interface{} `json:"settings"`
Warmers map[string]interface{} `json:"warmers"`
}
================================================
FILE: indices_get_aliases.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// AliasesService returns the aliases associated with one or more indices, or the
// indices associated with one or more aliases, or a combination of those filters.
// See http://www.elastic.co/guide/en/elasticsearch/reference/7.0/indices-aliases.html.
type AliasesService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
index []string
alias []string
}
// NewAliasesService instantiates a new AliasesService.
func NewAliasesService(client *Client) *AliasesService {
builder := &AliasesService{
client: client,
}
return builder
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *AliasesService) Pretty(pretty bool) *AliasesService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *AliasesService) Human(human bool) *AliasesService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *AliasesService) ErrorTrace(errorTrace bool) *AliasesService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *AliasesService) FilterPath(filterPath ...string) *AliasesService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *AliasesService) Header(name string, value string) *AliasesService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *AliasesService) Headers(headers http.Header) *AliasesService {
s.headers = headers
return s
}
// Index adds one or more indices.
func (s *AliasesService) Index(index ...string) *AliasesService {
s.index = append(s.index, index...)
return s
}
// Alias adds one or more aliases.
func (s *AliasesService) Alias(alias ...string) *AliasesService {
s.alias = append(s.alias, alias...)
return s
}
// buildURL builds the URL for the operation.
func (s *AliasesService) buildURL() (string, url.Values, error) {
var err error
var path string
if len(s.index) > 0 {
path, err = uritemplates.Expand("/{index}/_alias/{alias}", map[string]string{
"index": strings.Join(s.index, ","),
"alias": strings.Join(s.alias, ","),
})
} else {
path, err = uritemplates.Expand("/_alias/{alias}", map[string]string{
"alias": strings.Join(s.alias, ","),
})
}
if err != nil {
return "", url.Values{}, err
}
path = strings.TrimSuffix(path, "/")
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
return path, params, nil
}
func (s *AliasesService) Do(ctx context.Context) (*AliasesResult, error) {
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// {
// "indexName" : {
// "aliases" : {
// "alias1" : { },
// "alias2" : { }
// }
// },
// "indexName2" : {
// ...
// },
// }
indexMap := make(map[string]struct {
Aliases map[string]struct {
IsWriteIndex bool `json:"is_write_index"`
} `json:"aliases"`
})
if err := s.client.decoder.Decode(res.Body, &indexMap); err != nil {
return nil, err
}
// Each (indexName, _)
ret := &AliasesResult{
Indices: make(map[string]indexResult),
}
for indexName, indexData := range indexMap {
if indexData.Aliases == nil {
continue
}
indexOut, found := ret.Indices[indexName]
if !found {
indexOut = indexResult{Aliases: make([]aliasResult, 0)}
}
// { "aliases" : { ... } }
for aliasName, aliasData := range indexData.Aliases {
aliasRes := aliasResult{AliasName: aliasName, IsWriteIndex: aliasData.IsWriteIndex}
indexOut.Aliases = append(indexOut.Aliases, aliasRes)
}
ret.Indices[indexName] = indexOut
}
return ret, nil
}
// -- Result of an alias request.
// AliasesResult is the outcome of calling AliasesService.Do.
type AliasesResult struct {
Indices map[string]indexResult
}
type indexResult struct {
Aliases []aliasResult
}
type aliasResult struct {
AliasName string
IsWriteIndex bool
}
// IndicesByAlias returns all indices given a specific alias name.
func (ar AliasesResult) IndicesByAlias(aliasName string) []string {
var indices []string
for indexName, indexInfo := range ar.Indices {
for _, aliasInfo := range indexInfo.Aliases {
if aliasInfo.AliasName == aliasName {
indices = append(indices, indexName)
}
}
}
return indices
}
// HasAlias returns true if the index has a specific alias.
func (ir indexResult) HasAlias(aliasName string) bool {
for _, alias := range ir.Aliases {
if alias.AliasName == aliasName {
return true
}
}
return false
}
================================================
FILE: indices_get_aliases_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestAliasesBuildURL(t *testing.T) {
client := setupTestClient(t)
tests := []struct {
Indices []string
Aliases []string
Expected string
}{
{
[]string{},
[]string{},
"/_alias",
},
{
[]string{"index1"},
[]string{},
"/index1/_alias",
},
{
[]string{"index1", "index2"},
[]string{},
"/index1%2Cindex2/_alias",
},
{
[]string{"index1", "index2"},
[]string{"alias1"},
"/index1%2Cindex2/_alias/alias1",
},
{
[]string{},
[]string{"alias1"},
"/_alias/alias1",
},
{
[]string{"index1"},
[]string{"alias1"},
"/index1/_alias/alias1",
},
{
[]string{"index1"},
[]string{"alias1", "alias2"},
"/index1/_alias/alias1%2Calias2",
},
{
[]string{},
[]string{"alias1", "alias2"},
"/_alias/alias1%2Calias2",
},
{
[]string{"index1", "index2"},
[]string{"alias1", "alias2"},
"/index1%2Cindex2/_alias/alias1%2Calias2",
},
}
for i, test := range tests {
path, _, err := client.Aliases().Index(test.Indices...).Alias(test.Aliases...).buildURL()
if err != nil {
t.Errorf("case #%d: %v", i+1, err)
continue
}
if path != test.Expected {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.Expected, path)
}
}
}
func TestAliases(t *testing.T) {
var err error
// client := setupTestClientAndCreateIndex(t, SetTraceLog(log.New(os.Stdout, "", 0)))
client := setupTestClientAndCreateIndex(t)
// Some tweets
tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
tweet2 := tweet{User: "sandrae", Message: "Cycling is fun."}
tweet3 := tweet{User: "olivere", Message: "Another unrelated topic."}
// Add tweets to first index
_, err = client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Add tweets to second index
_, err = client.Index().Index(testIndexName2).Id("3").BodyJson(&tweet3).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Refresh
_, err = client.Refresh().Index(testIndexName, testIndexName2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Alias should not yet exist
aliasesResult1, err := client.Aliases().
Index(testIndexName, testIndexName2).
Pretty(true).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if len(aliasesResult1.Indices) != 2 {
t.Errorf("expected len(AliasesResult.Indices) = %d; got %d", 2, len(aliasesResult1.Indices))
}
for indexName, indexDetails := range aliasesResult1.Indices {
if len(indexDetails.Aliases) != 0 {
t.Errorf("expected len(AliasesResult.Indices[%s].Aliases) = %d; got %d", indexName, 0, len(indexDetails.Aliases))
}
}
// Add both indices to a new alias
aliasCreate, err := client.Alias().
Add(testIndexName, testAliasName).
Action(
NewAliasAddAction(testAliasName).
Index(testIndexName2).
IsWriteIndex(true),
).
//Pretty(true).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if !aliasCreate.Acknowledged {
t.Errorf("expected AliasResult.Acknowledged %v; got %v", true, aliasCreate.Acknowledged)
}
// Alias should now exist
aliasesResult2, err := client.Aliases().
Index(testIndexName, testIndexName2).
//Pretty(true).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if len(aliasesResult2.Indices) != 2 {
t.Errorf("expected len(AliasesResult.Indices) = %d; got %d", 2, len(aliasesResult2.Indices))
}
for indexName, indexDetails := range aliasesResult2.Indices {
if len(indexDetails.Aliases) != 1 {
t.Errorf("expected len(AliasesResult.Indices[%s].Aliases) = %d; got %d", indexName, 1, len(indexDetails.Aliases))
}
}
indicesResult, err := client.Aliases().
Alias(testAliasName).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if len(indicesResult.Indices) != 2 {
t.Errorf("expected len(indicesResult.Indices) = %d; got %d", 2, len(indicesResult.Indices))
}
for indexName, indexDetails := range indicesResult.Indices {
if len(indexDetails.Aliases) != 1 {
t.Errorf("expected len(indicesResult.Indices[%s].Aliases) = %d; got %d", indexName, 1, len(indexDetails.Aliases))
}
if indexName == testIndexName2 {
if !indexDetails.Aliases[0].IsWriteIndex {
t.Errorf("expected alias on %s to be a write index", testIndexName2)
}
}
if indexName == testIndexName {
if indexDetails.Aliases[0].IsWriteIndex {
t.Errorf("expected alias on %s not to be a write index", testIndexName2)
}
}
}
// Check the reverse function:
indexInfo1, found := aliasesResult2.Indices[testIndexName]
if !found {
t.Errorf("expected info about index %s = %v; got %v", testIndexName, true, found)
}
aliasFound := indexInfo1.HasAlias(testAliasName)
if !aliasFound {
t.Errorf("expected alias %s to include index %s; got %v", testAliasName, testIndexName, aliasFound)
}
// Check the reverse function:
indexInfo2, found := aliasesResult2.Indices[testIndexName2]
if !found {
t.Errorf("expected info about index %s = %v; got %v", testIndexName, true, found)
}
aliasFound = indexInfo2.HasAlias(testAliasName)
if !aliasFound {
t.Errorf("expected alias %s to include index %s; got %v", testAliasName, testIndexName2, aliasFound)
}
// Remove first index should remove two tweets, so should only yield 1
aliasRemove1, err := client.Alias().
Remove(testIndexName, testAliasName).
//Pretty(true).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if !aliasRemove1.Acknowledged {
t.Errorf("expected AliasResult.Acknowledged %v; got %v", true, aliasRemove1.Acknowledged)
}
// Alias should now exist only for index 2
aliasesResult3, err := client.Aliases().Index(testIndexName, testIndexName2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if len(aliasesResult3.Indices) != 2 {
t.Errorf("expected len(AliasesResult.Indices) = %d; got %d", 2, len(aliasesResult3.Indices))
}
for indexName, indexDetails := range aliasesResult3.Indices {
if indexName == testIndexName {
if len(indexDetails.Aliases) != 0 {
t.Errorf("expected len(AliasesResult.Indices[%s].Aliases) = %d; got %d", indexName, 0, len(indexDetails.Aliases))
}
} else if indexName == testIndexName2 {
if len(indexDetails.Aliases) != 1 {
t.Errorf("expected len(AliasesResult.Indices[%s].Aliases) = %d; got %d", indexName, 1, len(indexDetails.Aliases))
}
} else {
t.Errorf("got index %s", indexName)
}
}
}
================================================
FILE: indices_get_component_template.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// IndicesGetComponentTemplateService returns a component template.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.10/getting-component-templates.html
// for more details.
type IndicesGetComponentTemplateService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
name []string
masterTimeout string
flatSettings *bool
local *bool
}
// NewIndicesGetComponentTemplateService creates a new IndicesGetComponentTemplateService.
func NewIndicesGetComponentTemplateService(client *Client) *IndicesGetComponentTemplateService {
return &IndicesGetComponentTemplateService{
client: client,
name: make([]string, 0),
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *IndicesGetComponentTemplateService) Pretty(pretty bool) *IndicesGetComponentTemplateService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *IndicesGetComponentTemplateService) Human(human bool) *IndicesGetComponentTemplateService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *IndicesGetComponentTemplateService) ErrorTrace(errorTrace bool) *IndicesGetComponentTemplateService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *IndicesGetComponentTemplateService) FilterPath(filterPath ...string) *IndicesGetComponentTemplateService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *IndicesGetComponentTemplateService) Header(name string, value string) *IndicesGetComponentTemplateService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *IndicesGetComponentTemplateService) Headers(headers http.Header) *IndicesGetComponentTemplateService {
s.headers = headers
return s
}
// Name is the name of the component template.
func (s *IndicesGetComponentTemplateService) Name(name ...string) *IndicesGetComponentTemplateService {
s.name = append(s.name, name...)
return s
}
// FlatSettings is returns settings in flat format (default: false).
func (s *IndicesGetComponentTemplateService) FlatSettings(flatSettings bool) *IndicesGetComponentTemplateService {
s.flatSettings = &flatSettings
return s
}
// Local indicates whether to return local information, i.e. do not retrieve
// the state from master node (default: false).
func (s *IndicesGetComponentTemplateService) Local(local bool) *IndicesGetComponentTemplateService {
s.local = &local
return s
}
// MasterTimeout specifies the timeout for connection to master.
func (s *IndicesGetComponentTemplateService) MasterTimeout(masterTimeout string) *IndicesGetComponentTemplateService {
s.masterTimeout = masterTimeout
return s
}
// buildURL builds the URL for the operation.
func (s *IndicesGetComponentTemplateService) buildURL() (string, url.Values, error) {
// Build URL
var err error
var path string
if len(s.name) > 0 {
path, err = uritemplates.Expand("/_component_template/{name}", map[string]string{
"name": strings.Join(s.name, ","),
})
} else {
path = "/_component_template"
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.flatSettings != nil {
params.Set("flat_settings", fmt.Sprintf("%v", *s.flatSettings))
}
if s.local != nil {
params.Set("local", fmt.Sprintf("%v", *s.local))
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *IndicesGetComponentTemplateService) Validate() error {
return nil
}
// Do executes the operation.
func (s *IndicesGetComponentTemplateService) Do(ctx context.Context) (*IndicesGetComponentTemplateResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
var ret *IndicesGetComponentTemplateResponse
if err := s.client.decoder.Decode(res.Body, &ret); err != nil {
return nil, err
}
return ret, nil
}
// IndicesGetComponentTemplateResponse is the response of IndicesGetComponentTemplateService.Do.
type IndicesGetComponentTemplateResponse struct {
ComponentTemplates []IndicesGetComponentTemplates `json:"component_templates"`
}
type IndicesGetComponentTemplates struct {
Name string `json:"name"`
ComponentTemplate *IndicesGetComponentTemplate `json:"component_template"`
}
type IndicesGetComponentTemplate struct {
Version int `json:"version,omitempty"`
Template *IndicesGetComponentTemplateData `json:"template,omitempty"`
}
type IndicesGetComponentTemplateData struct {
Settings map[string]interface{} `json:"settings,omitempty"`
Mappings map[string]interface{} `json:"mappings,omitempty"`
Aliases map[string]interface{} `json:"aliases,omitempty"`
}
================================================
FILE: indices_get_field_mapping.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// IndicesGetFieldMappingService retrieves the mapping definitions for the fields in an index
// or index/type.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/indices-get-field-mapping.html
// for details.
type IndicesGetFieldMappingService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
index []string
typ []string
field []string
local *bool
ignoreUnavailable *bool
allowNoIndices *bool
expandWildcards string
}
// NewGetFieldMappingService is an alias for NewIndicesGetFieldMappingService.
// Use NewIndicesGetFieldMappingService.
func NewGetFieldMappingService(client *Client) *IndicesGetFieldMappingService {
return NewIndicesGetFieldMappingService(client)
}
// NewIndicesGetFieldMappingService creates a new IndicesGetFieldMappingService.
func NewIndicesGetFieldMappingService(client *Client) *IndicesGetFieldMappingService {
return &IndicesGetFieldMappingService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *IndicesGetFieldMappingService) Pretty(pretty bool) *IndicesGetFieldMappingService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *IndicesGetFieldMappingService) Human(human bool) *IndicesGetFieldMappingService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *IndicesGetFieldMappingService) ErrorTrace(errorTrace bool) *IndicesGetFieldMappingService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *IndicesGetFieldMappingService) FilterPath(filterPath ...string) *IndicesGetFieldMappingService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *IndicesGetFieldMappingService) Header(name string, value string) *IndicesGetFieldMappingService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *IndicesGetFieldMappingService) Headers(headers http.Header) *IndicesGetFieldMappingService {
s.headers = headers
return s
}
// Index is a list of index names.
func (s *IndicesGetFieldMappingService) Index(indices ...string) *IndicesGetFieldMappingService {
s.index = append(s.index, indices...)
return s
}
// Type is a list of document types.
func (s *IndicesGetFieldMappingService) Type(types ...string) *IndicesGetFieldMappingService {
s.typ = append(s.typ, types...)
return s
}
// Field is a list of fields.
func (s *IndicesGetFieldMappingService) Field(fields ...string) *IndicesGetFieldMappingService {
s.field = append(s.field, fields...)
return s
}
// AllowNoIndices indicates whether to ignore if a wildcard indices
// expression resolves into no concrete indices.
// This includes `_all` string or when no indices have been specified.
func (s *IndicesGetFieldMappingService) AllowNoIndices(allowNoIndices bool) *IndicesGetFieldMappingService {
s.allowNoIndices = &allowNoIndices
return s
}
// ExpandWildcards indicates whether to expand wildcard expression to
// concrete indices that are open, closed or both..
func (s *IndicesGetFieldMappingService) ExpandWildcards(expandWildcards string) *IndicesGetFieldMappingService {
s.expandWildcards = expandWildcards
return s
}
// Local indicates whether to return local information, do not retrieve
// the state from master node (default: false).
func (s *IndicesGetFieldMappingService) Local(local bool) *IndicesGetFieldMappingService {
s.local = &local
return s
}
// IgnoreUnavailable indicates whether specified concrete indices should be
// ignored when unavailable (missing or closed).
func (s *IndicesGetFieldMappingService) IgnoreUnavailable(ignoreUnavailable bool) *IndicesGetFieldMappingService {
s.ignoreUnavailable = &ignoreUnavailable
return s
}
// buildURL builds the URL for the operation.
func (s *IndicesGetFieldMappingService) buildURL() (string, url.Values, error) {
var index, typ, field []string
if len(s.index) > 0 {
index = s.index
} else {
index = []string{"_all"}
}
if len(s.typ) > 0 {
typ = s.typ
} else {
typ = []string{"_all"}
}
if len(s.field) > 0 {
field = s.field
} else {
field = []string{"*"}
}
// Build URL
path, err := uritemplates.Expand("/{index}/_mapping/{type}/field/{field}", map[string]string{
"index": strings.Join(index, ","),
"type": strings.Join(typ, ","),
"field": strings.Join(field, ","),
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.ignoreUnavailable != nil {
params.Set("ignore_unavailable", fmt.Sprintf("%v", *s.ignoreUnavailable))
}
if s.allowNoIndices != nil {
params.Set("allow_no_indices", fmt.Sprintf("%v", *s.allowNoIndices))
}
if s.expandWildcards != "" {
params.Set("expand_wildcards", s.expandWildcards)
}
if s.local != nil {
params.Set("local", fmt.Sprintf("%v", *s.local))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *IndicesGetFieldMappingService) Validate() error {
return nil
}
// Do executes the operation. It returns mapping definitions for an index
// or index/type.
func (s *IndicesGetFieldMappingService) Do(ctx context.Context) (map[string]interface{}, error) {
var ret map[string]interface{}
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
if err := s.client.decoder.Decode(res.Body, &ret); err != nil {
return nil, err
}
return ret, nil
}
================================================
FILE: indices_get_field_mapping_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"testing"
)
func TestIndicesGetFieldMappingURL(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tests := []struct {
Indices []string
Types []string
Fields []string
Expected string
}{
{
[]string{},
[]string{},
[]string{},
"/_all/_mapping/_all/field/%2A",
},
{
[]string{},
[]string{"tweet"},
[]string{"message"},
"/_all/_mapping/tweet/field/message",
},
{
[]string{"twitter"},
[]string{"tweet"},
[]string{"*.id"},
"/twitter/_mapping/tweet/field/%2A.id",
},
{
[]string{"store-1", "store-2"},
[]string{"tweet", "user"},
[]string{"message", "*.id"},
"/store-1%2Cstore-2/_mapping/tweet%2Cuser/field/message%2C%2A.id",
},
}
for _, test := range tests {
path, _, err := client.GetFieldMapping().Index(test.Indices...).Type(test.Types...).Field(test.Fields...).buildURL()
if err != nil {
t.Fatal(err)
}
if path != test.Expected {
t.Errorf("expected %q; got: %q", test.Expected, path)
}
}
}
================================================
FILE: indices_get_index_template.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// IndicesGetIndexTemplateService returns an index template.
//
// Index templates have changed during in 7.8 update of Elasticsearch.
// This service implements the new version (7.8 or later). If you want
// the old version, please use the IndicesGetTemplateService.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.9/indices-get-template.html
// for more details.
type IndicesGetIndexTemplateService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
name []string
masterTimeout string
flatSettings *bool
local *bool
}
// NewIndicesGetIndexTemplateService creates a new IndicesGetIndexTemplateService.
func NewIndicesGetIndexTemplateService(client *Client) *IndicesGetIndexTemplateService {
return &IndicesGetIndexTemplateService{
client: client,
name: make([]string, 0),
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *IndicesGetIndexTemplateService) Pretty(pretty bool) *IndicesGetIndexTemplateService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *IndicesGetIndexTemplateService) Human(human bool) *IndicesGetIndexTemplateService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *IndicesGetIndexTemplateService) ErrorTrace(errorTrace bool) *IndicesGetIndexTemplateService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *IndicesGetIndexTemplateService) FilterPath(filterPath ...string) *IndicesGetIndexTemplateService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *IndicesGetIndexTemplateService) Header(name string, value string) *IndicesGetIndexTemplateService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *IndicesGetIndexTemplateService) Headers(headers http.Header) *IndicesGetIndexTemplateService {
s.headers = headers
return s
}
// Name is the name of the index template.
func (s *IndicesGetIndexTemplateService) Name(name ...string) *IndicesGetIndexTemplateService {
s.name = append(s.name, name...)
return s
}
// FlatSettings is returns settings in flat format (default: false).
func (s *IndicesGetIndexTemplateService) FlatSettings(flatSettings bool) *IndicesGetIndexTemplateService {
s.flatSettings = &flatSettings
return s
}
// Local indicates whether to return local information, i.e. do not retrieve
// the state from master node (default: false).
func (s *IndicesGetIndexTemplateService) Local(local bool) *IndicesGetIndexTemplateService {
s.local = &local
return s
}
// MasterTimeout specifies the timeout for connection to master.
func (s *IndicesGetIndexTemplateService) MasterTimeout(masterTimeout string) *IndicesGetIndexTemplateService {
s.masterTimeout = masterTimeout
return s
}
// buildURL builds the URL for the operation.
func (s *IndicesGetIndexTemplateService) buildURL() (string, url.Values, error) {
// Build URL
var err error
var path string
if len(s.name) > 0 {
path, err = uritemplates.Expand("/_index_template/{name}", map[string]string{
"name": strings.Join(s.name, ","),
})
} else {
path = "/_template"
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.flatSettings != nil {
params.Set("flat_settings", fmt.Sprintf("%v", *s.flatSettings))
}
if s.local != nil {
params.Set("local", fmt.Sprintf("%v", *s.local))
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *IndicesGetIndexTemplateService) Validate() error {
return nil
}
// Do executes the operation.
func (s *IndicesGetIndexTemplateService) Do(ctx context.Context) (*IndicesGetIndexTemplateResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
var ret *IndicesGetIndexTemplateResponse
if err := s.client.decoder.Decode(res.Body, &ret); err != nil {
return nil, err
}
return ret, nil
}
// IndicesGetIndexTemplateResponse is the response of IndicesGetIndexTemplateService.Do.
type IndicesGetIndexTemplateResponse struct {
IndexTemplates IndicesGetIndexTemplatesSlice `json:"index_templates"`
}
// IndicesGetIndexTemplatesSlice is a slice of IndicesGetIndexTemplates.
type IndicesGetIndexTemplatesSlice []IndicesGetIndexTemplates
// ByName returns the template with the given name, if it exists.
// The bool indicates whether a template with that name has been found.
func (slice IndicesGetIndexTemplatesSlice) ByName(name string) (*IndicesGetIndexTemplates, bool) {
for _, t := range slice {
if t.Name == name {
return &t, true
}
}
return nil, false
}
type IndicesGetIndexTemplates struct {
Name string `json:"name"`
IndexTemplate *IndicesGetIndexTemplate `json:"index_template"`
}
type IndicesGetIndexTemplate struct {
IndexPatterns []string `json:"index_patterns,omitempty"`
ComposedOf []string `json:"composed_of,omitempty"`
Priority int `json:"priority,omitempty"`
Version int `json:"version,omitempty"`
Template *IndicesGetIndexTemplateData `json:"template,omitempty"`
Meta map[string]interface{} `json:"_meta,omitempty"`
DataStream *IndicesDataStream `json:"data_stream,omitempty"`
AllowAutoCreate bool `json:"allow_auto_create,omitempty"`
}
type IndicesGetIndexTemplateData struct {
Settings map[string]interface{} `json:"settings,omitempty"`
Mappings map[string]interface{} `json:"mappings,omitempty"`
Aliases map[string]interface{} `json:"aliases,omitempty"`
}
type IndicesDataStream struct {
Name string `json:"name,omitempty"`
TimestampField *IndicesDataStreamTimestampField `json:"timestamp_field,omitempty"`
Indices []string `json:"indices,omitempty"`
Generation int64 `json:"generation,omitempty"`
Status string `json:"status,omitempty"`
IndexTemplate string `json:"template,omitempty"`
IlmPolicy string `json:"ilm_policy,omitempty"`
Meta map[string]interface{} `json:"_meta,omitempty"`
Hidden bool `json:"hidden,omitempty"`
System bool `json:"system,omitempty"`
AllowCustomRouting bool `json:"allow_custom_routing,omitempty"`
Replicated bool `json:"replicated,omitempty"`
}
type IndicesDataStreamTimestampField struct {
Name string `json:"name,omitempty"`
}
================================================
FILE: indices_get_index_template_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestIndexGetIndexTemplateURL(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tests := []struct {
Name string
Expected string
}{
{
"template_1",
"/_index_template/template_1",
},
}
for _, test := range tests {
path, _, err := client.IndexGetIndexTemplate(test.Name).buildURL()
if err != nil {
t.Fatal(err)
}
if path != test.Expected {
t.Errorf("expected %q; got: %q", test.Expected, path)
}
}
}
func TestIndexGetIndexTemplateService(t *testing.T) {
// client := setupTestClientAndCreateIndex(t, SetTraceLog(log.New(os.Stdout, "", 0)))
client := setupTestClientAndCreateIndex(t)
create := true
body := `
{
"index_patterns": ["te*"],
"priority": 1,
"template": {
"settings": {
"index": {
"number_of_shards": 1
}
},
"mappings": {
"_source": {
"enabled": false
},
"properties": {
"host_name": {
"type": "keyword"
},
"created_at": {
"type": "date",
"format": "yyyy MM dd HH:mm:ss Z"
}
}
}
}
}
`
_, err := client.IndexPutIndexTemplate("template_1").BodyString(body).Create(create).Pretty(true).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
defer client.IndexDeleteIndexTemplate("template_1").Pretty(true).Do(context.TODO())
res, err := client.IndexGetIndexTemplate("template_1").Pretty(true).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatalf("expected result; got: %v", res)
}
template, found := res.IndexTemplates.ByName("template_1")
if !found {
t.Fatalf("expected template %q to be found; got: %v", "template_1", found)
}
if template == nil {
t.Fatalf("expected template %q to be != nil; got: %v", "template_1", template)
}
if template.IndexTemplate == nil {
t.Fatalf("expected index template of template %q to be != nil; got: %v", "template_1", template.IndexTemplate)
}
if len(template.IndexTemplate.IndexPatterns) != 1 || template.IndexTemplate.IndexPatterns[0] != "te*" {
t.Fatalf("expected index settings of %q to be [\"index1\"]; got: %v", testIndexName, template.IndexTemplate.IndexPatterns)
}
}
================================================
FILE: indices_get_mapping.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// IndicesGetMappingService retrieves the mapping definitions for an index or
// index/type.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/indices-get-mapping.html
// for details.
type IndicesGetMappingService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
index []string
typ []string
local *bool
ignoreUnavailable *bool
allowNoIndices *bool
expandWildcards string
}
// NewGetMappingService is an alias for NewIndicesGetMappingService.
// Use NewIndicesGetMappingService.
func NewGetMappingService(client *Client) *IndicesGetMappingService {
return NewIndicesGetMappingService(client)
}
// NewIndicesGetMappingService creates a new IndicesGetMappingService.
func NewIndicesGetMappingService(client *Client) *IndicesGetMappingService {
return &IndicesGetMappingService{
client: client,
index: make([]string, 0),
typ: make([]string, 0),
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *IndicesGetMappingService) Pretty(pretty bool) *IndicesGetMappingService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *IndicesGetMappingService) Human(human bool) *IndicesGetMappingService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *IndicesGetMappingService) ErrorTrace(errorTrace bool) *IndicesGetMappingService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *IndicesGetMappingService) FilterPath(filterPath ...string) *IndicesGetMappingService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *IndicesGetMappingService) Header(name string, value string) *IndicesGetMappingService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *IndicesGetMappingService) Headers(headers http.Header) *IndicesGetMappingService {
s.headers = headers
return s
}
// Index is a list of index names.
func (s *IndicesGetMappingService) Index(indices ...string) *IndicesGetMappingService {
s.index = append(s.index, indices...)
return s
}
// Type is a list of document types.
func (s *IndicesGetMappingService) Type(types ...string) *IndicesGetMappingService {
s.typ = append(s.typ, types...)
return s
}
// AllowNoIndices indicates whether to ignore if a wildcard indices
// expression resolves into no concrete indices.
// This includes `_all` string or when no indices have been specified.
func (s *IndicesGetMappingService) AllowNoIndices(allowNoIndices bool) *IndicesGetMappingService {
s.allowNoIndices = &allowNoIndices
return s
}
// ExpandWildcards indicates whether to expand wildcard expression to
// concrete indices that are open, closed or both..
func (s *IndicesGetMappingService) ExpandWildcards(expandWildcards string) *IndicesGetMappingService {
s.expandWildcards = expandWildcards
return s
}
// Local indicates whether to return local information, do not retrieve
// the state from master node (default: false).
func (s *IndicesGetMappingService) Local(local bool) *IndicesGetMappingService {
s.local = &local
return s
}
// IgnoreUnavailable indicates whether specified concrete indices should be
// ignored when unavailable (missing or closed).
func (s *IndicesGetMappingService) IgnoreUnavailable(ignoreUnavailable bool) *IndicesGetMappingService {
s.ignoreUnavailable = &ignoreUnavailable
return s
}
// buildURL builds the URL for the operation.
func (s *IndicesGetMappingService) buildURL() (string, url.Values, error) {
var index, typ []string
if len(s.index) > 0 {
index = s.index
} else {
index = []string{"_all"}
}
if len(s.typ) > 0 {
typ = s.typ
} else {
typ = []string{"_all"}
}
// Build URL
path, err := uritemplates.Expand("/{index}/_mapping/{type}", map[string]string{
"index": strings.Join(index, ","),
"type": strings.Join(typ, ","),
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.ignoreUnavailable != nil {
params.Set("ignore_unavailable", fmt.Sprintf("%v", *s.ignoreUnavailable))
}
if s.allowNoIndices != nil {
params.Set("allow_no_indices", fmt.Sprintf("%v", *s.allowNoIndices))
}
if s.expandWildcards != "" {
params.Set("expand_wildcards", s.expandWildcards)
}
if s.local != nil {
params.Set("local", fmt.Sprintf("%v", *s.local))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *IndicesGetMappingService) Validate() error {
return nil
}
// Do executes the operation. It returns mapping definitions for an index
// or index/type.
func (s *IndicesGetMappingService) Do(ctx context.Context) (map[string]interface{}, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
var ret map[string]interface{}
if err := s.client.decoder.Decode(res.Body, &ret); err != nil {
return nil, err
}
return ret, nil
}
================================================
FILE: indices_get_mapping_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"testing"
)
func TestIndicesGetMappingURL(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tests := []struct {
Indices []string
Types []string
Expected string
}{
{
[]string{},
[]string{},
"/_all/_mapping/_all",
},
{
[]string{},
[]string{"tweet"},
"/_all/_mapping/tweet",
},
{
[]string{"twitter"},
[]string{"tweet"},
"/twitter/_mapping/tweet",
},
{
[]string{"store-1", "store-2"},
[]string{"tweet", "user"},
"/store-1%2Cstore-2/_mapping/tweet%2Cuser",
},
}
for _, test := range tests {
path, _, err := client.GetMapping().Index(test.Indices...).Type(test.Types...).buildURL()
if err != nil {
t.Fatal(err)
}
if path != test.Expected {
t.Errorf("expected %q; got: %q", test.Expected, path)
}
}
}
================================================
FILE: indices_get_settings.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// IndicesGetSettingsService allows to retrieve settings of one
// or more indices.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/indices-get-settings.html
// for more details.
type IndicesGetSettingsService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
index []string
name []string
ignoreUnavailable *bool
allowNoIndices *bool
expandWildcards string
flatSettings *bool
local *bool
}
// NewIndicesGetSettingsService creates a new IndicesGetSettingsService.
func NewIndicesGetSettingsService(client *Client) *IndicesGetSettingsService {
return &IndicesGetSettingsService{
client: client,
index: make([]string, 0),
name: make([]string, 0),
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *IndicesGetSettingsService) Pretty(pretty bool) *IndicesGetSettingsService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *IndicesGetSettingsService) Human(human bool) *IndicesGetSettingsService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *IndicesGetSettingsService) ErrorTrace(errorTrace bool) *IndicesGetSettingsService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *IndicesGetSettingsService) FilterPath(filterPath ...string) *IndicesGetSettingsService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *IndicesGetSettingsService) Header(name string, value string) *IndicesGetSettingsService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *IndicesGetSettingsService) Headers(headers http.Header) *IndicesGetSettingsService {
s.headers = headers
return s
}
// Index is a list of index names; use `_all` or empty string to perform
// the operation on all indices.
func (s *IndicesGetSettingsService) Index(indices ...string) *IndicesGetSettingsService {
s.index = append(s.index, indices...)
return s
}
// Name are the names of the settings that should be included.
func (s *IndicesGetSettingsService) Name(name ...string) *IndicesGetSettingsService {
s.name = append(s.name, name...)
return s
}
// IgnoreUnavailable indicates whether specified concrete indices should
// be ignored when unavailable (missing or closed).
func (s *IndicesGetSettingsService) IgnoreUnavailable(ignoreUnavailable bool) *IndicesGetSettingsService {
s.ignoreUnavailable = &ignoreUnavailable
return s
}
// AllowNoIndices indicates whether to ignore if a wildcard indices
// expression resolves into no concrete indices.
// (This includes `_all` string or when no indices have been specified).
func (s *IndicesGetSettingsService) AllowNoIndices(allowNoIndices bool) *IndicesGetSettingsService {
s.allowNoIndices = &allowNoIndices
return s
}
// ExpandWildcards indicates whether to expand wildcard expression
// to concrete indices that are open, closed or both.
// Options: open, closed, none, all. Default: open,closed.
func (s *IndicesGetSettingsService) ExpandWildcards(expandWildcards string) *IndicesGetSettingsService {
s.expandWildcards = expandWildcards
return s
}
// FlatSettings indicates whether to return settings in flat format (default: false).
func (s *IndicesGetSettingsService) FlatSettings(flatSettings bool) *IndicesGetSettingsService {
s.flatSettings = &flatSettings
return s
}
// Local indicates whether to return local information, do not retrieve
// the state from master node (default: false).
func (s *IndicesGetSettingsService) Local(local bool) *IndicesGetSettingsService {
s.local = &local
return s
}
// buildURL builds the URL for the operation.
func (s *IndicesGetSettingsService) buildURL() (string, url.Values, error) {
var err error
var path string
var index []string
if len(s.index) > 0 {
index = s.index
} else {
index = []string{"_all"}
}
if len(s.name) > 0 {
// Build URL
path, err = uritemplates.Expand("/{index}/_settings/{name}", map[string]string{
"index": strings.Join(index, ","),
"name": strings.Join(s.name, ","),
})
} else {
// Build URL
path, err = uritemplates.Expand("/{index}/_settings", map[string]string{
"index": strings.Join(index, ","),
})
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.ignoreUnavailable != nil {
params.Set("ignore_unavailable", fmt.Sprintf("%v", *s.ignoreUnavailable))
}
if s.allowNoIndices != nil {
params.Set("allow_no_indices", fmt.Sprintf("%v", *s.allowNoIndices))
}
if s.expandWildcards != "" {
params.Set("expand_wildcards", s.expandWildcards)
}
if s.flatSettings != nil {
params.Set("flat_settings", fmt.Sprintf("%v", *s.flatSettings))
}
if s.local != nil {
params.Set("local", fmt.Sprintf("%v", *s.local))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *IndicesGetSettingsService) Validate() error {
return nil
}
// Do executes the operation.
func (s *IndicesGetSettingsService) Do(ctx context.Context) (map[string]*IndicesGetSettingsResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
var ret map[string]*IndicesGetSettingsResponse
if err := s.client.decoder.Decode(res.Body, &ret); err != nil {
return nil, err
}
return ret, nil
}
// IndicesGetSettingsResponse is the response of IndicesGetSettingsService.Do.
type IndicesGetSettingsResponse struct {
Settings map[string]interface{} `json:"settings"`
}
================================================
FILE: indices_get_settings_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestIndexGetSettingsURL(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tests := []struct {
Indices []string
Names []string
Expected string
}{
{
[]string{},
[]string{},
"/_all/_settings",
},
{
[]string{},
[]string{"index.merge.*"},
"/_all/_settings/index.merge.%2A",
},
{
[]string{"twitter-*"},
[]string{"index.merge.*", "_settings"},
"/twitter-%2A/_settings/index.merge.%2A%2C_settings",
},
{
[]string{"store-1", "store-2"},
[]string{"index.merge.*", "_settings"},
"/store-1%2Cstore-2/_settings/index.merge.%2A%2C_settings",
},
}
for _, test := range tests {
path, _, err := client.IndexGetSettings().Index(test.Indices...).Name(test.Names...).buildURL()
if err != nil {
t.Fatal(err)
}
if path != test.Expected {
t.Errorf("expected %q; got: %q", test.Expected, path)
}
}
}
func TestIndexGetSettingsService(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
esversion, err := client.ElasticsearchVersion(DefaultURL)
if err != nil {
t.Fatal(err)
}
if esversion < "1.4.0" {
t.Skip("Index Get API is available since 1.4")
return
}
res, err := client.IndexGetSettings().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatalf("expected result; got: %v", res)
}
info, found := res[testIndexName]
if !found {
t.Fatalf("expected index %q to be found; got: %v", testIndexName, found)
}
if info == nil {
t.Fatalf("expected index %q to be != nil; got: %v", testIndexName, info)
}
if info.Settings == nil {
t.Fatalf("expected index settings of %q to be != nil; got: %v", testIndexName, info.Settings)
}
}
================================================
FILE: indices_get_template.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// IndicesGetTemplateService returns an index template (v1).
//
// Index templates have changed during in 7.8 update of Elasticsearch.
// This service implements the legacy version (7.7 or lower). If you want
// the new version, please use the IndicesGetIndexTemplateService.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.9/indices-get-template-v1.html
// for more details.
//
// Deprecated: Legacy index templates are deprecated in favor of composable templates.
type IndicesGetTemplateService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
name []string
flatSettings *bool
local *bool
}
// NewIndicesGetTemplateService creates a new IndicesGetTemplateService.
func NewIndicesGetTemplateService(client *Client) *IndicesGetTemplateService {
return &IndicesGetTemplateService{
client: client,
name: make([]string, 0),
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *IndicesGetTemplateService) Pretty(pretty bool) *IndicesGetTemplateService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *IndicesGetTemplateService) Human(human bool) *IndicesGetTemplateService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *IndicesGetTemplateService) ErrorTrace(errorTrace bool) *IndicesGetTemplateService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *IndicesGetTemplateService) FilterPath(filterPath ...string) *IndicesGetTemplateService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *IndicesGetTemplateService) Header(name string, value string) *IndicesGetTemplateService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *IndicesGetTemplateService) Headers(headers http.Header) *IndicesGetTemplateService {
s.headers = headers
return s
}
// Name is the name of the index template.
func (s *IndicesGetTemplateService) Name(name ...string) *IndicesGetTemplateService {
s.name = append(s.name, name...)
return s
}
// FlatSettings is returns settings in flat format (default: false).
func (s *IndicesGetTemplateService) FlatSettings(flatSettings bool) *IndicesGetTemplateService {
s.flatSettings = &flatSettings
return s
}
// Local indicates whether to return local information, i.e. do not retrieve
// the state from master node (default: false).
func (s *IndicesGetTemplateService) Local(local bool) *IndicesGetTemplateService {
s.local = &local
return s
}
// buildURL builds the URL for the operation.
func (s *IndicesGetTemplateService) buildURL() (string, url.Values, error) {
// Build URL
var err error
var path string
if len(s.name) > 0 {
path, err = uritemplates.Expand("/_template/{name}", map[string]string{
"name": strings.Join(s.name, ","),
})
} else {
path = "/_template"
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.flatSettings != nil {
params.Set("flat_settings", fmt.Sprintf("%v", *s.flatSettings))
}
if s.local != nil {
params.Set("local", fmt.Sprintf("%v", *s.local))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *IndicesGetTemplateService) Validate() error {
return nil
}
// Do executes the operation.
//
// Deprecated: Legacy index templates are deprecated in favor of composable templates.
func (s *IndicesGetTemplateService) Do(ctx context.Context) (map[string]*IndicesGetTemplateResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
var ret map[string]*IndicesGetTemplateResponse
if err := s.client.decoder.Decode(res.Body, &ret); err != nil {
return nil, err
}
return ret, nil
}
// IndicesGetTemplateResponse is the response of IndicesGetTemplateService.Do.
type IndicesGetTemplateResponse struct {
Order int `json:"order,omitempty"`
Version int `json:"version,omitempty"`
IndexPatterns []string `json:"index_patterns,omitempty"`
Settings map[string]interface{} `json:"settings,omitempty"`
Mappings map[string]interface{} `json:"mappings,omitempty"`
Aliases map[string]interface{} `json:"aliases,omitempty"`
}
================================================
FILE: indices_get_template_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestIndexGetTemplateURL(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tests := []struct {
Names []string
Expected string
}{
{
[]string{},
"/_template",
},
{
[]string{"index1"},
"/_template/index1",
},
{
[]string{"index1", "index2"},
"/_template/index1%2Cindex2",
},
}
for _, test := range tests {
path, _, err := client.IndexGetTemplate().Name(test.Names...).buildURL()
if err != nil {
t.Fatal(err)
}
if path != test.Expected {
t.Errorf("expected %q; got: %q", test.Expected, path)
}
}
}
func TestIndexGetTemplateService(t *testing.T) {
// client := setupTestClientAndCreateIndex(t, SetTraceLog(log.New(os.Stdout, "", 0)))
client := setupTestClientAndCreateIndex(t)
create := true
body := `
{
"index_patterns": ["te*"],
"settings": {
"index": {
"number_of_shards": 1
}
},
"mappings": {
"_source": {
"enabled": false
},
"properties": {
"host_name": {
"type": "keyword"
},
"created_at": {
"type": "date",
"format": "yyyy MM dd HH:mm:ss Z"
}
}
}
}
`
_, err := client.IndexPutTemplate("template_1").BodyString(body).Create(create).Pretty(true).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
defer client.IndexDeleteTemplate("template_1").Pretty(true).Do(context.TODO())
res, err := client.IndexGetTemplate("template_1").Pretty(true).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatalf("expected result; got: %v", res)
}
template := res["template_1"]
if template == nil {
t.Fatalf("expected template %q to be != nil; got: %v", "template_1", template)
}
if len(template.IndexPatterns) != 1 || template.IndexPatterns[0] != "te*" {
t.Fatalf("expected index settings of %q to be [\"index1\"]; got: %v", testIndexName, template.IndexPatterns)
}
}
================================================
FILE: indices_get_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestIndicesGetValidate(t *testing.T) {
client := setupTestClient(t)
// No index name -> fail with error
res, err := NewIndicesGetService(client).Index("").Do(context.TODO())
if err == nil {
t.Fatalf("expected IndicesGet to fail without index name")
}
if res != nil {
t.Fatalf("expected result to be == nil; got: %v", res)
}
}
func TestIndicesGetURL(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tests := []struct {
Indices []string
Features []string
Expected string
}{
{
[]string{},
[]string{},
"/_all",
},
{
[]string{},
[]string{"_mappings"},
"/_all/_mappings",
},
{
[]string{"twitter"},
[]string{"_mappings", "_settings"},
"/twitter/_mappings%2C_settings",
},
{
[]string{"store-1", "store-2"},
[]string{"_mappings", "_settings"},
"/store-1%2Cstore-2/_mappings%2C_settings",
},
}
for _, test := range tests {
path, _, err := NewIndicesGetService(client).Index(test.Indices...).Feature(test.Features...).buildURL()
if err != nil {
t.Fatal(err)
}
if path != test.Expected {
t.Errorf("expected %q; got: %q", test.Expected, path)
}
}
}
func TestIndicesGetService(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
esversion, err := client.ElasticsearchVersion(DefaultURL)
if err != nil {
t.Fatal(err)
}
if esversion < "1.4.0" {
t.Skip("Index Get API is available since 1.4")
return
}
res, err := client.IndexGet().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatalf("expected result; got: %v", res)
}
info, found := res[testIndexName]
if !found {
t.Fatalf("expected index %q to be found; got: %v", testIndexName, found)
}
if info == nil {
t.Fatalf("expected index %q to be != nil; got: %v", testIndexName, info)
}
if info.Mappings == nil {
t.Errorf("expected mappings to be != nil; got: %v", info.Mappings)
}
if info.Settings == nil {
t.Errorf("expected settings to be != nil; got: %v", info.Settings)
}
}
================================================
FILE: indices_index_templates_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestIndexTemplatesLifecycle(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
const templateName = "template_1"
// Always make sure the index template is deleted
defer func() {
_, _ = client.IndexDeleteIndexTemplate(templateName).Pretty(true).Do(context.Background())
}()
// Create an index template
{
resp, err := client.IndexPutIndexTemplate(templateName).Pretty(true).BodyString(`{
"index_patterns": ["elastic-index-templates-*"],
"priority": 1,
"template": {
"settings": {
"number_of_shards": 2,
"number_of_replicas": 0
},
"mappings": {
"_source": { "enabled": true }
}
}
}`).Do(context.Background())
if err != nil {
t.Fatalf("expected to successfully create index template, got %v", err)
}
if resp == nil {
t.Fatal("expected response on creating index template")
}
if want, have := true, resp.Acknowledged; want != have {
t.Errorf("expected Acknowledged=%v, got %v", want, have)
}
}
// Get the index template
{
resp, err := client.IndexGetIndexTemplate(templateName).Pretty(true).Do(context.Background())
if err != nil {
t.Fatalf("expected to successfully get index template, got %v", err)
}
if resp == nil {
t.Fatal("expected response on getting index template")
}
}
// Delete the index template
{
resp, err := client.IndexDeleteIndexTemplate(templateName).Pretty(true).Do(context.Background())
if err != nil {
t.Fatalf("expected to successfully delete index template, got %v", err)
}
if resp == nil {
t.Fatal("expected response on deleting index template")
}
}
}
================================================
FILE: indices_open.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// IndicesOpenService opens an index.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/indices-open-close.html
// for details.
type IndicesOpenService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
index string
timeout string
masterTimeout string
ignoreUnavailable *bool
allowNoIndices *bool
expandWildcards string
waitForActiveShards string
}
// NewIndicesOpenService creates and initializes a new IndicesOpenService.
func NewIndicesOpenService(client *Client) *IndicesOpenService {
return &IndicesOpenService{client: client}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *IndicesOpenService) Pretty(pretty bool) *IndicesOpenService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *IndicesOpenService) Human(human bool) *IndicesOpenService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *IndicesOpenService) ErrorTrace(errorTrace bool) *IndicesOpenService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *IndicesOpenService) FilterPath(filterPath ...string) *IndicesOpenService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *IndicesOpenService) Header(name string, value string) *IndicesOpenService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *IndicesOpenService) Headers(headers http.Header) *IndicesOpenService {
s.headers = headers
return s
}
// Index is the name of the index to open.
func (s *IndicesOpenService) Index(index string) *IndicesOpenService {
s.index = index
return s
}
// Timeout is an explicit operation timeout.
func (s *IndicesOpenService) Timeout(timeout string) *IndicesOpenService {
s.timeout = timeout
return s
}
// MasterTimeout specifies the timeout for connection to master.
func (s *IndicesOpenService) MasterTimeout(masterTimeout string) *IndicesOpenService {
s.masterTimeout = masterTimeout
return s
}
// IgnoreUnavailable indicates whether specified concrete indices should
// be ignored when unavailable (missing or closed).
func (s *IndicesOpenService) IgnoreUnavailable(ignoreUnavailable bool) *IndicesOpenService {
s.ignoreUnavailable = &ignoreUnavailable
return s
}
// AllowNoIndices indicates whether to ignore if a wildcard indices
// expression resolves into no concrete indices.
// (This includes `_all` string or when no indices have been specified).
func (s *IndicesOpenService) AllowNoIndices(allowNoIndices bool) *IndicesOpenService {
s.allowNoIndices = &allowNoIndices
return s
}
// ExpandWildcards indicates whether to expand wildcard expression to
// concrete indices that are open, closed or both..
func (s *IndicesOpenService) ExpandWildcards(expandWildcards string) *IndicesOpenService {
s.expandWildcards = expandWildcards
return s
}
// WaitForActiveShards specifies the number of shards that must be allocated
// before the Open operation returns. Valid values are "all" or an integer
// between 0 and number_of_replicas+1 (default: 0)
func (s *IndicesOpenService) WaitForActiveShards(waitForActiveShards string) *IndicesOpenService {
s.waitForActiveShards = waitForActiveShards
return s
}
// buildURL builds the URL for the operation.
func (s *IndicesOpenService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/{index}/_open", map[string]string{
"index": s.index,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.timeout != "" {
params.Set("timeout", s.timeout)
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
if s.ignoreUnavailable != nil {
params.Set("ignore_unavailable", fmt.Sprintf("%v", *s.ignoreUnavailable))
}
if s.allowNoIndices != nil {
params.Set("allow_no_indices", fmt.Sprintf("%v", *s.allowNoIndices))
}
if s.expandWildcards != "" {
params.Set("expand_wildcards", s.expandWildcards)
}
if s.waitForActiveShards != "" {
params.Set("wait_for_active_shards", s.waitForActiveShards)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *IndicesOpenService) Validate() error {
var invalid []string
if s.index == "" {
invalid = append(invalid, "Index")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *IndicesOpenService) Do(ctx context.Context) (*IndicesOpenResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "POST",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(IndicesOpenResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// IndicesOpenResponse is the response of IndicesOpenService.Do.
type IndicesOpenResponse struct {
Acknowledged bool `json:"acknowledged"`
ShardsAcknowledged bool `json:"shards_acknowledged"`
Index string `json:"index,omitempty"`
}
================================================
FILE: indices_open_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestIndicesOpenValidate(t *testing.T) {
client := setupTestClient(t)
// No index name -> fail with error
res, err := NewIndicesOpenService(client).Do(context.TODO())
if err == nil {
t.Fatalf("expected IndicesOpen to fail without index name")
}
if res != nil {
t.Fatalf("expected result to be == nil; got: %v", res)
}
}
================================================
FILE: indices_put_alias.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
)
// -- Actions --
// AliasAction is an action to apply to an alias, e.g. "add" or "remove".
type AliasAction interface {
Source() (interface{}, error)
}
// AliasAddAction is an action to add to an alias.
type AliasAddAction struct {
index []string // index name(s)
alias string // alias name
filter Query
routing string
searchRouting string
indexRouting string
isWriteIndex *bool
}
// NewAliasAddAction returns an action to add an alias.
func NewAliasAddAction(alias string) *AliasAddAction {
return &AliasAddAction{
alias: alias,
}
}
// Index associates one or more indices to the alias.
func (a *AliasAddAction) Index(index ...string) *AliasAddAction {
a.index = append(a.index, index...)
return a
}
func (a *AliasAddAction) removeBlankIndexNames() {
var indices []string
for _, index := range a.index {
if len(index) > 0 {
indices = append(indices, index)
}
}
a.index = indices
}
// Filter associates a filter to the alias.
func (a *AliasAddAction) Filter(filter Query) *AliasAddAction {
a.filter = filter
return a
}
// Routing associates a routing value to the alias.
// This basically sets index and search routing to the same value.
func (a *AliasAddAction) Routing(routing string) *AliasAddAction {
a.routing = routing
return a
}
// IndexRouting associates an index routing value to the alias.
func (a *AliasAddAction) IndexRouting(routing string) *AliasAddAction {
a.indexRouting = routing
return a
}
// SearchRouting associates a search routing value to the alias.
func (a *AliasAddAction) SearchRouting(routing ...string) *AliasAddAction {
a.searchRouting = strings.Join(routing, ",")
return a
}
// IsWriteIndex associates an is_write_index flag to the alias.
func (a *AliasAddAction) IsWriteIndex(flag bool) *AliasAddAction {
a.isWriteIndex = &flag
return a
}
// Validate checks if the operation is valid.
func (a *AliasAddAction) Validate() error {
var invalid []string
if len(a.alias) == 0 {
invalid = append(invalid, "Alias")
}
if len(a.index) == 0 {
invalid = append(invalid, "Index")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
if a.isWriteIndex != nil && len(a.index) > 1 {
return fmt.Errorf("more than 1 target index specified in operation with 'is_write_index' flag present")
}
return nil
}
// Source returns the JSON-serializable data.
func (a *AliasAddAction) Source() (interface{}, error) {
a.removeBlankIndexNames()
if err := a.Validate(); err != nil {
return nil, err
}
src := make(map[string]interface{})
act := make(map[string]interface{})
src["add"] = act
act["alias"] = a.alias
switch len(a.index) {
case 1:
act["index"] = a.index[0]
default:
act["indices"] = a.index
}
if a.filter != nil {
f, err := a.filter.Source()
if err != nil {
return nil, err
}
act["filter"] = f
}
if len(a.routing) > 0 {
act["routing"] = a.routing
}
if len(a.indexRouting) > 0 {
act["index_routing"] = a.indexRouting
}
if len(a.searchRouting) > 0 {
act["search_routing"] = a.searchRouting
}
if a.isWriteIndex != nil {
act["is_write_index"] = *a.isWriteIndex
}
return src, nil
}
// AliasRemoveAction is an action to remove an alias.
type AliasRemoveAction struct {
index []string // index name(s)
alias string // alias name
}
// NewAliasRemoveAction returns an action to remove an alias.
func NewAliasRemoveAction(alias string) *AliasRemoveAction {
return &AliasRemoveAction{
alias: alias,
}
}
// Index associates one or more indices to the alias.
func (a *AliasRemoveAction) Index(index ...string) *AliasRemoveAction {
a.index = append(a.index, index...)
return a
}
func (a *AliasRemoveAction) removeBlankIndexNames() {
var indices []string
for _, index := range a.index {
if len(index) > 0 {
indices = append(indices, index)
}
}
a.index = indices
}
// Validate checks if the operation is valid.
func (a *AliasRemoveAction) Validate() error {
var invalid []string
if len(a.alias) == 0 {
invalid = append(invalid, "Alias")
}
if len(a.index) == 0 {
invalid = append(invalid, "Index")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Source returns the JSON-serializable data.
func (a *AliasRemoveAction) Source() (interface{}, error) {
a.removeBlankIndexNames()
if err := a.Validate(); err != nil {
return nil, err
}
src := make(map[string]interface{})
act := make(map[string]interface{})
src["remove"] = act
act["alias"] = a.alias
switch len(a.index) {
case 1:
act["index"] = a.index[0]
default:
act["indices"] = a.index
}
return src, nil
}
// AliasRemoveIndexAction is an action to remove an index during an alias
// operation.
type AliasRemoveIndexAction struct {
index string // index name
}
// NewAliasRemoveIndexAction returns an action to remove an index.
func NewAliasRemoveIndexAction(index string) *AliasRemoveIndexAction {
return &AliasRemoveIndexAction{
index: index,
}
}
// Validate checks if the operation is valid.
func (a *AliasRemoveIndexAction) Validate() error {
if a.index == "" {
return fmt.Errorf("missing required field: index")
}
return nil
}
// Source returns the JSON-serializable data.
func (a *AliasRemoveIndexAction) Source() (interface{}, error) {
if err := a.Validate(); err != nil {
return nil, err
}
src := make(map[string]interface{})
act := make(map[string]interface{})
src["remove_index"] = act
act["index"] = a.index
return src, nil
}
// -- Service --
// AliasService enables users to add or remove an alias.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/indices-aliases.html
// for details.
type AliasService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
actions []AliasAction
}
// NewAliasService implements a service to manage aliases.
func NewAliasService(client *Client) *AliasService {
builder := &AliasService{
client: client,
}
return builder
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *AliasService) Pretty(pretty bool) *AliasService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *AliasService) Human(human bool) *AliasService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *AliasService) ErrorTrace(errorTrace bool) *AliasService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *AliasService) FilterPath(filterPath ...string) *AliasService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *AliasService) Header(name string, value string) *AliasService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *AliasService) Headers(headers http.Header) *AliasService {
s.headers = headers
return s
}
// Add adds an alias to an index.
func (s *AliasService) Add(indexName string, aliasName string) *AliasService {
action := NewAliasAddAction(aliasName).Index(indexName)
s.actions = append(s.actions, action)
return s
}
// Add adds an alias to an index and associates a filter to the alias.
func (s *AliasService) AddWithFilter(indexName string, aliasName string, filter Query) *AliasService {
action := NewAliasAddAction(aliasName).Index(indexName).Filter(filter)
s.actions = append(s.actions, action)
return s
}
// Remove removes an alias.
func (s *AliasService) Remove(indexName string, aliasName string) *AliasService {
action := NewAliasRemoveAction(aliasName).Index(indexName)
s.actions = append(s.actions, action)
return s
}
// Action accepts one or more AliasAction instances which can be
// of type AliasAddAction or AliasRemoveAction.
func (s *AliasService) Action(action ...AliasAction) *AliasService {
s.actions = append(s.actions, action...)
return s
}
// buildURL builds the URL for the operation.
func (s *AliasService) buildURL() (string, url.Values, error) {
path := "/_aliases"
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
return path, params, nil
}
// Do executes the command.
func (s *AliasService) Do(ctx context.Context) (*AliasResult, error) {
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Body with actions
body := make(map[string]interface{})
var actions []interface{}
for _, action := range s.actions {
src, err := action.Source()
if err != nil {
return nil, err
}
actions = append(actions, src)
}
body["actions"] = actions
// Get response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "POST",
Path: path,
Params: params,
Body: body,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return results
ret := new(AliasResult)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// -- Result of an alias request.
// AliasResult is the outcome of calling Do on AliasService.
type AliasResult struct {
Acknowledged bool `json:"acknowledged"`
ShardsAcknowledged bool `json:"shards_acknowledged"`
Index string `json:"index,omitempty"`
}
================================================
FILE: indices_put_alias_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"testing"
)
const (
testAliasName = "elastic-test-alias"
testAliasName2 = "elastic-test-alias2"
)
func TestAliasLifecycle(t *testing.T) {
var err error
client := setupTestClientAndCreateIndex(t)
// Some tweets
tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
tweet2 := tweet{User: "sandrae", Message: "Cycling is fun."}
tweet3 := tweet{User: "olivere", Message: "Another unrelated topic."}
// Add tweets to first index
_, err = client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Add tweets to second index
_, err = client.Index().Index(testIndexName2).Id("3").BodyJson(&tweet3).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Refresh
_, err = client.Refresh().Index(testIndexName, testIndexName2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Add both indices to a new alias
aliasCreate, err := client.Alias().
Add(testIndexName, testAliasName).
Action(NewAliasAddAction(testAliasName).Index(testIndexName2)).
//Pretty(true).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if !aliasCreate.Acknowledged {
t.Errorf("expected AliasResult.Acknowledged %v; got %v", true, aliasCreate.Acknowledged)
}
// Search should return all 3 tweets
matchAll := NewMatchAllQuery()
searchResult1, err := client.Search().Index(testAliasName).Query(matchAll).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult1.Hits == nil {
t.Errorf("expected SearchResult.Hits != nil; got nil")
}
if searchResult1.TotalHits() != 3 {
t.Errorf("expected SearchResult.TotalHits() = %d; got %d", 3, searchResult1.TotalHits())
}
// Remove first index should remove two tweets, so should only yield 1
aliasRemove1, err := client.Alias().
Remove(testIndexName, testAliasName).
//Pretty(true).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if !aliasRemove1.Acknowledged {
t.Errorf("expected AliasResult.Acknowledged %v; got %v", true, aliasRemove1.Acknowledged)
}
searchResult2, err := client.Search().Index(testAliasName).Query(matchAll).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult2.Hits == nil {
t.Errorf("expected SearchResult.Hits != nil; got nil")
}
if searchResult2.TotalHits() != 1 {
t.Errorf("expected SearchResult.TotalHits() = %d; got %d", 1, searchResult2.TotalHits())
}
// Add second index back to alias as write index
aliasCreate, err = client.Alias().
Action(NewAliasAddAction(testAliasName).Index(testIndexName).IsWriteIndex(false)).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if !aliasCreate.Acknowledged {
t.Errorf("expected AliasResult.Acknowledged %v; got %v", true, aliasCreate.Acknowledged)
}
aliasCreate, err = client.Alias().
Action(NewAliasAddAction(testAliasName).Index(testIndexName2).IsWriteIndex(true)).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if !aliasCreate.Acknowledged {
t.Errorf("expected AliasResult.Acknowledged %v; got %v", true, aliasCreate.Acknowledged)
}
_, err = client.Aliases().Do(context.TODO())
if err != nil {
t.Fatal(err)
}
tweet4 := tweet{User: "chris", Message: "Foo bar baz."}
_, err = client.Index().Index(testAliasName).Id("4").BodyJson(&tweet4).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testIndexName, testIndexName2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
searchResult3, err := client.Search().Index(testIndexName2).Query(NewIdsQuery().Ids("4")).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult3.Hits == nil {
t.Errorf("expected SearchResult.Hits != nil; got nil")
}
if searchResult3.TotalHits() != 1 {
t.Errorf("expected SearchResult.TotalHits() = %d; got %d", 1, searchResult3.TotalHits())
}
}
func TestAliasAddAction(t *testing.T) {
var tests = []struct {
Action *AliasAddAction
Expected string
Invalid bool
}{
{
Action: NewAliasAddAction("").Index(""),
Invalid: true,
},
{
Action: NewAliasAddAction("alias1").Index(""),
Invalid: true,
},
{
Action: NewAliasAddAction("").Index("index1"),
Invalid: true,
},
{
Action: NewAliasAddAction("alias1").Index("index1"),
Expected: `{"add":{"alias":"alias1","index":"index1"}}`,
},
{
Action: NewAliasAddAction("alias1").Index("index1", "index2"),
Expected: `{"add":{"alias":"alias1","indices":["index1","index2"]}}`,
},
{
Action: NewAliasAddAction("alias1").Index("index1").Routing("routing1"),
Expected: `{"add":{"alias":"alias1","index":"index1","routing":"routing1"}}`,
},
{
Action: NewAliasAddAction("alias1").Index("index1").Routing("routing1").IndexRouting("indexRouting1"),
Expected: `{"add":{"alias":"alias1","index":"index1","index_routing":"indexRouting1","routing":"routing1"}}`,
},
{
Action: NewAliasAddAction("alias1").Index("index1").Routing("routing1").SearchRouting("searchRouting1"),
Expected: `{"add":{"alias":"alias1","index":"index1","routing":"routing1","search_routing":"searchRouting1"}}`,
},
{
Action: NewAliasAddAction("alias1").Index("index1").Routing("routing1").SearchRouting("searchRouting1", "searchRouting2"),
Expected: `{"add":{"alias":"alias1","index":"index1","routing":"routing1","search_routing":"searchRouting1,searchRouting2"}}`,
},
{
Action: NewAliasAddAction("alias1").Index("index1").Filter(NewTermQuery("user", "olivere")),
Expected: `{"add":{"alias":"alias1","filter":{"term":{"user":"olivere"}},"index":"index1"}}`,
},
}
for i, tt := range tests {
src, err := tt.Action.Source()
if err != nil {
if !tt.Invalid {
t.Errorf("#%d: expected to succeed", i)
}
} else {
if tt.Invalid {
t.Errorf("#%d: expected to fail", i)
} else {
dst, err := json.Marshal(src)
if err != nil {
t.Fatal(err)
}
if want, have := tt.Expected, string(dst); want != have {
t.Errorf("#%d: expected %s, got %s", i, want, have)
}
}
}
}
}
func TestAliasRemoveAction(t *testing.T) {
var tests = []struct {
Action *AliasRemoveAction
Expected string
Invalid bool
}{
{
Action: NewAliasRemoveAction(""),
Invalid: true,
},
{
Action: NewAliasRemoveAction("alias1"),
Invalid: true,
},
{
Action: NewAliasRemoveAction("").Index("index1"),
Invalid: true,
},
{
Action: NewAliasRemoveAction("alias1").Index("index1"),
Expected: `{"remove":{"alias":"alias1","index":"index1"}}`,
},
{
Action: NewAliasRemoveAction("alias1").Index("index1", "index2"),
Expected: `{"remove":{"alias":"alias1","indices":["index1","index2"]}}`,
},
}
for i, tt := range tests {
src, err := tt.Action.Source()
if err != nil {
if !tt.Invalid {
t.Errorf("#%d: expected to succeed", i)
}
} else {
if tt.Invalid {
t.Errorf("#%d: expected to fail", i)
} else {
dst, err := json.Marshal(src)
if err != nil {
t.Fatal(err)
}
if want, have := tt.Expected, string(dst); want != have {
t.Errorf("#%d: expected %s, got %s", i, want, have)
}
}
}
}
}
func TestAliasRemoveIndexAction(t *testing.T) {
var tests = []struct {
Action *AliasRemoveIndexAction
Expected string
Invalid bool
}{
{
Action: NewAliasRemoveIndexAction(""),
Invalid: true,
},
{
Action: NewAliasRemoveIndexAction("index1"),
Expected: `{"remove_index":{"index":"index1"}}`,
},
}
for i, tt := range tests {
src, err := tt.Action.Source()
if err != nil {
if !tt.Invalid {
t.Errorf("#%d: expected to succeed", i)
}
} else {
if tt.Invalid {
t.Errorf("#%d: expected to fail", i)
} else {
dst, err := json.Marshal(src)
if err != nil {
t.Fatal(err)
}
if want, have := tt.Expected, string(dst); want != have {
t.Errorf("#%d: expected %s, got %s", i, want, have)
}
}
}
}
}
================================================
FILE: indices_put_component_template.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// IndicesPutComponentTemplateService creates or updates component templates.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.10/indices-component-template.html
// for more details on this API.
type IndicesPutComponentTemplateService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
name string
create *bool
cause string
masterTimeout string
bodyJson interface{}
bodyString string
}
// NewIndicesPutComponentTemplateService creates a new IndicesPutComponentTemplateService.
func NewIndicesPutComponentTemplateService(client *Client) *IndicesPutComponentTemplateService {
return &IndicesPutComponentTemplateService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *IndicesPutComponentTemplateService) Pretty(pretty bool) *IndicesPutComponentTemplateService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *IndicesPutComponentTemplateService) Human(human bool) *IndicesPutComponentTemplateService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *IndicesPutComponentTemplateService) ErrorTrace(errorTrace bool) *IndicesPutComponentTemplateService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *IndicesPutComponentTemplateService) FilterPath(filterPath ...string) *IndicesPutComponentTemplateService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *IndicesPutComponentTemplateService) Header(name string, value string) *IndicesPutComponentTemplateService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *IndicesPutComponentTemplateService) Headers(headers http.Header) *IndicesPutComponentTemplateService {
s.headers = headers
return s
}
// Name is the name of the component template.
func (s *IndicesPutComponentTemplateService) Name(name string) *IndicesPutComponentTemplateService {
s.name = name
return s
}
// Create indicates whether the component template should only be added if
// new or can also replace an existing one.
func (s *IndicesPutComponentTemplateService) Create(create bool) *IndicesPutComponentTemplateService {
s.create = &create
return s
}
// Cause is the user-defined reason for creating/updating the the component template.
func (s *IndicesPutComponentTemplateService) Cause(cause string) *IndicesPutComponentTemplateService {
s.cause = cause
return s
}
// MasterTimeout specifies the timeout for connection to master.
func (s *IndicesPutComponentTemplateService) MasterTimeout(masterTimeout string) *IndicesPutComponentTemplateService {
s.masterTimeout = masterTimeout
return s
}
// BodyJson is the component template definition as a JSON serializable
// type, e.g. map[string]interface{}.
func (s *IndicesPutComponentTemplateService) BodyJson(body interface{}) *IndicesPutComponentTemplateService {
s.bodyJson = body
return s
}
// BodyString is the component template definition as a raw string.
func (s *IndicesPutComponentTemplateService) BodyString(body string) *IndicesPutComponentTemplateService {
s.bodyString = body
return s
}
// buildURL builds the URL for the operation.
func (s *IndicesPutComponentTemplateService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/_component_template/{name}", map[string]string{
"name": s.name,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.create != nil {
params.Set("create", fmt.Sprint(*s.create))
}
if s.cause != "" {
params.Set("cause", s.cause)
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *IndicesPutComponentTemplateService) Validate() error {
var invalid []string
if s.name == "" {
invalid = append(invalid, "Name")
}
if s.bodyString == "" && s.bodyJson == nil {
invalid = append(invalid, "BodyJson")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *IndicesPutComponentTemplateService) Do(ctx context.Context) (*IndicesPutComponentTemplateResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Setup HTTP request body
var body interface{}
if s.bodyJson != nil {
body = s.bodyJson
} else {
body = s.bodyString
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "PUT",
Path: path,
Params: params,
Body: body,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(IndicesPutComponentTemplateResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// IndicesPutComponentTemplateResponse is the response of IndicesPutComponentTemplateService.Do.
type IndicesPutComponentTemplateResponse struct {
Acknowledged bool `json:"acknowledged"`
ShardsAcknowledged bool `json:"shards_acknowledged"`
Index string `json:"index,omitempty"`
}
================================================
FILE: indices_put_index_template.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// IndicesPutIndexTemplateService creates or updates index templates.
//
// Index templates have changed during in 7.8 update of Elasticsearch.
// This service implements the new version (7.8 or higher) for managing
// index templates. If you want the v1/legacy version, please see e.g.
// IndicesPutTemplateService and friends.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.9/indices-put-template.html
// for more details on this API.
type IndicesPutIndexTemplateService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
name string
create *bool
cause string
masterTimeout string
bodyJson interface{}
bodyString string
}
// NewIndicesPutIndexTemplateService creates a new IndicesPutIndexTemplateService.
func NewIndicesPutIndexTemplateService(client *Client) *IndicesPutIndexTemplateService {
return &IndicesPutIndexTemplateService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *IndicesPutIndexTemplateService) Pretty(pretty bool) *IndicesPutIndexTemplateService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *IndicesPutIndexTemplateService) Human(human bool) *IndicesPutIndexTemplateService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *IndicesPutIndexTemplateService) ErrorTrace(errorTrace bool) *IndicesPutIndexTemplateService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *IndicesPutIndexTemplateService) FilterPath(filterPath ...string) *IndicesPutIndexTemplateService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *IndicesPutIndexTemplateService) Header(name string, value string) *IndicesPutIndexTemplateService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *IndicesPutIndexTemplateService) Headers(headers http.Header) *IndicesPutIndexTemplateService {
s.headers = headers
return s
}
// Name is the name of the index template.
func (s *IndicesPutIndexTemplateService) Name(name string) *IndicesPutIndexTemplateService {
s.name = name
return s
}
// Create indicates whether the index template should only be added if
// new or can also replace an existing one.
func (s *IndicesPutIndexTemplateService) Create(create bool) *IndicesPutIndexTemplateService {
s.create = &create
return s
}
// Cause is the user-defined reason for creating/updating the the index template.
func (s *IndicesPutIndexTemplateService) Cause(cause string) *IndicesPutIndexTemplateService {
s.cause = cause
return s
}
// MasterTimeout specifies the timeout for connection to master.
func (s *IndicesPutIndexTemplateService) MasterTimeout(masterTimeout string) *IndicesPutIndexTemplateService {
s.masterTimeout = masterTimeout
return s
}
// BodyJson is the index template definition as a JSON serializable
// type, e.g. map[string]interface{}.
func (s *IndicesPutIndexTemplateService) BodyJson(body interface{}) *IndicesPutIndexTemplateService {
s.bodyJson = body
return s
}
// BodyString is the index template definition as a raw string.
func (s *IndicesPutIndexTemplateService) BodyString(body string) *IndicesPutIndexTemplateService {
s.bodyString = body
return s
}
// buildURL builds the URL for the operation.
func (s *IndicesPutIndexTemplateService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/_index_template/{name}", map[string]string{
"name": s.name,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.create != nil {
params.Set("create", fmt.Sprint(*s.create))
}
if s.cause != "" {
params.Set("cause", s.cause)
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *IndicesPutIndexTemplateService) Validate() error {
var invalid []string
if s.name == "" {
invalid = append(invalid, "Name")
}
if s.bodyString == "" && s.bodyJson == nil {
invalid = append(invalid, "BodyJson")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *IndicesPutIndexTemplateService) Do(ctx context.Context) (*IndicesPutIndexTemplateResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Setup HTTP request body
var body interface{}
if s.bodyJson != nil {
body = s.bodyJson
} else {
body = s.bodyString
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "PUT",
Path: path,
Params: params,
Body: body,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(IndicesPutIndexTemplateResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// IndicesPutIndexTemplateResponse is the response of IndicesPutIndexTemplateService.Do.
type IndicesPutIndexTemplateResponse struct {
Acknowledged bool `json:"acknowledged"`
ShardsAcknowledged bool `json:"shards_acknowledged"`
Index string `json:"index,omitempty"`
}
================================================
FILE: indices_put_mapping.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// IndicesPutMappingService allows to register specific mapping definition
// for a specific type.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/indices-put-mapping.html
// for details.
type IndicesPutMappingService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
index []string
masterTimeout string
ignoreUnavailable *bool
allowNoIndices *bool
expandWildcards string
includeTypeName *bool
writeIndexOnly *bool
timeout string
bodyJson map[string]interface{}
bodyString string
}
// NewPutMappingService is an alias for NewIndicesPutMappingService.
// Use NewIndicesPutMappingService.
func NewPutMappingService(client *Client) *IndicesPutMappingService {
return NewIndicesPutMappingService(client)
}
// NewIndicesPutMappingService creates a new IndicesPutMappingService.
func NewIndicesPutMappingService(client *Client) *IndicesPutMappingService {
return &IndicesPutMappingService{
client: client,
index: make([]string, 0),
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *IndicesPutMappingService) Pretty(pretty bool) *IndicesPutMappingService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *IndicesPutMappingService) Human(human bool) *IndicesPutMappingService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *IndicesPutMappingService) ErrorTrace(errorTrace bool) *IndicesPutMappingService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *IndicesPutMappingService) FilterPath(filterPath ...string) *IndicesPutMappingService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *IndicesPutMappingService) Header(name string, value string) *IndicesPutMappingService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *IndicesPutMappingService) Headers(headers http.Header) *IndicesPutMappingService {
s.headers = headers
return s
}
// Index is a list of index names the mapping should be added to
// (supports wildcards); use `_all` or omit to add the mapping on all indices.
func (s *IndicesPutMappingService) Index(indices ...string) *IndicesPutMappingService {
s.index = append(s.index, indices...)
return s
}
// Timeout is an explicit operation timeout.
func (s *IndicesPutMappingService) Timeout(timeout string) *IndicesPutMappingService {
s.timeout = timeout
return s
}
// MasterTimeout specifies the timeout for connection to master.
func (s *IndicesPutMappingService) MasterTimeout(masterTimeout string) *IndicesPutMappingService {
s.masterTimeout = masterTimeout
return s
}
// IgnoreUnavailable indicates whether specified concrete indices should be
// ignored when unavailable (missing or closed).
func (s *IndicesPutMappingService) IgnoreUnavailable(ignoreUnavailable bool) *IndicesPutMappingService {
s.ignoreUnavailable = &ignoreUnavailable
return s
}
// AllowNoIndices indicates whether to ignore if a wildcard indices
// expression resolves into no concrete indices.
// This includes `_all` string or when no indices have been specified.
func (s *IndicesPutMappingService) AllowNoIndices(allowNoIndices bool) *IndicesPutMappingService {
s.allowNoIndices = &allowNoIndices
return s
}
// ExpandWildcards indicates whether to expand wildcard expression to
// concrete indices that are open, closed or both.
func (s *IndicesPutMappingService) ExpandWildcards(expandWildcards string) *IndicesPutMappingService {
s.expandWildcards = expandWildcards
return s
}
// IncludeTypeName indicates whether a type should be expected in the body of the mappings.
func (s *IndicesPutMappingService) IncludeTypeName(includeTypeName bool) *IndicesPutMappingService {
s.includeTypeName = &includeTypeName
return s
}
// WriteIndexOnly, when true, applies mappings only to the write index of an alias or data stream.
func (s *IndicesPutMappingService) WriteIndexOnly(writeIndexOnly bool) *IndicesPutMappingService {
s.writeIndexOnly = &writeIndexOnly
return s
}
// BodyJson contains the mapping definition.
func (s *IndicesPutMappingService) BodyJson(mapping map[string]interface{}) *IndicesPutMappingService {
s.bodyJson = mapping
return s
}
// BodyString is the mapping definition serialized as a string.
func (s *IndicesPutMappingService) BodyString(mapping string) *IndicesPutMappingService {
s.bodyString = mapping
return s
}
// buildURL builds the URL for the operation.
func (s *IndicesPutMappingService) buildURL() (string, url.Values, error) {
path, err := uritemplates.Expand("/{index}/_mapping", map[string]string{
"index": strings.Join(s.index, ","),
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.ignoreUnavailable != nil {
params.Set("ignore_unavailable", fmt.Sprint(*s.ignoreUnavailable))
}
if s.allowNoIndices != nil {
params.Set("allow_no_indices", fmt.Sprint(*s.allowNoIndices))
}
if s.expandWildcards != "" {
params.Set("expand_wildcards", s.expandWildcards)
}
if s.includeTypeName != nil {
params.Set("include_type_name", fmt.Sprint(*s.includeTypeName))
}
if s.writeIndexOnly != nil {
params.Set("write_index_only", fmt.Sprint(*s.writeIndexOnly))
}
if s.timeout != "" {
params.Set("timeout", s.timeout)
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *IndicesPutMappingService) Validate() error {
var invalid []string
if len(s.index) == 0 {
invalid = append(invalid, "Index")
}
if s.bodyString == "" && s.bodyJson == nil {
invalid = append(invalid, "BodyJson")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *IndicesPutMappingService) Do(ctx context.Context) (*PutMappingResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Setup HTTP request body
var body interface{}
if s.bodyJson != nil {
body = s.bodyJson
} else {
body = s.bodyString
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "PUT",
Path: path,
Params: params,
Body: body,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(PutMappingResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// PutMappingResponse is the response of IndicesPutMappingService.Do.
type PutMappingResponse struct {
Acknowledged bool `json:"acknowledged"`
ShardsAcknowledged bool `json:"shards_acknowledged"`
Index string `json:"index,omitempty"`
}
================================================
FILE: indices_put_mapping_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestPutMappingURL(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tests := []struct {
Indices []string
Expected string
}{
{
[]string{"*"},
"/%2A/_mapping",
},
{
[]string{"store-1", "store-2"},
"/store-1%2Cstore-2/_mapping",
},
}
for _, test := range tests {
path, _, err := client.PutMapping().Index(test.Indices...).buildURL()
if err != nil {
t.Fatal(err)
}
if path != test.Expected {
t.Errorf("expected %q; got: %q", test.Expected, path)
}
}
}
func TestMappingLifecycle(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
//client := setupTestClientAndCreateIndexAndLog(t)
// Create index
createIndex, err := client.CreateIndex(testIndexName3).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if createIndex == nil {
t.Errorf("expected result to be != nil; got: %v", createIndex)
}
mapping := `{
"properties":{
"field":{
"type":"keyword"
}
}
}`
putresp, err := client.PutMapping().Index(testIndexName3).BodyString(mapping).Do(context.TODO())
if err != nil {
t.Fatalf("expected put mapping to succeed; got: %v", err)
}
if putresp == nil {
t.Fatalf("expected put mapping response; got: %v", putresp)
}
if !putresp.Acknowledged {
t.Fatalf("expected put mapping ack; got: %v", putresp.Acknowledged)
}
getresp, err := client.GetMapping().Index(testIndexName3).Do(context.TODO())
if err != nil {
t.Fatalf("expected get mapping to succeed; got: %v", err)
}
if getresp == nil {
t.Fatalf("expected get mapping response; got: %v", getresp)
}
props, ok := getresp[testIndexName3]
if !ok {
t.Fatalf("expected JSON root to be of type map[string]interface{}; got: %#v", props)
}
// NOTE There is no Delete Mapping API in Elasticsearch 2.0
}
================================================
FILE: indices_put_settings.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// IndicesPutSettingsService changes specific index level settings in
// real time.
//
// See the documentation at
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/indices-update-settings.html.
type IndicesPutSettingsService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
index []string
allowNoIndices *bool
expandWildcards string
flatSettings *bool
ignoreUnavailable *bool
masterTimeout string
bodyJson interface{}
bodyString string
}
// NewIndicesPutSettingsService creates a new IndicesPutSettingsService.
func NewIndicesPutSettingsService(client *Client) *IndicesPutSettingsService {
return &IndicesPutSettingsService{
client: client,
index: make([]string, 0),
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *IndicesPutSettingsService) Pretty(pretty bool) *IndicesPutSettingsService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *IndicesPutSettingsService) Human(human bool) *IndicesPutSettingsService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *IndicesPutSettingsService) ErrorTrace(errorTrace bool) *IndicesPutSettingsService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *IndicesPutSettingsService) FilterPath(filterPath ...string) *IndicesPutSettingsService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *IndicesPutSettingsService) Header(name string, value string) *IndicesPutSettingsService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *IndicesPutSettingsService) Headers(headers http.Header) *IndicesPutSettingsService {
s.headers = headers
return s
}
// Index is a list of index names the mapping should be added to
// (supports wildcards); use `_all` or omit to add the mapping on all indices.
func (s *IndicesPutSettingsService) Index(indices ...string) *IndicesPutSettingsService {
s.index = append(s.index, indices...)
return s
}
// AllowNoIndices indicates whether to ignore if a wildcard indices
// expression resolves into no concrete indices. (This includes `_all`
// string or when no indices have been specified).
func (s *IndicesPutSettingsService) AllowNoIndices(allowNoIndices bool) *IndicesPutSettingsService {
s.allowNoIndices = &allowNoIndices
return s
}
// ExpandWildcards specifies whether to expand wildcard expression to
// concrete indices that are open, closed or both.
func (s *IndicesPutSettingsService) ExpandWildcards(expandWildcards string) *IndicesPutSettingsService {
s.expandWildcards = expandWildcards
return s
}
// FlatSettings indicates whether to return settings in flat format (default: false).
func (s *IndicesPutSettingsService) FlatSettings(flatSettings bool) *IndicesPutSettingsService {
s.flatSettings = &flatSettings
return s
}
// IgnoreUnavailable specifies whether specified concrete indices should be
// ignored when unavailable (missing or closed).
func (s *IndicesPutSettingsService) IgnoreUnavailable(ignoreUnavailable bool) *IndicesPutSettingsService {
s.ignoreUnavailable = &ignoreUnavailable
return s
}
// MasterTimeout is the timeout for connection to master.
func (s *IndicesPutSettingsService) MasterTimeout(masterTimeout string) *IndicesPutSettingsService {
s.masterTimeout = masterTimeout
return s
}
// BodyJson is documented as: The index settings to be updated.
func (s *IndicesPutSettingsService) BodyJson(body interface{}) *IndicesPutSettingsService {
s.bodyJson = body
return s
}
// BodyString is documented as: The index settings to be updated.
func (s *IndicesPutSettingsService) BodyString(body string) *IndicesPutSettingsService {
s.bodyString = body
return s
}
// buildURL builds the URL for the operation.
func (s *IndicesPutSettingsService) buildURL() (string, url.Values, error) {
// Build URL
var err error
var path string
if len(s.index) > 0 {
path, err = uritemplates.Expand("/{index}/_settings", map[string]string{
"index": strings.Join(s.index, ","),
})
} else {
path = "/_settings"
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.allowNoIndices != nil {
params.Set("allow_no_indices", fmt.Sprintf("%v", *s.allowNoIndices))
}
if s.expandWildcards != "" {
params.Set("expand_wildcards", s.expandWildcards)
}
if s.flatSettings != nil {
params.Set("flat_settings", fmt.Sprintf("%v", *s.flatSettings))
}
if s.ignoreUnavailable != nil {
params.Set("ignore_unavailable", fmt.Sprintf("%v", *s.ignoreUnavailable))
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *IndicesPutSettingsService) Validate() error {
return nil
}
// Do executes the operation.
func (s *IndicesPutSettingsService) Do(ctx context.Context) (*IndicesPutSettingsResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Setup HTTP request body
var body interface{}
if s.bodyJson != nil {
body = s.bodyJson
} else {
body = s.bodyString
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "PUT",
Path: path,
Params: params,
Body: body,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(IndicesPutSettingsResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// IndicesPutSettingsResponse is the response of IndicesPutSettingsService.Do.
type IndicesPutSettingsResponse struct {
Acknowledged bool `json:"acknowledged"`
ShardsAcknowledged bool `json:"shards_acknowledged"`
Index string `json:"index,omitempty"`
}
================================================
FILE: indices_put_settings_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestIndicesPutSettingsBuildURL(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tests := []struct {
Indices []string
Expected string
}{
{
[]string{},
"/_settings",
},
{
[]string{"*"},
"/%2A/_settings",
},
{
[]string{"store-1", "store-2"},
"/store-1%2Cstore-2/_settings",
},
}
for _, test := range tests {
path, _, err := client.IndexPutSettings().Index(test.Indices...).buildURL()
if err != nil {
t.Fatal(err)
}
if path != test.Expected {
t.Errorf("expected %q; got: %q", test.Expected, path)
}
}
}
func TestIndicesSettingsLifecycle(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
body := `{
"index":{
"refresh_interval":"-1"
}
}`
// Put settings
putres, err := client.IndexPutSettings().Index(testIndexName).BodyString(body).Do(context.TODO())
if err != nil {
t.Fatalf("expected put settings to succeed; got: %v", err)
}
if putres == nil {
t.Fatalf("expected put settings response; got: %v", putres)
}
if !putres.Acknowledged {
t.Fatalf("expected put settings ack; got: %v", putres.Acknowledged)
}
// Read settings
getres, err := client.IndexGetSettings().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatalf("expected get mapping to succeed; got: %v", err)
}
if getres == nil {
t.Fatalf("expected get mapping response; got: %v", getres)
}
// Check settings
index, found := getres[testIndexName]
if !found {
t.Fatalf("expected to return settings for index %q; got: %#v", testIndexName, getres)
}
// Retrieve "index" section of the settings for index testIndexName
sectionIntf, ok := index.Settings["index"]
if !ok {
t.Fatalf("expected settings to have %q field; got: %#v", "index", getres)
}
section, ok := sectionIntf.(map[string]interface{})
if !ok {
t.Fatalf("expected settings to be of type map[string]interface{}; got: %#v", getres)
}
refintv, ok := section["refresh_interval"]
if !ok {
t.Fatalf(`expected JSON to include "refresh_interval" field; got: %#v`, getres)
}
if got, want := refintv, "-1"; got != want {
t.Fatalf("expected refresh_interval = %v; got: %v", want, got)
}
}
================================================
FILE: indices_put_template.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// IndicesPutTemplateService creates or updates templates.
//
// Index templates have changed during in 7.8 update of Elasticsearch.
// This service implements the legacy version (7.7 or lower). If you want
// the new version, please use the IndicesPutIndexTemplateService.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.9/indices-templates-v1.html
// for more details.
//
// Deprecated: Legacy index templates are deprecated in favor of composable templates.
type IndicesPutTemplateService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
name string
cause string
order interface{}
version *int
create *bool
timeout string
masterTimeout string
flatSettings *bool
includeTypeName *bool
bodyJson interface{}
bodyString string
}
// NewIndicesPutTemplateService creates a new IndicesPutTemplateService.
func NewIndicesPutTemplateService(client *Client) *IndicesPutTemplateService {
return &IndicesPutTemplateService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *IndicesPutTemplateService) Pretty(pretty bool) *IndicesPutTemplateService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *IndicesPutTemplateService) Human(human bool) *IndicesPutTemplateService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *IndicesPutTemplateService) ErrorTrace(errorTrace bool) *IndicesPutTemplateService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *IndicesPutTemplateService) FilterPath(filterPath ...string) *IndicesPutTemplateService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *IndicesPutTemplateService) Header(name string, value string) *IndicesPutTemplateService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *IndicesPutTemplateService) Headers(headers http.Header) *IndicesPutTemplateService {
s.headers = headers
return s
}
// Name is the name of the index template.
func (s *IndicesPutTemplateService) Name(name string) *IndicesPutTemplateService {
s.name = name
return s
}
// Cause describes the cause for this index template creation. This is currently
// undocumented, but part of the Java source.
func (s *IndicesPutTemplateService) Cause(cause string) *IndicesPutTemplateService {
s.cause = cause
return s
}
// Timeout is an explicit operation timeout.
func (s *IndicesPutTemplateService) Timeout(timeout string) *IndicesPutTemplateService {
s.timeout = timeout
return s
}
// MasterTimeout specifies the timeout for connection to master.
func (s *IndicesPutTemplateService) MasterTimeout(masterTimeout string) *IndicesPutTemplateService {
s.masterTimeout = masterTimeout
return s
}
// IncludeTypeName indicates whether a type should be expected in the body of the mappings.
func (s *IndicesPutTemplateService) IncludeTypeName(includeTypeName bool) *IndicesPutTemplateService {
s.includeTypeName = &includeTypeName
return s
}
// FlatSettings indicates whether to return settings in flat format (default: false).
func (s *IndicesPutTemplateService) FlatSettings(flatSettings bool) *IndicesPutTemplateService {
s.flatSettings = &flatSettings
return s
}
// Order is the order for this template when merging multiple matching ones
// (higher numbers are merged later, overriding the lower numbers).
func (s *IndicesPutTemplateService) Order(order interface{}) *IndicesPutTemplateService {
s.order = order
return s
}
// Version sets the version number for this template.
func (s *IndicesPutTemplateService) Version(version int) *IndicesPutTemplateService {
s.version = &version
return s
}
// Create indicates whether the index template should only be added if
// new or can also replace an existing one.
func (s *IndicesPutTemplateService) Create(create bool) *IndicesPutTemplateService {
s.create = &create
return s
}
// BodyJson is documented as: The template definition.
func (s *IndicesPutTemplateService) BodyJson(body interface{}) *IndicesPutTemplateService {
s.bodyJson = body
return s
}
// BodyString is documented as: The template definition.
func (s *IndicesPutTemplateService) BodyString(body string) *IndicesPutTemplateService {
s.bodyString = body
return s
}
// buildURL builds the URL for the operation.
func (s *IndicesPutTemplateService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/_template/{name}", map[string]string{
"name": s.name,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.order != nil {
params.Set("order", fmt.Sprintf("%v", s.order))
}
if s.version != nil {
params.Set("version", fmt.Sprint(*s.version))
}
if s.create != nil {
params.Set("create", fmt.Sprint(*s.create))
}
if s.cause != "" {
params.Set("cause", s.cause)
}
if s.timeout != "" {
params.Set("timeout", s.timeout)
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
if s.flatSettings != nil {
params.Set("flat_settings", fmt.Sprint(*s.flatSettings))
}
if s.includeTypeName != nil {
params.Set("include_type_name", fmt.Sprint(*s.includeTypeName))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *IndicesPutTemplateService) Validate() error {
var invalid []string
if s.name == "" {
invalid = append(invalid, "Name")
}
if s.bodyString == "" && s.bodyJson == nil {
invalid = append(invalid, "BodyJson")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
//
// Deprecated: Legacy index templates are deprecated in favor of composable templates.
func (s *IndicesPutTemplateService) Do(ctx context.Context) (*IndicesPutTemplateResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Setup HTTP request body
var body interface{}
if s.bodyJson != nil {
body = s.bodyJson
} else {
body = s.bodyString
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "PUT",
Path: path,
Params: params,
Body: body,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(IndicesPutTemplateResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// IndicesPutTemplateResponse is the response of IndicesPutTemplateService.Do.
type IndicesPutTemplateResponse struct {
Acknowledged bool `json:"acknowledged"`
ShardsAcknowledged bool `json:"shards_acknowledged"`
Index string `json:"index,omitempty"`
}
================================================
FILE: indices_refresh.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// RefreshService explicitly refreshes one or more indices.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/indices-refresh.html.
type RefreshService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
index []string
}
// NewRefreshService creates a new instance of RefreshService.
func NewRefreshService(client *Client) *RefreshService {
builder := &RefreshService{
client: client,
}
return builder
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *RefreshService) Pretty(pretty bool) *RefreshService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *RefreshService) Human(human bool) *RefreshService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *RefreshService) ErrorTrace(errorTrace bool) *RefreshService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *RefreshService) FilterPath(filterPath ...string) *RefreshService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *RefreshService) Header(name string, value string) *RefreshService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *RefreshService) Headers(headers http.Header) *RefreshService {
s.headers = headers
return s
}
// Index specifies the indices to refresh.
func (s *RefreshService) Index(index ...string) *RefreshService {
s.index = append(s.index, index...)
return s
}
// buildURL builds the URL for the operation.
func (s *RefreshService) buildURL() (string, url.Values, error) {
var err error
var path string
if len(s.index) > 0 {
path, err = uritemplates.Expand("/{index}/_refresh", map[string]string{
"index": strings.Join(s.index, ","),
})
} else {
path = "/_refresh"
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
return path, params, nil
}
// Do executes the request.
func (s *RefreshService) Do(ctx context.Context) (*RefreshResult, error) {
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "POST",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return result
ret := new(RefreshResult)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// -- Result of a refresh request.
// RefreshResult is the outcome of RefreshService.Do.
type RefreshResult struct {
BroadcastResponse
}
================================================
FILE: indices_refresh_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestRefreshBuildURL(t *testing.T) {
client := setupTestClient(t)
tests := []struct {
Indices []string
Expected string
}{
{
[]string{},
"/_refresh",
},
{
[]string{"index1"},
"/index1/_refresh",
},
{
[]string{"index1", "index2"},
"/index1%2Cindex2/_refresh",
},
}
for i, test := range tests {
path, _, err := client.Refresh().Index(test.Indices...).buildURL()
if err != nil {
t.Errorf("case #%d: %v", i+1, err)
continue
}
if path != test.Expected {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.Expected, path)
}
}
}
func TestRefresh(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
tweet2 := tweet{User: "olivere", Message: "Another unrelated topic."}
tweet3 := tweet{User: "sandrae", Message: "Cycling is fun."}
// Add some documents
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("3").BodyJson(&tweet3).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Flush().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Refresh indices
res, err := client.Refresh(testIndexName, testIndexName2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected result; got nil")
}
}
================================================
FILE: indices_rollover.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// IndicesRolloverService rolls an alias over to a new index when the
// existing index is considered to be too large or too old.
//
// It is documented at
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/indices-rollover-index.html.
type IndicesRolloverService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
dryRun bool
newIndex string
alias string
masterTimeout string
timeout string
waitForActiveShards string
conditions map[string]interface{}
settings map[string]interface{}
mappings map[string]interface{}
bodyJson interface{}
bodyString string
}
// NewIndicesRolloverService creates a new IndicesRolloverService.
func NewIndicesRolloverService(client *Client) *IndicesRolloverService {
return &IndicesRolloverService{
client: client,
conditions: make(map[string]interface{}),
settings: make(map[string]interface{}),
mappings: make(map[string]interface{}),
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *IndicesRolloverService) Pretty(pretty bool) *IndicesRolloverService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *IndicesRolloverService) Human(human bool) *IndicesRolloverService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *IndicesRolloverService) ErrorTrace(errorTrace bool) *IndicesRolloverService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *IndicesRolloverService) FilterPath(filterPath ...string) *IndicesRolloverService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *IndicesRolloverService) Header(name string, value string) *IndicesRolloverService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *IndicesRolloverService) Headers(headers http.Header) *IndicesRolloverService {
s.headers = headers
return s
}
// Alias is the name of the alias to rollover.
func (s *IndicesRolloverService) Alias(alias string) *IndicesRolloverService {
s.alias = alias
return s
}
// NewIndex is the name of the rollover index.
func (s *IndicesRolloverService) NewIndex(newIndex string) *IndicesRolloverService {
s.newIndex = newIndex
return s
}
// MasterTimeout specifies the timeout for connection to master.
func (s *IndicesRolloverService) MasterTimeout(masterTimeout string) *IndicesRolloverService {
s.masterTimeout = masterTimeout
return s
}
// Timeout sets an explicit operation timeout.
func (s *IndicesRolloverService) Timeout(timeout string) *IndicesRolloverService {
s.timeout = timeout
return s
}
// WaitForActiveShards sets the number of active shards to wait for on the
// newly created rollover index before the operation returns.
func (s *IndicesRolloverService) WaitForActiveShards(waitForActiveShards string) *IndicesRolloverService {
s.waitForActiveShards = waitForActiveShards
return s
}
// DryRun, when set, specifies that only conditions are checked without
// performing the actual rollover.
func (s *IndicesRolloverService) DryRun(dryRun bool) *IndicesRolloverService {
s.dryRun = dryRun
return s
}
// Conditions allows to specify all conditions as a dictionary.
func (s *IndicesRolloverService) Conditions(conditions map[string]interface{}) *IndicesRolloverService {
s.conditions = conditions
return s
}
// AddCondition adds a condition to the rollover decision.
func (s *IndicesRolloverService) AddCondition(name string, value interface{}) *IndicesRolloverService {
s.conditions[name] = value
return s
}
// AddMaxIndexAgeCondition adds a condition to set the max index age.
func (s *IndicesRolloverService) AddMaxIndexAgeCondition(time string) *IndicesRolloverService {
s.conditions["max_age"] = time
return s
}
// AddMaxIndexDocsCondition adds a condition to set the max documents in the index.
func (s *IndicesRolloverService) AddMaxIndexDocsCondition(docs int64) *IndicesRolloverService {
s.conditions["max_docs"] = docs
return s
}
// Settings adds the index settings.
func (s *IndicesRolloverService) Settings(settings map[string]interface{}) *IndicesRolloverService {
s.settings = settings
return s
}
// AddSetting adds an index setting.
func (s *IndicesRolloverService) AddSetting(name string, value interface{}) *IndicesRolloverService {
s.settings[name] = value
return s
}
// Mappings adds the index mappings.
func (s *IndicesRolloverService) Mappings(mappings map[string]interface{}) *IndicesRolloverService {
s.mappings = mappings
return s
}
// AddMapping adds a mapping for the given type.
func (s *IndicesRolloverService) AddMapping(typ string, mapping interface{}) *IndicesRolloverService {
s.mappings[typ] = mapping
return s
}
// BodyJson sets the conditions that needs to be met for executing rollover,
// specified as a serializable JSON instance which is sent as the body of
// the request.
func (s *IndicesRolloverService) BodyJson(body interface{}) *IndicesRolloverService {
s.bodyJson = body
return s
}
// BodyString sets the conditions that needs to be met for executing rollover,
// specified as a string which is sent as the body of the request.
func (s *IndicesRolloverService) BodyString(body string) *IndicesRolloverService {
s.bodyString = body
return s
}
// getBody returns the body of the request, if not explicitly set via
// BodyJson or BodyString.
func (s *IndicesRolloverService) getBody() interface{} {
body := make(map[string]interface{})
if len(s.conditions) > 0 {
body["conditions"] = s.conditions
}
if len(s.settings) > 0 {
body["settings"] = s.settings
}
if len(s.mappings) > 0 {
body["mappings"] = s.mappings
}
return body
}
// buildURL builds the URL for the operation.
func (s *IndicesRolloverService) buildURL() (string, url.Values, error) {
// Build URL
var err error
var path string
if s.newIndex != "" {
path, err = uritemplates.Expand("/{alias}/_rollover/{new_index}", map[string]string{
"alias": s.alias,
"new_index": s.newIndex,
})
} else {
path, err = uritemplates.Expand("/{alias}/_rollover", map[string]string{
"alias": s.alias,
})
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.dryRun {
params.Set("dry_run", "true")
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
if s.timeout != "" {
params.Set("timeout", s.timeout)
}
if s.waitForActiveShards != "" {
params.Set("wait_for_active_shards", s.waitForActiveShards)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *IndicesRolloverService) Validate() error {
var invalid []string
if s.alias == "" {
invalid = append(invalid, "Alias")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *IndicesRolloverService) Do(ctx context.Context) (*IndicesRolloverResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Setup HTTP request body
var body interface{}
if s.bodyJson != nil {
body = s.bodyJson
} else if s.bodyString != "" {
body = s.bodyString
} else {
body = s.getBody()
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "POST",
Path: path,
Params: params,
Body: body,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(IndicesRolloverResponse)
if err := json.Unmarshal(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// IndicesRolloverResponse is the response of IndicesRolloverService.Do.
type IndicesRolloverResponse struct {
OldIndex string `json:"old_index"`
NewIndex string `json:"new_index"`
RolledOver bool `json:"rolled_over"`
DryRun bool `json:"dry_run"`
Acknowledged bool `json:"acknowledged"`
ShardsAcknowledged bool `json:"shards_acknowledged"`
Conditions map[string]bool `json:"conditions"`
}
================================================
FILE: indices_rollover_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestIndicesRolloverBuildURL(t *testing.T) {
client := setupTestClient(t)
tests := []struct {
Alias string
NewIndex string
Expected string
}{
{
"logs_write",
"",
"/logs_write/_rollover",
},
{
"logs_write",
"my_new_index_name",
"/logs_write/_rollover/my_new_index_name",
},
}
for i, test := range tests {
path, _, err := client.RolloverIndex(test.Alias).NewIndex(test.NewIndex).buildURL()
if err != nil {
t.Errorf("case #%d: %v", i+1, err)
continue
}
if path != test.Expected {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.Expected, path)
}
}
}
func TestIndicesRolloverBodyConditions(t *testing.T) {
client := setupTestClient(t)
svc := NewIndicesRolloverService(client).
Conditions(map[string]interface{}{
"max_age": "7d",
"max_docs": 1000,
})
data, err := json.Marshal(svc.getBody())
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"conditions":{"max_age":"7d","max_docs":1000}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestIndicesRolloverBodyAddCondition(t *testing.T) {
client := setupTestClient(t)
svc := NewIndicesRolloverService(client).
AddCondition("max_age", "7d").
AddCondition("max_docs", 1000)
data, err := json.Marshal(svc.getBody())
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"conditions":{"max_age":"7d","max_docs":1000}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestIndicesRolloverBodyAddPredefinedConditions(t *testing.T) {
client := setupTestClient(t)
svc := NewIndicesRolloverService(client).
AddMaxIndexAgeCondition("2d").
AddMaxIndexDocsCondition(1000000)
data, err := json.Marshal(svc.getBody())
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"conditions":{"max_age":"2d","max_docs":1000000}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestIndicesRolloverBodyComplex(t *testing.T) {
client := setupTestClient(t)
svc := NewIndicesRolloverService(client).
AddMaxIndexAgeCondition("2d").
AddMaxIndexDocsCondition(1000000).
AddSetting("index.number_of_shards", 2).
AddMapping("doc", map[string]interface{}{
"properties": map[string]interface{}{
"user": map[string]interface{}{
"type": "keyword",
},
},
})
data, err := json.Marshal(svc.getBody())
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"conditions":{"max_age":"2d","max_docs":1000000},"mappings":{"doc":{"properties":{"user":{"type":"keyword"}}}},"settings":{"index.number_of_shards":2}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: indices_segments.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// IndicesSegmentsService provides low level segments information that a
// Lucene index (shard level) is built with. Allows to be used to provide
// more information on the state of a shard and an index, possibly
// optimization information, data "wasted" on deletes, and so on.
//
// Find further documentation at
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/indices-segments.html.
type IndicesSegmentsService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
index []string
allowNoIndices *bool
expandWildcards string
ignoreUnavailable *bool
operationThreading interface{}
verbose *bool
}
// NewIndicesSegmentsService creates a new IndicesSegmentsService.
func NewIndicesSegmentsService(client *Client) *IndicesSegmentsService {
return &IndicesSegmentsService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *IndicesSegmentsService) Pretty(pretty bool) *IndicesSegmentsService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *IndicesSegmentsService) Human(human bool) *IndicesSegmentsService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *IndicesSegmentsService) ErrorTrace(errorTrace bool) *IndicesSegmentsService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *IndicesSegmentsService) FilterPath(filterPath ...string) *IndicesSegmentsService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *IndicesSegmentsService) Header(name string, value string) *IndicesSegmentsService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *IndicesSegmentsService) Headers(headers http.Header) *IndicesSegmentsService {
s.headers = headers
return s
}
// Index is a comma-separated list of index names; use `_all` or empty string
// to perform the operation on all indices.
func (s *IndicesSegmentsService) Index(indices ...string) *IndicesSegmentsService {
s.index = append(s.index, indices...)
return s
}
// AllowNoIndices indicates whether to ignore if a wildcard indices expression
// resolves into no concrete indices. (This includes `_all` string or when
// no indices have been specified).
func (s *IndicesSegmentsService) AllowNoIndices(allowNoIndices bool) *IndicesSegmentsService {
s.allowNoIndices = &allowNoIndices
return s
}
// ExpandWildcards indicates whether to expand wildcard expression to concrete indices
// that are open, closed or both.
func (s *IndicesSegmentsService) ExpandWildcards(expandWildcards string) *IndicesSegmentsService {
s.expandWildcards = expandWildcards
return s
}
// IgnoreUnavailable indicates whether specified concrete indices should be
// ignored when unavailable (missing or closed).
func (s *IndicesSegmentsService) IgnoreUnavailable(ignoreUnavailable bool) *IndicesSegmentsService {
s.ignoreUnavailable = &ignoreUnavailable
return s
}
// OperationThreading is undocumented in Elasticsearch as of now.
func (s *IndicesSegmentsService) OperationThreading(operationThreading interface{}) *IndicesSegmentsService {
s.operationThreading = operationThreading
return s
}
// Verbose, when set to true, includes detailed memory usage by Lucene.
func (s *IndicesSegmentsService) Verbose(verbose bool) *IndicesSegmentsService {
s.verbose = &verbose
return s
}
// buildURL builds the URL for the operation.
func (s *IndicesSegmentsService) buildURL() (string, url.Values, error) {
var err error
var path string
if len(s.index) > 0 {
path, err = uritemplates.Expand("/{index}/_segments", map[string]string{
"index": strings.Join(s.index, ","),
})
} else {
path = "/_segments"
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.allowNoIndices != nil {
params.Set("allow_no_indices", fmt.Sprintf("%v", *s.allowNoIndices))
}
if s.expandWildcards != "" {
params.Set("expand_wildcards", s.expandWildcards)
}
if s.ignoreUnavailable != nil {
params.Set("ignore_unavailable", fmt.Sprintf("%v", *s.ignoreUnavailable))
}
if s.operationThreading != nil {
params.Set("operation_threading", fmt.Sprintf("%v", s.operationThreading))
}
if s.verbose != nil {
params.Set("verbose", fmt.Sprintf("%v", *s.verbose))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *IndicesSegmentsService) Validate() error {
return nil
}
// Do executes the operation.
func (s *IndicesSegmentsService) Do(ctx context.Context) (*IndicesSegmentsResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(IndicesSegmentsResponse)
if err := json.Unmarshal(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// IndicesSegmentsResponse is the response of IndicesSegmentsService.Do.
type IndicesSegmentsResponse struct {
// Shards provides information returned from shards.
Shards *ShardsInfo `json:"_shards"`
// Indices provides a map into the stats of an index.
// The key of the map is the index name.
Indices map[string]*IndexSegments `json:"indices,omitempty"`
}
type IndexSegments struct {
// Shards provides a map into the shard related information of an index.
// The key of the map is the number of a specific shard.
Shards map[string][]*IndexSegmentsShards `json:"shards,omitempty"`
}
type IndexSegmentsShards struct {
Routing *IndexSegmentsRouting `json:"routing,omitempty"`
NumCommittedSegments int64 `json:"num_committed_segments,omitempty"`
NumSearchSegments int64 `json:"num_search_segments"`
// Segments provides a map into the segment related information of a shard.
// The key of the map is the specific lucene segment id.
Segments map[string]*IndexSegmentsDetails `json:"segments,omitempty"`
}
type IndexSegmentsRouting struct {
State string `json:"state,omitempty"`
Primary bool `json:"primary,omitempty"`
Node string `json:"node,omitempty"`
RelocatingNode string `json:"relocating_node,omitempty"`
}
type IndexSegmentsDetails struct {
Generation int64 `json:"generation,omitempty"`
NumDocs int64 `json:"num_docs,omitempty"`
DeletedDocs int64 `json:"deleted_docs,omitempty"`
Size string `json:"size,omitempty"`
SizeInBytes int64 `json:"size_in_bytes,omitempty"`
Memory string `json:"memory,omitempty"`
MemoryInBytes int64 `json:"memory_in_bytes,omitempty"`
Committed bool `json:"committed,omitempty"`
Search bool `json:"search,omitempty"`
Version string `json:"version,omitempty"`
Compound bool `json:"compound,omitempty"`
MergeId string `json:"merge_id,omitempty"`
Sort []*IndexSegmentsSort `json:"sort,omitempty"`
RAMTree []*IndexSegmentsRamTree `json:"ram_tree,omitempty"`
Attributes map[string]string `json:"attributes,omitempty"`
}
type IndexSegmentsSort struct {
Field string `json:"field,omitempty"`
Mode string `json:"mode,omitempty"`
Missing interface{} `json:"missing,omitempty"`
Reverse bool `json:"reverse,omitempty"`
}
type IndexSegmentsRamTree struct {
Description string `json:"description,omitempty"`
Size string `json:"size,omitempty"`
SizeInBytes int64 `json:"size_in_bytes,omitempty"`
Children []*IndexSegmentsRamTree `json:"children,omitempty"`
}
================================================
FILE: indices_segments_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestIndicesSegments(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tests := []struct {
Indices []string
Expected string
}{
{
[]string{},
"/_segments",
},
{
[]string{"index1"},
"/index1/_segments",
},
{
[]string{"index1", "index2"},
"/index1%2Cindex2/_segments",
},
}
for i, test := range tests {
path, _, err := client.IndexSegments().Index(test.Indices...).buildURL()
if err != nil {
t.Errorf("case #%d: %v", i+1, err)
}
if path != test.Expected {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.Expected, path)
}
}
}
func TestIndexSegments(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t)
//client := setupTestClientAndCreateIndexAndAddDocs(t, SetTraceLog(log.New(os.Stdout, "", 0)))
segments, err := client.IndexSegments(testIndexName).Pretty(true).Human(true).Do(context.TODO())
if err != nil {
t.Fatalf("expected no error; got: %v", err)
}
if segments == nil {
t.Fatalf("expected response; got: %v", segments)
}
indices, found := segments.Indices[testIndexName]
if !found {
t.Fatalf("expected index information about index %v; got: %v", testIndexName, found)
}
shards, found := indices.Shards["0"]
if !found {
t.Fatalf("expected shard information about index %v", testIndexName)
}
if shards == nil {
t.Fatalf("expected shard information to be != nil for index %v", testIndexName)
}
shard := shards[0]
if shard == nil {
t.Fatalf("expected shard information to be != nil for shard 0 in index %v", testIndexName)
}
if shard.Routing == nil {
t.Fatalf("expected shard routing information to be != nil for index %v", testIndexName)
}
segmentDetail, found := shard.Segments["_0"]
if !found {
t.Fatalf("expected segment detail to be != nil for index %v", testIndexName)
}
if segmentDetail == nil {
t.Fatalf("expected segment detail to be != nil for index %v", testIndexName)
}
if segmentDetail.NumDocs == 0 {
t.Fatal("expected segment to contain >= 1 docs")
}
if len(segmentDetail.Attributes) == 0 {
t.Fatalf("expected segment attributes map to contain at least one key, value pair for index %v", testIndexName)
}
}
================================================
FILE: indices_shrink.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// IndicesShrinkService allows you to shrink an existing index into a
// new index with fewer primary shards.
//
// For further details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/indices-shrink-index.html.
type IndicesShrinkService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
source string
target string
masterTimeout string
timeout string
waitForActiveShards string
bodyJson interface{}
bodyString string
}
// NewIndicesShrinkService creates a new IndicesShrinkService.
func NewIndicesShrinkService(client *Client) *IndicesShrinkService {
return &IndicesShrinkService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *IndicesShrinkService) Pretty(pretty bool) *IndicesShrinkService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *IndicesShrinkService) Human(human bool) *IndicesShrinkService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *IndicesShrinkService) ErrorTrace(errorTrace bool) *IndicesShrinkService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *IndicesShrinkService) FilterPath(filterPath ...string) *IndicesShrinkService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *IndicesShrinkService) Header(name string, value string) *IndicesShrinkService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *IndicesShrinkService) Headers(headers http.Header) *IndicesShrinkService {
s.headers = headers
return s
}
// Source is the name of the source index to shrink.
func (s *IndicesShrinkService) Source(source string) *IndicesShrinkService {
s.source = source
return s
}
// Target is the name of the target index to shrink into.
func (s *IndicesShrinkService) Target(target string) *IndicesShrinkService {
s.target = target
return s
}
// MasterTimeout specifies the timeout for connection to master.
func (s *IndicesShrinkService) MasterTimeout(masterTimeout string) *IndicesShrinkService {
s.masterTimeout = masterTimeout
return s
}
// Timeout is an explicit operation timeout.
func (s *IndicesShrinkService) Timeout(timeout string) *IndicesShrinkService {
s.timeout = timeout
return s
}
// WaitForActiveShards sets the number of active shards to wait for on
// the shrunken index before the operation returns.
func (s *IndicesShrinkService) WaitForActiveShards(waitForActiveShards string) *IndicesShrinkService {
s.waitForActiveShards = waitForActiveShards
return s
}
// BodyJson is the configuration for the target index (`settings` and `aliases`)
// defined as a JSON-serializable instance to be sent as the request body.
func (s *IndicesShrinkService) BodyJson(body interface{}) *IndicesShrinkService {
s.bodyJson = body
return s
}
// BodyString is the configuration for the target index (`settings` and `aliases`)
// defined as a string to send as the request body.
func (s *IndicesShrinkService) BodyString(body string) *IndicesShrinkService {
s.bodyString = body
return s
}
// buildURL builds the URL for the operation.
func (s *IndicesShrinkService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/{source}/_shrink/{target}", map[string]string{
"source": s.source,
"target": s.target,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
if s.timeout != "" {
params.Set("timeout", s.timeout)
}
if s.waitForActiveShards != "" {
params.Set("wait_for_active_shards", s.waitForActiveShards)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *IndicesShrinkService) Validate() error {
var invalid []string
if s.source == "" {
invalid = append(invalid, "Source")
}
if s.target == "" {
invalid = append(invalid, "Target")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *IndicesShrinkService) Do(ctx context.Context) (*IndicesShrinkResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Setup HTTP request body
var body interface{}
if s.bodyJson != nil {
body = s.bodyJson
} else if s.bodyString != "" {
body = s.bodyString
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "POST",
Path: path,
Params: params,
Body: body,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(IndicesShrinkResponse)
if err := json.Unmarshal(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// IndicesShrinkResponse is the response of IndicesShrinkService.Do.
type IndicesShrinkResponse struct {
Acknowledged bool `json:"acknowledged"`
ShardsAcknowledged bool `json:"shards_acknowledged"`
Index string `json:"index,omitempty"`
}
================================================
FILE: indices_shrink_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import "testing"
func TestIndicesShrinkBuildURL(t *testing.T) {
client := setupTestClient(t)
tests := []struct {
Source string
Target string
Expected string
}{
{
"my_source_index",
"my_target_index",
"/my_source_index/_shrink/my_target_index",
},
}
for i, test := range tests {
path, _, err := client.ShrinkIndex(test.Source, test.Target).buildURL()
if err != nil {
t.Errorf("case #%d: %v", i+1, err)
continue
}
if path != test.Expected {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.Expected, path)
}
}
}
================================================
FILE: indices_stats.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// IndicesStatsService provides stats on various metrics of one or more
// indices. See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/indices-stats.html.
type IndicesStatsService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
metric []string
index []string
level string
types []string
completionFields []string
fielddataFields []string
fields []string
groups []string
}
// NewIndicesStatsService creates a new IndicesStatsService.
func NewIndicesStatsService(client *Client) *IndicesStatsService {
return &IndicesStatsService{
client: client,
index: make([]string, 0),
metric: make([]string, 0),
completionFields: make([]string, 0),
fielddataFields: make([]string, 0),
fields: make([]string, 0),
groups: make([]string, 0),
types: make([]string, 0),
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *IndicesStatsService) Pretty(pretty bool) *IndicesStatsService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *IndicesStatsService) Human(human bool) *IndicesStatsService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *IndicesStatsService) ErrorTrace(errorTrace bool) *IndicesStatsService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *IndicesStatsService) FilterPath(filterPath ...string) *IndicesStatsService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *IndicesStatsService) Header(name string, value string) *IndicesStatsService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *IndicesStatsService) Headers(headers http.Header) *IndicesStatsService {
s.headers = headers
return s
}
// Metric limits the information returned the specific metrics. Options are:
// docs, store, indexing, get, search, completion, fielddata, flush, merge,
// query_cache, refresh, suggest, and warmer.
func (s *IndicesStatsService) Metric(metric ...string) *IndicesStatsService {
s.metric = append(s.metric, metric...)
return s
}
// Index is the list of index names; use `_all` or empty string to perform
// the operation on all indices.
func (s *IndicesStatsService) Index(indices ...string) *IndicesStatsService {
s.index = append(s.index, indices...)
return s
}
// Type is a list of document types for the `indexing` index metric.
func (s *IndicesStatsService) Type(types ...string) *IndicesStatsService {
s.types = append(s.types, types...)
return s
}
// Level returns stats aggregated at cluster, index or shard level.
func (s *IndicesStatsService) Level(level string) *IndicesStatsService {
s.level = level
return s
}
// CompletionFields is a list of fields for `fielddata` and `suggest`
// index metric (supports wildcards).
func (s *IndicesStatsService) CompletionFields(completionFields ...string) *IndicesStatsService {
s.completionFields = append(s.completionFields, completionFields...)
return s
}
// FielddataFields is a list of fields for `fielddata` index metric (supports wildcards).
func (s *IndicesStatsService) FielddataFields(fielddataFields ...string) *IndicesStatsService {
s.fielddataFields = append(s.fielddataFields, fielddataFields...)
return s
}
// Fields is a list of fields for `fielddata` and `completion` index metric
// (supports wildcards).
func (s *IndicesStatsService) Fields(fields ...string) *IndicesStatsService {
s.fields = append(s.fields, fields...)
return s
}
// Groups is a list of search groups for `search` index metric.
func (s *IndicesStatsService) Groups(groups ...string) *IndicesStatsService {
s.groups = append(s.groups, groups...)
return s
}
// buildURL builds the URL for the operation.
func (s *IndicesStatsService) buildURL() (string, url.Values, error) {
var err error
var path string
if len(s.index) > 0 && len(s.metric) > 0 {
path, err = uritemplates.Expand("/{index}/_stats/{metric}", map[string]string{
"index": strings.Join(s.index, ","),
"metric": strings.Join(s.metric, ","),
})
} else if len(s.index) > 0 {
path, err = uritemplates.Expand("/{index}/_stats", map[string]string{
"index": strings.Join(s.index, ","),
})
} else if len(s.metric) > 0 {
path, err = uritemplates.Expand("/_stats/{metric}", map[string]string{
"metric": strings.Join(s.metric, ","),
})
} else {
path = "/_stats"
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if len(s.groups) > 0 {
params.Set("groups", strings.Join(s.groups, ","))
}
if s.level != "" {
params.Set("level", s.level)
}
if len(s.types) > 0 {
params.Set("types", strings.Join(s.types, ","))
}
if len(s.completionFields) > 0 {
params.Set("completion_fields", strings.Join(s.completionFields, ","))
}
if len(s.fielddataFields) > 0 {
params.Set("fielddata_fields", strings.Join(s.fielddataFields, ","))
}
if len(s.fields) > 0 {
params.Set("fields", strings.Join(s.fields, ","))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *IndicesStatsService) Validate() error {
return nil
}
// Do executes the operation.
func (s *IndicesStatsService) Do(ctx context.Context) (*IndicesStatsResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(IndicesStatsResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// IndicesStatsResponse is the response of IndicesStatsService.Do.
type IndicesStatsResponse struct {
// Shards provides information returned from shards.
Shards *ShardsInfo `json:"_shards"`
// All provides summary stats about all indices.
All *IndexStats `json:"_all,omitempty"`
// Indices provides a map into the stats of an index. The key of the
// map is the index name.
Indices map[string]*IndexStats `json:"indices,omitempty"`
}
// IndexStats is index stats for a specific index.
type IndexStats struct {
UUID string `json:"uuid,omitempty"`
Primaries *IndexStatsDetails `json:"primaries,omitempty"`
Total *IndexStatsDetails `json:"total,omitempty"`
Shards map[string][]*IndexStatsDetails `json:"shards,omitempty"`
}
type IndexStatsDetails struct {
Routing *IndexStatsRouting `json:"routing,omitempty"`
Docs *IndexStatsDocs `json:"docs,omitempty"`
Store *IndexStatsStore `json:"store,omitempty"`
Indexing *IndexStatsIndexing `json:"indexing,omitempty"`
Get *IndexStatsGet `json:"get,omitempty"`
Search *IndexStatsSearch `json:"search,omitempty"`
Merges *IndexStatsMerges `json:"merges,omitempty"`
Refresh *IndexStatsRefresh `json:"refresh,omitempty"`
Recovery *IndexStatsRecovery `json:"recovery,omitempty"`
Flush *IndexStatsFlush `json:"flush,omitempty"`
Warmer *IndexStatsWarmer `json:"warmer,omitempty"`
FilterCache *IndexStatsFilterCache `json:"filter_cache,omitempty"`
IdCache *IndexStatsIdCache `json:"id_cache,omitempty"`
Fielddata *IndexStatsFielddata `json:"fielddata,omitempty"`
Percolate *IndexStatsPercolate `json:"percolate,omitempty"`
Completion *IndexStatsCompletion `json:"completion,omitempty"`
Segments *IndexStatsSegments `json:"segments,omitempty"`
Translog *IndexStatsTranslog `json:"translog,omitempty"`
Suggest *IndexStatsSuggest `json:"suggest,omitempty"`
QueryCache *IndexStatsQueryCache `json:"query_cache,omitempty"`
RequestCache *IndexStatsRequestCache `json:"request_cache,omitempty"`
Commit *IndexStatsCommit `json:"commit,omitempty"`
SeqNo *IndexStatsSeqNo `json:"seq_no,omitempty"`
RetentionLeases *IndexStatsRetentionLeases `json:"retention_leases,omitempty"`
ShardPath *IndexStatsShardPath `json:"shard_path,omitempty"`
ShardStats *IndexStatsShardStats `json:"shard_stats,omitempty"`
}
type IndexStatsRouting struct {
State string `json:"state"` // e.g. "STARTED"
Primary bool `json:"primary"`
Node string `json:"node"` // e.g. "-aXnGv4oTW6bIIl0db3eCg"
RelocatingNode *string `json:"relocating_node"`
}
type IndexStatsShardPath struct {
StatePath string `json:"state_path"` // e.g. "/usr/share/elasticsearch/data/nodes/0"
DataPath string `json:"data_path"` // e.g. "/usr/share/elasticsearch/data/nodes/0"
IsCustomDataPath bool `json:"is_custom_data_path"`
}
type IndexStatsShardStats struct {
TotalCount int64 `json:"total_count,omitempty"`
}
type IndexStatsDocs struct {
Count int64 `json:"count,omitempty"`
Deleted int64 `json:"deleted,omitempty"`
}
type IndexStatsStore struct {
Size string `json:"size,omitempty"` // human size, e.g. 119.3mb
SizeInBytes int64 `json:"size_in_bytes,omitempty"`
TotalDataSetSize string `json:"total_data_set_size,omitempty"`
TotalDataSetSizeInBytes int64 `json:"total_data_set_size_in_bytes,omitempty"`
Reserved string `json:"reserved,omitempty"`
ReservedInBytes int64 `json:"reserved_in_bytes,omitempty"`
}
type IndexStatsIndexing struct {
IndexTotal int64 `json:"index_total,omitempty"`
IndexTime string `json:"index_time,omitempty"`
IndexTimeInMillis int64 `json:"index_time_in_millis,omitempty"`
IndexCurrent int64 `json:"index_current,omitempty"`
IndexFailed int64 `json:"index_failed,omitempty"`
DeleteTotal int64 `json:"delete_total,omitempty"`
DeleteTime string `json:"delete_time,omitempty"`
DeleteTimeInMillis int64 `json:"delete_time_in_millis,omitempty"`
DeleteCurrent int64 `json:"delete_current,omitempty"`
NoopUpdateTotal int64 `json:"noop_update_total,omitempty"`
IsThrottled bool `json:"is_throttled,omitempty"`
ThrottleTime string `json:"throttle_time,omitempty"`
ThrottleTimeInMillis int64 `json:"throttle_time_in_millis,omitempty"`
}
type IndexStatsGet struct {
Total int64 `json:"total,omitempty"`
GetTime string `json:"getTime,omitempty"` // 7.4.0 uses "getTime", earlier versions used "get_time"
TimeInMillis int64 `json:"time_in_millis,omitempty"`
ExistsTotal int64 `json:"exists_total,omitempty"`
ExistsTime string `json:"exists_time,omitempty"`
ExistsTimeInMillis int64 `json:"exists_time_in_millis,omitempty"`
MissingTotal int64 `json:"missing_total,omitempty"`
MissingTime string `json:"missing_time,omitempty"`
MissingTimeInMillis int64 `json:"missing_time_in_millis,omitempty"`
Current int64 `json:"current,omitempty"`
}
type IndexStatsSearch struct {
OpenContexts int64 `json:"open_contexts,omitempty"`
QueryTotal int64 `json:"query_total,omitempty"`
QueryTime string `json:"query_time,omitempty"`
QueryTimeInMillis int64 `json:"query_time_in_millis,omitempty"`
QueryCurrent int64 `json:"query_current,omitempty"`
FetchTotal int64 `json:"fetch_total,omitempty"`
FetchTime string `json:"fetch_time,omitempty"`
FetchTimeInMillis int64 `json:"fetch_time_in_millis,omitempty"`
FetchCurrent int64 `json:"fetch_current,omitempty"`
ScrollTotal int64 `json:"scroll_total,omitempty"`
ScrollTime string `json:"scroll_time,omitempty"`
ScrollTimeInMillis int64 `json:"scroll_time_in_millis,omitempty"`
ScrollCurrent int64 `json:"scroll_current,omitempty"`
SuggestTotal int64 `json:"suggest_total,omitempty"`
SuggestTime string `json:"suggest_time,omitempty"`
SuggestTimeInMillis int64 `json:"suggest_time_in_millis,omitempty"`
SuggestCurrent int64 `json:"suggest_current,omitempty"`
}
type IndexStatsMerges struct {
Current int64 `json:"current,omitempty"`
CurrentDocs int64 `json:"current_docs,omitempty"`
CurrentSize string `json:"current_size,omitempty"`
CurrentSizeInBytes int64 `json:"current_size_in_bytes,omitempty"`
Total int64 `json:"total,omitempty"`
TotalTime string `json:"total_time,omitempty"`
TotalTimeInMillis int64 `json:"total_time_in_millis,omitempty"`
TotalDocs int64 `json:"total_docs,omitempty"`
TotalSize string `json:"total_size,omitempty"`
TotalSizeInBytes int64 `json:"total_size_in_bytes,omitempty"`
TotalStoppedTime string `json:"total_stopped_time,omitempty"`
TotalStoppedTimeInMillis int64 `json:"total_stopped_time_in_millis,omitempty"`
TotalThrottledTime string `json:"total_throttled_time,omitempty"`
TotalThrottledTimeInMillis int64 `json:"total_throttled_time_in_millis,omitempty"`
TotalAutoThrottle string `json:"total_auto_throttle,omitempty"`
TotalAutoThrottleInBytes int64 `json:"total_auto_throttle_in_bytes,omitempty"`
}
type IndexStatsRefresh struct {
Total int64 `json:"total,omitempty"`
TotalTime string `json:"total_time,omitempty"`
TotalTimeInMillis int64 `json:"total_time_in_millis,omitempty"`
ExternalTotal int64 `json:"external_total,omitempty"`
ExternalTotalTime string `json:"external_total_time,omitempty"`
ExternalTotalTimeInMillis int64 `json:"external_total_time_in_millis,omitempty"`
Listeners int64 `json:"listeners,omitempty"`
}
type IndexStatsRecovery struct {
CurrentAsSource int64 `json:"current_as_source,omitempty"`
CurrentAsTarget int64 `json:"current_as_target,omitempty"`
ThrottleTime string `json:"throttle_time,omitempty"`
ThrottleTimeInMillis int64 `json:"throttle_time_in_millis,omitempty"`
}
type IndexStatsFlush struct {
Total int64 `json:"total,omitempty"`
TotalTime string `json:"total_time,omitempty"`
TotalTimeInMillis int64 `json:"total_time_in_millis,omitempty"`
Periodic int64 `json:"periodic,omitempty"`
}
type IndexStatsWarmer struct {
Current int64 `json:"current,omitempty"`
Total int64 `json:"total,omitempty"`
TotalTime string `json:"total_time,omitempty"`
TotalTimeInMillis int64 `json:"total_time_in_millis,omitempty"`
}
type IndexStatsRequestCache struct {
MemorySize string `json:"memory_size,omitempty"`
MemorySizeInBytes int64 `json:"memory_size_in_bytes,omitempty"`
Evictions int64 `json:"evictions,omitempty"`
HitCount int64 `json:"hit_count,omitempty"`
MissCount int64 `json:"miss_count,omitempty"`
}
type IndexStatsCommit struct {
ID string `json:"id,omitempty"` // lucene commit ID in base64, e.g. "m2tDMYHzSpSV6zJH0lIAnA=="
Generation int64 `json:"generation,omitempty"`
UserData map[string]string `json:"user_data,omitempty"`
NumDocs int64 `json:"num_docs,omitempty"`
}
type IndexStatsFilterCache struct {
MemorySize string `json:"memory_size,omitempty"`
MemorySizeInBytes int64 `json:"memory_size_in_bytes,omitempty"`
Evictions int64 `json:"evictions,omitempty"`
}
type IndexStatsIdCache struct {
MemorySize string `json:"memory_size,omitempty"`
MemorySizeInBytes int64 `json:"memory_size_in_bytes,omitempty"`
}
type IndexStatsFielddata struct {
MemorySize string `json:"memory_size,omitempty"`
MemorySizeInBytes int64 `json:"memory_size_in_bytes,omitempty"`
Evictions int64 `json:"evictions,omitempty"`
}
type IndexStatsPercolate struct {
Total int64 `json:"total,omitempty"`
GetTime string `json:"get_time,omitempty"`
TimeInMillis int64 `json:"time_in_millis,omitempty"`
Current int64 `json:"current,omitempty"`
MemorySize string `json:"memory_size,omitempty"`
MemorySizeInBytes int64 `json:"memory_size_in_bytes,omitempty"`
Queries int64 `json:"queries,omitempty"`
}
type IndexStatsCompletion struct {
Size string `json:"size,omitempty"`
SizeInBytes int64 `json:"size_in_bytes,omitempty"`
}
type IndexStatsSegments struct {
Count int64 `json:"count"`
Memory string `json:"memory"` // e.g. "61.3kb"
MemoryInBytes int64 `json:"memory_in_bytes"`
TermsMemory string `json:"terms_memory"` // e.g. "61.3kb"
TermsMemoryInBytes int64 `json:"terms_memory_in_bytes"`
StoredFieldsMemory string `json:"stored_fields_memory"` // e.g. "61.3kb"
StoredFieldsMemoryInBytes int64 `json:"stored_fields_memory_in_bytes"`
TermVectorsMemory string `json:"term_vectors_memory"` // e.g. "61.3kb"
TermVectorsMemoryInBytes int64 `json:"term_vectors_memory_in_bytes"`
NormsMemory string `json:"norms_memory"` // e.g. "61.3kb"
NormsMemoryInBytes int64 `json:"norms_memory_in_bytes"`
PointsMemory string `json:"points_memory"` // e.g. "61.3kb"
PointsMemoryInBytes int64 `json:"points_memory_in_bytes"`
DocValuesMemory string `json:"doc_values_memory"` // e.g. "61.3kb"
DocValuesMemoryInBytes int64 `json:"doc_values_memory_in_bytes"`
IndexWriterMemory string `json:"index_writer_memory"` // e.g. "61.3kb"
IndexWriterMemoryInBytes int64 `json:"index_writer_memory_in_bytes"`
VersionMapMemory string `json:"version_map_memory"` // e.g. "61.3kb"
VersionMapMemoryInBytes int64 `json:"version_map_memory_in_bytes"`
FixedBitSet string `json:"fixed_bit_set"` // e.g. "61.3kb"
FixedBitSetInBytes int64 `json:"fixed_bit_set_memory_in_bytes"`
MaxUnsafeAutoIDTimestamp int64 `json:"max_unsafe_auto_id_timestamp"`
FileSizes map[string]*ClusterStatsIndicesSegmentsFile `json:"file_sizes"`
}
type IndexStatsTranslog struct {
Operations int64 `json:"operations,omitempty"`
Size string `json:"size,omitempty"`
SizeInBytes int64 `json:"size_in_bytes,omitempty"`
UncommittedOperations int64 `json:"uncommitted_operations,omitempty"`
UncommittedSize string `json:"uncommitted_size,omitempty"`
UncommittedSizeInBytes int64 `json:"uncommitted_size_in_bytes,omitempty"`
EarliestLastModifiedAge int64 `json:"earliest_last_modified_age,omitempty"`
}
type IndexStatsSuggest struct {
Total int64 `json:"total,omitempty"`
Time string `json:"time,omitempty"`
TimeInMillis int64 `json:"time_in_millis,omitempty"`
Current int64 `json:"current,omitempty"`
}
type IndexStatsQueryCache struct {
MemorySize string `json:"memory_size,omitempty"`
MemorySizeInBytes int64 `json:"memory_size_in_bytes,omitempty"`
TotalCount int64 `json:"total_count,omitempty"`
HitCount int64 `json:"hit_count,omitempty"`
MissCount int64 `json:"miss_count,omitempty"`
CacheSize int64 `json:"cache_size,omitempty"`
CacheCount int64 `json:"cache_count,omitempty"`
Evictions int64 `json:"evictions,omitempty"`
}
type IndexStatsSeqNo struct {
MaxSeqNo int64 `json:"max_seq_no,omitempty"`
LocalCheckpoint int64 `json:"local_checkpoint,omitempty"`
GlobalCheckpoint int64 `json:"global_checkpoint,omitempty"`
}
type IndexStatsRetentionLeases struct {
PrimaryTerm int64 `json:"primary_term,omitempty"`
Version int64 `json:"version,omitempty"`
Leases []*IndexStatsRetentionLease `json:"leases,omitempty"`
}
type IndexStatsRetentionLease struct {
Id string `json:"id,omitempty"`
RetainingSeqNo int64 `json:"retaining_seq_no,omitempty"`
Timestamp int64 `json:"timestamp,omitempty"`
Source string `json:"source,omitempty"`
}
================================================
FILE: indices_stats_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestIndexStatsBuildURL(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tests := []struct {
Indices []string
Metrics []string
Expected string
}{
{
[]string{},
[]string{},
"/_stats",
},
{
[]string{"index1"},
[]string{},
"/index1/_stats",
},
{
[]string{},
[]string{"metric1"},
"/_stats/metric1",
},
{
[]string{"index1"},
[]string{"metric1"},
"/index1/_stats/metric1",
},
{
[]string{"index1", "index2"},
[]string{"metric1"},
"/index1%2Cindex2/_stats/metric1",
},
{
[]string{"index1", "index2"},
[]string{"metric1", "metric2"},
"/index1%2Cindex2/_stats/metric1%2Cmetric2",
},
}
for i, test := range tests {
path, _, err := client.IndexStats().Index(test.Indices...).Metric(test.Metrics...).buildURL()
if err != nil {
t.Fatalf("case #%d: %v", i+1, err)
}
if path != test.Expected {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.Expected, path)
}
}
}
func TestIndexStats(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t) //, SetTraceLog(log.New(os.Stdout, "", 0)))
stats, err := client.IndexStats(testIndexName).Human(true).Pretty(true).Do(context.TODO())
if err != nil {
t.Fatalf("expected no error; got: %v", err)
}
if stats == nil {
t.Fatalf("expected response; got: %v", stats)
}
stat, found := stats.Indices[testIndexName]
if !found {
t.Fatalf("expected stats about index %q; got: %v", testIndexName, found)
}
if stat.Total == nil {
t.Fatalf("expected total to be != nil; got: %v", stat.Total)
}
if stat.Total.Docs == nil {
t.Fatalf("expected total docs to be != nil; got: %v", stat.Total.Docs)
}
if stat.Total.Docs.Count == 0 {
t.Fatalf("expected total docs count to be > 0; got: %d", stat.Total.Docs.Count)
}
}
func TestIndexStatsWithShards(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t) //, SetTraceLog(log.New(os.Stdout, "", 0)))
stats, err := client.IndexStats(testIndexName).Level("shards").Human(true).Pretty(true).Do(context.TODO())
if err != nil {
t.Fatalf("expected no error; got: %v", err)
}
if stats == nil {
t.Fatalf("expected response; got: %v", stats)
}
stat, found := stats.Indices[testIndexName]
if !found {
t.Fatalf("expected stats about index %q; got: %v", testIndexName, found)
}
if stat.Total == nil {
t.Fatalf("expected total to be != nil; got: %v", stat.Total)
}
if stat.Total.Docs == nil {
t.Fatalf("expected total docs to be != nil; got: %v", stat.Total.Docs)
}
if stat.Total.Docs.Count == 0 {
t.Fatalf("expected total docs count to be > 0; got: %d", stat.Total.Docs.Count)
}
if stat.Shards == nil {
t.Fatalf("expected shard level information to be != nil; got: %v", stat.Shards)
}
shard, found := stat.Shards["0"]
if !found || shard == nil {
t.Fatalf("expected shard level information for shard 0; got: %v (found=%v)", shard, found)
}
if len(shard) != 1 {
t.Fatalf("expected shard level information array to be == 1; got: %v", len(shard))
}
if shard[0].Docs == nil {
t.Fatalf("expected docs to be != nil; got: %v", shard[0].Docs)
}
}
================================================
FILE: indices_unfreeze.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// IndicesUnfreezeService unfreezes an index.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/unfreeze-index-api.html
// and https://www.elastic.co/blog/creating-frozen-indices-with-the-elasticsearch-freeze-index-api
// for details.
//
// Deprecated: Frozen indices are deprecated because they provide no benefit
// given improvements in heap memory utilization.
type IndicesUnfreezeService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
index string
timeout string
masterTimeout string
ignoreUnavailable *bool
allowNoIndices *bool
expandWildcards string
waitForActiveShards string
}
// NewIndicesUnfreezeService creates a new IndicesUnfreezeService.
func NewIndicesUnfreezeService(client *Client) *IndicesUnfreezeService {
return &IndicesUnfreezeService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *IndicesUnfreezeService) Pretty(pretty bool) *IndicesUnfreezeService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *IndicesUnfreezeService) Human(human bool) *IndicesUnfreezeService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *IndicesUnfreezeService) ErrorTrace(errorTrace bool) *IndicesUnfreezeService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *IndicesUnfreezeService) FilterPath(filterPath ...string) *IndicesUnfreezeService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *IndicesUnfreezeService) Header(name string, value string) *IndicesUnfreezeService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *IndicesUnfreezeService) Headers(headers http.Header) *IndicesUnfreezeService {
s.headers = headers
return s
}
// Index is the name of the index to unfreeze.
func (s *IndicesUnfreezeService) Index(index string) *IndicesUnfreezeService {
s.index = index
return s
}
// Timeout allows to specify an explicit timeout.
func (s *IndicesUnfreezeService) Timeout(timeout string) *IndicesUnfreezeService {
s.timeout = timeout
return s
}
// MasterTimeout allows to specify a timeout for connection to master.
func (s *IndicesUnfreezeService) MasterTimeout(masterTimeout string) *IndicesUnfreezeService {
s.masterTimeout = masterTimeout
return s
}
// IgnoreUnavailable indicates whether specified concrete indices should be
// ignored when unavailable (missing or closed).
func (s *IndicesUnfreezeService) IgnoreUnavailable(ignoreUnavailable bool) *IndicesUnfreezeService {
s.ignoreUnavailable = &ignoreUnavailable
return s
}
// AllowNoIndices indicates whether to ignore if a wildcard indices expression
// resolves into no concrete indices. (This includes `_all` string or when
// no indices have been specified).
func (s *IndicesUnfreezeService) AllowNoIndices(allowNoIndices bool) *IndicesUnfreezeService {
s.allowNoIndices = &allowNoIndices
return s
}
// ExpandWildcards specifies whether to expand wildcard expression to
// concrete indices that are open, closed or both..
func (s *IndicesUnfreezeService) ExpandWildcards(expandWildcards string) *IndicesUnfreezeService {
s.expandWildcards = expandWildcards
return s
}
// WaitForActiveShards sets the number of active shards to wait for
// before the operation returns.
func (s *IndicesUnfreezeService) WaitForActiveShards(numShards string) *IndicesUnfreezeService {
s.waitForActiveShards = numShards
return s
}
// buildURL builds the URL for the operation.
func (s *IndicesUnfreezeService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/{index}/_unfreeze", map[string]string{
"index": s.index,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.timeout != "" {
params.Set("timeout", s.timeout)
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
if s.expandWildcards != "" {
params.Set("expand_wildcards", s.expandWildcards)
}
if s.ignoreUnavailable != nil {
params.Set("ignore_unavailable", fmt.Sprintf("%v", *s.ignoreUnavailable))
}
if s.allowNoIndices != nil {
params.Set("allow_no_indices", fmt.Sprintf("%v", *s.allowNoIndices))
}
if s.expandWildcards != "" {
params.Set("expand_wildcards", s.expandWildcards)
}
if s.waitForActiveShards != "" {
params.Set("wait_for_active_shards", s.waitForActiveShards)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *IndicesUnfreezeService) Validate() error {
var invalid []string
if s.index == "" {
invalid = append(invalid, "Index")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the service.
//
// Deprecated: Frozen indices are deprecated because they provide no benefit
// given improvements in heap memory utilization.
func (s *IndicesUnfreezeService) Do(ctx context.Context) (*IndicesUnfreezeResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "POST",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(IndicesUnfreezeResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// IndicesUnfreezeResponse is the outcome of freezing an index.
type IndicesUnfreezeResponse struct {
Shards *ShardsInfo `json:"_shards"`
}
================================================
FILE: indices_unfreeze_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"testing"
)
func TestIndicesUnfreezeBuildURL(t *testing.T) {
client := setupTestClient(t)
tests := []struct {
Index string
Expected string
}{
{
"index1",
"/index1/_unfreeze",
},
}
for i, test := range tests {
path, _, err := client.UnfreezeIndex(test.Index).buildURL()
if err != nil {
t.Errorf("case #%d: %v", i+1, err)
continue
}
if path != test.Expected {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.Expected, path)
}
}
}
================================================
FILE: ingest_delete_pipeline.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// IngestDeletePipelineService deletes pipelines by ID.
// It is documented at https://www.elastic.co/guide/en/elasticsearch/reference/7.0/delete-pipeline-api.html.
type IngestDeletePipelineService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
id string
masterTimeout string
timeout string
}
// NewIngestDeletePipelineService creates a new IngestDeletePipelineService.
func NewIngestDeletePipelineService(client *Client) *IngestDeletePipelineService {
return &IngestDeletePipelineService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *IngestDeletePipelineService) Pretty(pretty bool) *IngestDeletePipelineService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *IngestDeletePipelineService) Human(human bool) *IngestDeletePipelineService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *IngestDeletePipelineService) ErrorTrace(errorTrace bool) *IngestDeletePipelineService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *IngestDeletePipelineService) FilterPath(filterPath ...string) *IngestDeletePipelineService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *IngestDeletePipelineService) Header(name string, value string) *IngestDeletePipelineService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *IngestDeletePipelineService) Headers(headers http.Header) *IngestDeletePipelineService {
s.headers = headers
return s
}
// Id is documented as: Pipeline ID.
func (s *IngestDeletePipelineService) Id(id string) *IngestDeletePipelineService {
s.id = id
return s
}
// MasterTimeout is documented as: Explicit operation timeout for connection to master node.
func (s *IngestDeletePipelineService) MasterTimeout(masterTimeout string) *IngestDeletePipelineService {
s.masterTimeout = masterTimeout
return s
}
// Timeout is documented as: Explicit operation timeout.
func (s *IngestDeletePipelineService) Timeout(timeout string) *IngestDeletePipelineService {
s.timeout = timeout
return s
}
// buildURL builds the URL for the operation.
func (s *IngestDeletePipelineService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/_ingest/pipeline/{id}", map[string]string{
"id": s.id,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
if s.timeout != "" {
params.Set("timeout", s.timeout)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *IngestDeletePipelineService) Validate() error {
var invalid []string
if s.id == "" {
invalid = append(invalid, "Id")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *IngestDeletePipelineService) Do(ctx context.Context) (*IngestDeletePipelineResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "DELETE",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(IngestDeletePipelineResponse)
if err := json.Unmarshal(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// IngestDeletePipelineResponse is the response of IngestDeletePipelineService.Do.
type IngestDeletePipelineResponse struct {
Acknowledged bool `json:"acknowledged"`
ShardsAcknowledged bool `json:"shards_acknowledged"`
Index string `json:"index,omitempty"`
}
================================================
FILE: ingest_delete_pipeline_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import "testing"
func TestIngestDeletePipelineURL(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tests := []struct {
Id string
Expected string
}{
{
"my-pipeline-id",
"/_ingest/pipeline/my-pipeline-id",
},
}
for _, test := range tests {
path, _, err := client.IngestDeletePipeline(test.Id).buildURL()
if err != nil {
t.Fatal(err)
}
if path != test.Expected {
t.Errorf("expected %q; got: %q", test.Expected, path)
}
}
}
================================================
FILE: ingest_get_pipeline.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// IngestGetPipelineService returns pipelines based on ID.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/get-pipeline-api.html
// for documentation.
type IngestGetPipelineService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
id []string
masterTimeout string
}
// NewIngestGetPipelineService creates a new IngestGetPipelineService.
func NewIngestGetPipelineService(client *Client) *IngestGetPipelineService {
return &IngestGetPipelineService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *IngestGetPipelineService) Pretty(pretty bool) *IngestGetPipelineService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *IngestGetPipelineService) Human(human bool) *IngestGetPipelineService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *IngestGetPipelineService) ErrorTrace(errorTrace bool) *IngestGetPipelineService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *IngestGetPipelineService) FilterPath(filterPath ...string) *IngestGetPipelineService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *IngestGetPipelineService) Header(name string, value string) *IngestGetPipelineService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *IngestGetPipelineService) Headers(headers http.Header) *IngestGetPipelineService {
s.headers = headers
return s
}
// Id is a list of pipeline ids. Wildcards supported.
func (s *IngestGetPipelineService) Id(id ...string) *IngestGetPipelineService {
s.id = append(s.id, id...)
return s
}
// MasterTimeout is an explicit operation timeout for connection to master node.
func (s *IngestGetPipelineService) MasterTimeout(masterTimeout string) *IngestGetPipelineService {
s.masterTimeout = masterTimeout
return s
}
// buildURL builds the URL for the operation.
func (s *IngestGetPipelineService) buildURL() (string, url.Values, error) {
var err error
var path string
// Build URL
if len(s.id) > 0 {
path, err = uritemplates.Expand("/_ingest/pipeline/{id}", map[string]string{
"id": strings.Join(s.id, ","),
})
} else {
path = "/_ingest/pipeline"
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *IngestGetPipelineService) Validate() error {
return nil
}
// Do executes the operation.
func (s *IngestGetPipelineService) Do(ctx context.Context) (IngestGetPipelineResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
var ret IngestGetPipelineResponse
if err := json.Unmarshal(res.Body, &ret); err != nil {
return nil, err
}
return ret, nil
}
// IngestGetPipelineResponse is the response of IngestGetPipelineService.Do.
type IngestGetPipelineResponse map[string]*IngestGetPipeline
// IngestGetPipeline describes a specific ingest pipeline, its
// processors etc.
type IngestGetPipeline struct {
Description string `json:"description"`
Processors []map[string]interface{} `json:"processors"`
Version int64 `json:"version,omitempty"`
OnFailure []map[string]interface{} `json:"on_failure,omitempty"`
}
================================================
FILE: ingest_get_pipeline_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestIngestGetPipelineURL(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tests := []struct {
Id []string
Expected string
}{
{
nil,
"/_ingest/pipeline",
},
{
[]string{"my-pipeline-id"},
"/_ingest/pipeline/my-pipeline-id",
},
{
[]string{"*"},
"/_ingest/pipeline/%2A",
},
{
[]string{"pipeline-1", "pipeline-2"},
"/_ingest/pipeline/pipeline-1%2Cpipeline-2",
},
}
for _, test := range tests {
path, _, err := client.IngestGetPipeline(test.Id...).buildURL()
if err != nil {
t.Fatal(err)
}
if path != test.Expected {
t.Errorf("expected %q; got: %q", test.Expected, path)
}
}
}
func TestIngestLifecycle(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t) //, SetTraceLog(log.New(os.Stdout, "", 0)))
// With the new ES Docker images, XPack is already installed and returns a pipeline. So we cannot test for "no pipelines". Skipping for now.
/*
// Get all pipelines (returns 404 that indicates an error)
getres, err := client.IngestGetPipeline().Do(context.TODO())
if err == nil {
t.Fatal(err)
}
if getres != nil {
t.Fatalf("expected no response, got %v", getres)
}
//*/
// Add a pipeline
pipelineDef := `{
"description" : "reset retweets",
"processors" : [
{
"set" : {
"field": "retweets",
"value": 0
}
}
]
}`
putres, err := client.IngestPutPipeline("my-pipeline").BodyString(pipelineDef).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if putres == nil {
t.Fatal("expected response, got nil")
}
if want, have := true, putres.Acknowledged; want != have {
t.Fatalf("expected ack = %v, got %v", want, have)
}
// Get all pipelines again
{
getres, err := client.IngestGetPipeline().Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if have := len(getres); have == 0 {
t.Fatalf("expected at least 1 pipeline, got %d", have)
}
pipeline, found := getres["my-pipeline"]
if !found {
t.Fatalf("expected to find pipline with id %q", "my-pipeline")
}
if want, have := "reset retweets", pipeline.Description; want != have {
t.Fatalf("expected pipeline description of %q, have %q", want, have)
}
}
// Get pipeline by ID
{
getres, err := client.IngestGetPipeline("my-pipeline").Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if want, have := 1, len(getres); want != have {
t.Fatalf("expected %d pipelines, got %d", want, have)
}
if _, found := getres["my-pipeline"]; !found {
t.Fatalf("expected to find pipline with id %q", "my-pipeline")
}
}
// Delete pipeline
delres, err := client.IngestDeletePipeline("my-pipeline").Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if delres == nil {
t.Fatal("expected response, got nil")
}
if want, have := true, delres.Acknowledged; want != have {
t.Fatalf("expected ack = %v, got %v", want, have)
}
}
================================================
FILE: ingest_put_pipeline.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// IngestPutPipelineService adds pipelines and updates existing pipelines in
// the cluster.
//
// It is documented at https://www.elastic.co/guide/en/elasticsearch/reference/7.0/put-pipeline-api.html.
type IngestPutPipelineService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
id string
masterTimeout string
timeout string
bodyJson interface{}
bodyString string
}
// NewIngestPutPipelineService creates a new IngestPutPipelineService.
func NewIngestPutPipelineService(client *Client) *IngestPutPipelineService {
return &IngestPutPipelineService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *IngestPutPipelineService) Pretty(pretty bool) *IngestPutPipelineService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *IngestPutPipelineService) Human(human bool) *IngestPutPipelineService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *IngestPutPipelineService) ErrorTrace(errorTrace bool) *IngestPutPipelineService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *IngestPutPipelineService) FilterPath(filterPath ...string) *IngestPutPipelineService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *IngestPutPipelineService) Header(name string, value string) *IngestPutPipelineService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *IngestPutPipelineService) Headers(headers http.Header) *IngestPutPipelineService {
s.headers = headers
return s
}
// Id is the pipeline ID.
func (s *IngestPutPipelineService) Id(id string) *IngestPutPipelineService {
s.id = id
return s
}
// MasterTimeout is an explicit operation timeout for connection to master node.
func (s *IngestPutPipelineService) MasterTimeout(masterTimeout string) *IngestPutPipelineService {
s.masterTimeout = masterTimeout
return s
}
// Timeout specifies an explicit operation timeout.
func (s *IngestPutPipelineService) Timeout(timeout string) *IngestPutPipelineService {
s.timeout = timeout
return s
}
// BodyJson is the ingest definition, defined as a JSON-serializable document.
// Use e.g. a map[string]interface{} here.
func (s *IngestPutPipelineService) BodyJson(body interface{}) *IngestPutPipelineService {
s.bodyJson = body
return s
}
// BodyString is the ingest definition, specified as a string.
func (s *IngestPutPipelineService) BodyString(body string) *IngestPutPipelineService {
s.bodyString = body
return s
}
// buildURL builds the URL for the operation.
func (s *IngestPutPipelineService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/_ingest/pipeline/{id}", map[string]string{
"id": s.id,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
if s.timeout != "" {
params.Set("timeout", s.timeout)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *IngestPutPipelineService) Validate() error {
var invalid []string
if s.id == "" {
invalid = append(invalid, "Id")
}
if s.bodyString == "" && s.bodyJson == nil {
invalid = append(invalid, "BodyJson")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *IngestPutPipelineService) Do(ctx context.Context) (*IngestPutPipelineResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Setup HTTP request body
var body interface{}
if s.bodyJson != nil {
body = s.bodyJson
} else {
body = s.bodyString
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "PUT",
Path: path,
Params: params,
Body: body,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(IngestPutPipelineResponse)
if err := json.Unmarshal(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// IngestPutPipelineResponse is the response of IngestPutPipelineService.Do.
type IngestPutPipelineResponse struct {
Acknowledged bool `json:"acknowledged"`
ShardsAcknowledged bool `json:"shards_acknowledged"`
Index string `json:"index,omitempty"`
}
================================================
FILE: ingest_put_pipeline_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import "testing"
func TestIngestPutPipelineURL(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tests := []struct {
Id string
Expected string
}{
{
"my-pipeline-id",
"/_ingest/pipeline/my-pipeline-id",
},
}
for _, test := range tests {
path, _, err := client.IngestPutPipeline(test.Id).buildURL()
if err != nil {
t.Fatal(err)
}
if path != test.Expected {
t.Errorf("expected %q; got: %q", test.Expected, path)
}
}
}
================================================
FILE: ingest_simulate_pipeline.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// IngestSimulatePipelineService executes a specific pipeline against the set of
// documents provided in the body of the request.
//
// The API is documented at
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/simulate-pipeline-api.html.
type IngestSimulatePipelineService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
id string
verbose *bool
bodyJson interface{}
bodyString string
}
// NewIngestSimulatePipelineService creates a new IngestSimulatePipeline.
func NewIngestSimulatePipelineService(client *Client) *IngestSimulatePipelineService {
return &IngestSimulatePipelineService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *IngestSimulatePipelineService) Pretty(pretty bool) *IngestSimulatePipelineService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *IngestSimulatePipelineService) Human(human bool) *IngestSimulatePipelineService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *IngestSimulatePipelineService) ErrorTrace(errorTrace bool) *IngestSimulatePipelineService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *IngestSimulatePipelineService) FilterPath(filterPath ...string) *IngestSimulatePipelineService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *IngestSimulatePipelineService) Header(name string, value string) *IngestSimulatePipelineService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *IngestSimulatePipelineService) Headers(headers http.Header) *IngestSimulatePipelineService {
s.headers = headers
return s
}
// Id specifies the pipeline ID.
func (s *IngestSimulatePipelineService) Id(id string) *IngestSimulatePipelineService {
s.id = id
return s
}
// Verbose mode. Display data output for each processor in executed pipeline.
func (s *IngestSimulatePipelineService) Verbose(verbose bool) *IngestSimulatePipelineService {
s.verbose = &verbose
return s
}
// BodyJson is the ingest definition, defined as a JSON-serializable simulate
// definition. Use e.g. a map[string]interface{} here.
func (s *IngestSimulatePipelineService) BodyJson(body interface{}) *IngestSimulatePipelineService {
s.bodyJson = body
return s
}
// BodyString is the simulate definition, defined as a string.
func (s *IngestSimulatePipelineService) BodyString(body string) *IngestSimulatePipelineService {
s.bodyString = body
return s
}
// buildURL builds the URL for the operation.
func (s *IngestSimulatePipelineService) buildURL() (string, url.Values, error) {
var err error
var path string
// Build URL
if s.id != "" {
path, err = uritemplates.Expand("/_ingest/pipeline/{id}/_simulate", map[string]string{
"id": s.id,
})
} else {
path = "/_ingest/pipeline/_simulate"
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if v := s.verbose; v != nil {
params.Set("verbose", fmt.Sprint(*v))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *IngestSimulatePipelineService) Validate() error {
var invalid []string
if s.bodyString == "" && s.bodyJson == nil {
invalid = append(invalid, "BodyJson")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *IngestSimulatePipelineService) Do(ctx context.Context) (*IngestSimulatePipelineResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Setup HTTP request body
var body interface{}
if s.bodyJson != nil {
body = s.bodyJson
} else {
body = s.bodyString
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "POST",
Path: path,
Params: params,
Body: body,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(IngestSimulatePipelineResponse)
if err := json.Unmarshal(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// IngestSimulatePipelineResponse is the response of IngestSimulatePipeline.Do.
type IngestSimulatePipelineResponse struct {
Docs []*IngestSimulateDocumentResult `json:"docs"`
}
type IngestSimulateDocumentResult struct {
Doc map[string]interface{} `json:"doc"`
ProcessorResults []*IngestSimulateProcessorResult `json:"processor_results"`
}
type IngestSimulateProcessorResult struct {
ProcessorTag string `json:"tag"`
Doc map[string]interface{} `json:"doc"`
}
================================================
FILE: ingest_simulate_pipeline_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import "testing"
func TestIngestSimulatePipelineURL(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tests := []struct {
Id string
Expected string
}{
{
"",
"/_ingest/pipeline/_simulate",
},
{
"my-pipeline-id",
"/_ingest/pipeline/my-pipeline-id/_simulate",
},
}
for _, test := range tests {
path, _, err := client.IngestSimulatePipeline().Id(test.Id).buildURL()
if err != nil {
t.Fatal(err)
}
if path != test.Expected {
t.Errorf("expected %q; got: %q", test.Expected, path)
}
}
}
================================================
FILE: inner_hit.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// InnerHit implements a simple join for parent/child, nested, and even
// top-level documents in Elasticsearch.
// It is an experimental feature for Elasticsearch versions 1.5 (or greater).
// See http://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-request-inner-hits.html
// for documentation.
//
// See the tests for SearchSource, HasChildFilter, HasChildQuery,
// HasParentFilter, HasParentQuery, NestedFilter, and NestedQuery
// for usage examples.
type InnerHit struct {
source *SearchSource
path string
typ string
name string
}
// NewInnerHit creates a new InnerHit.
func NewInnerHit() *InnerHit {
return &InnerHit{source: NewSearchSource()}
}
func (hit *InnerHit) Path(path string) *InnerHit {
hit.path = path
return hit
}
func (hit *InnerHit) Type(typ string) *InnerHit {
hit.typ = typ
return hit
}
func (hit *InnerHit) Query(query Query) *InnerHit {
hit.source.Query(query)
return hit
}
func (hit *InnerHit) Collapse(collapse *CollapseBuilder) *InnerHit {
hit.source.Collapse(collapse)
return hit
}
func (hit *InnerHit) From(from int) *InnerHit {
hit.source.From(from)
return hit
}
func (hit *InnerHit) Size(size int) *InnerHit {
hit.source.Size(size)
return hit
}
func (hit *InnerHit) TrackScores(trackScores bool) *InnerHit {
hit.source.TrackScores(trackScores)
return hit
}
func (hit *InnerHit) Explain(explain bool) *InnerHit {
hit.source.Explain(explain)
return hit
}
func (hit *InnerHit) Version(version bool) *InnerHit {
hit.source.Version(version)
return hit
}
func (hit *InnerHit) StoredField(storedFieldName string) *InnerHit {
hit.source.StoredField(storedFieldName)
return hit
}
func (hit *InnerHit) StoredFields(storedFieldNames ...string) *InnerHit {
hit.source.StoredFields(storedFieldNames...)
return hit
}
func (hit *InnerHit) NoStoredFields() *InnerHit {
hit.source.NoStoredFields()
return hit
}
func (hit *InnerHit) FetchSource(fetchSource bool) *InnerHit {
hit.source.FetchSource(fetchSource)
return hit
}
func (hit *InnerHit) FetchSourceContext(fetchSourceContext *FetchSourceContext) *InnerHit {
hit.source.FetchSourceContext(fetchSourceContext)
return hit
}
func (hit *InnerHit) DocvalueFields(docvalueFields ...string) *InnerHit {
hit.source.DocvalueFields(docvalueFields...)
return hit
}
func (hit *InnerHit) DocvalueFieldsWithFormat(docvalueFields ...DocvalueField) *InnerHit {
hit.source.DocvalueFieldsWithFormat(docvalueFields...)
return hit
}
func (hit *InnerHit) DocvalueField(docvalueField string) *InnerHit {
hit.source.DocvalueField(docvalueField)
return hit
}
func (hit *InnerHit) DocvalueFieldWithFormat(docvalueField DocvalueField) *InnerHit {
hit.source.DocvalueFieldWithFormat(docvalueField)
return hit
}
func (hit *InnerHit) ScriptFields(scriptFields ...*ScriptField) *InnerHit {
hit.source.ScriptFields(scriptFields...)
return hit
}
func (hit *InnerHit) ScriptField(scriptField *ScriptField) *InnerHit {
hit.source.ScriptField(scriptField)
return hit
}
func (hit *InnerHit) Sort(field string, ascending bool) *InnerHit {
hit.source.Sort(field, ascending)
return hit
}
func (hit *InnerHit) SortWithInfo(info SortInfo) *InnerHit {
hit.source.SortWithInfo(info)
return hit
}
func (hit *InnerHit) SortBy(sorter ...Sorter) *InnerHit {
hit.source.SortBy(sorter...)
return hit
}
func (hit *InnerHit) Highlight(highlight *Highlight) *InnerHit {
hit.source.Highlight(highlight)
return hit
}
func (hit *InnerHit) Highlighter() *Highlight {
return hit.source.Highlighter()
}
func (hit *InnerHit) Name(name string) *InnerHit {
hit.name = name
return hit
}
func (hit *InnerHit) Source() (interface{}, error) {
src, err := hit.source.Source()
if err != nil {
return nil, err
}
source, ok := src.(map[string]interface{})
if !ok {
return nil, nil
}
// Notice that hit.typ and hit.path are not exported here.
// They are only used with SearchSource and serialized there.
if hit.name != "" {
source["name"] = hit.name
}
return source, nil
}
================================================
FILE: inner_hit_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestInnerHitEmpty(t *testing.T) {
hit := NewInnerHit()
src, err := hit.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestInnerHitWithName(t *testing.T) {
hit := NewInnerHit().Name("comments")
src, err := hit.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"name":"comments"}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestInnerHitSecondLevelCollapse(t *testing.T) {
hit := NewInnerHit().Name("by_location").Size(3).Collapse(NewCollapseBuilder("user.id"))
src, err := hit.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"collapse":{"field":"user.id"},"name":"by_location","size":3}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: logger.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// Logger specifies the interface for all log operations.
type Logger interface {
Printf(format string, v ...interface{})
}
================================================
FILE: mget.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
)
// MgetService allows to get multiple documents based on an index,
// type (optional) and id (possibly routing). The response includes
// a docs array with all the fetched documents, each element similar
// in structure to a document provided by the Get API.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-multi-get.html
// for details.
type MgetService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
preference string
realtime *bool
refresh string
routing string
storedFields []string
items []*MultiGetItem
}
// NewMgetService initializes a new Multi GET API request call.
func NewMgetService(client *Client) *MgetService {
builder := &MgetService{
client: client,
}
return builder
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *MgetService) Pretty(pretty bool) *MgetService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *MgetService) Human(human bool) *MgetService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *MgetService) ErrorTrace(errorTrace bool) *MgetService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *MgetService) FilterPath(filterPath ...string) *MgetService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *MgetService) Header(name string, value string) *MgetService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *MgetService) Headers(headers http.Header) *MgetService {
s.headers = headers
return s
}
// Preference specifies the node or shard the operation should be performed
// on (default: random).
func (s *MgetService) Preference(preference string) *MgetService {
s.preference = preference
return s
}
// Refresh the shard containing the document before performing the operation.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-refresh.html
// for details.
func (s *MgetService) Refresh(refresh string) *MgetService {
s.refresh = refresh
return s
}
// Realtime specifies whether to perform the operation in realtime or search mode.
func (s *MgetService) Realtime(realtime bool) *MgetService {
s.realtime = &realtime
return s
}
// Routing is the specific routing value.
func (s *MgetService) Routing(routing string) *MgetService {
s.routing = routing
return s
}
// StoredFields is a list of fields to return in the response.
func (s *MgetService) StoredFields(storedFields ...string) *MgetService {
s.storedFields = append(s.storedFields, storedFields...)
return s
}
// Add an item to the request.
func (s *MgetService) Add(items ...*MultiGetItem) *MgetService {
s.items = append(s.items, items...)
return s
}
// Source returns the request body, which will be serialized into JSON.
func (s *MgetService) Source() (interface{}, error) {
source := make(map[string]interface{})
items := make([]interface{}, len(s.items))
for i, item := range s.items {
src, err := item.Source()
if err != nil {
return nil, err
}
items[i] = src
}
source["docs"] = items
return source, nil
}
// Do executes the request.
func (s *MgetService) Do(ctx context.Context) (*MgetResponse, error) {
// Build url
path := "/_mget"
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.realtime != nil {
params.Add("realtime", fmt.Sprintf("%v", *s.realtime))
}
if s.preference != "" {
params.Add("preference", s.preference)
}
if s.refresh != "" {
params.Add("refresh", s.refresh)
}
if s.routing != "" {
params.Set("routing", s.routing)
}
if len(s.storedFields) > 0 {
params.Set("stored_fields", strings.Join(s.storedFields, ","))
}
// Set body
body, err := s.Source()
if err != nil {
return nil, err
}
// Get response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Body: body,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return result
ret := new(MgetResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// -- Multi Get Item --
// MultiGetItem is a single document to retrieve via the MgetService.
type MultiGetItem struct {
index string
typ string
id string
routing string
storedFields []string
version *int64 // see org.elasticsearch.common.lucene.uid.Versions
versionType string // see org.elasticsearch.index.VersionType
fsc *FetchSourceContext
}
// NewMultiGetItem initializes a new, single item for a Multi GET request.
func NewMultiGetItem() *MultiGetItem {
return &MultiGetItem{}
}
// Index specifies the index name.
func (item *MultiGetItem) Index(index string) *MultiGetItem {
item.index = index
return item
}
// Type specifies the type name.
func (item *MultiGetItem) Type(typ string) *MultiGetItem {
item.typ = typ
return item
}
// Id specifies the identifier of the document.
func (item *MultiGetItem) Id(id string) *MultiGetItem {
item.id = id
return item
}
// Routing is the specific routing value.
func (item *MultiGetItem) Routing(routing string) *MultiGetItem {
item.routing = routing
return item
}
// StoredFields is a list of fields to return in the response.
func (item *MultiGetItem) StoredFields(storedFields ...string) *MultiGetItem {
item.storedFields = append(item.storedFields, storedFields...)
return item
}
// Version can be MatchAny (-3), MatchAnyPre120 (0), NotFound (-1),
// or NotSet (-2). These are specified in org.elasticsearch.common.lucene.uid.Versions.
// The default in Elasticsearch is MatchAny (-3).
func (item *MultiGetItem) Version(version int64) *MultiGetItem {
item.version = &version
return item
}
// VersionType can be "internal", "external", "external_gt", or "external_gte".
// See org.elasticsearch.index.VersionType in Elasticsearch source.
// It is "internal" by default.
func (item *MultiGetItem) VersionType(versionType string) *MultiGetItem {
item.versionType = versionType
return item
}
// FetchSource allows to specify source filtering.
func (item *MultiGetItem) FetchSource(fetchSourceContext *FetchSourceContext) *MultiGetItem {
item.fsc = fetchSourceContext
return item
}
// Source returns the serialized JSON to be sent to Elasticsearch as
// part of a MultiGet search.
func (item *MultiGetItem) Source() (interface{}, error) {
source := make(map[string]interface{})
source["_id"] = item.id
if item.index != "" {
source["_index"] = item.index
}
if item.typ != "" {
source["_type"] = item.typ
}
if item.fsc != nil {
src, err := item.fsc.Source()
if err != nil {
return nil, err
}
source["_source"] = src
}
if item.routing != "" {
source["routing"] = item.routing
}
if len(item.storedFields) > 0 {
source["stored_fields"] = strings.Join(item.storedFields, ",")
}
if item.version != nil {
source["version"] = fmt.Sprintf("%d", *item.version)
}
if item.versionType != "" {
source["version_type"] = item.versionType
}
return source, nil
}
// -- Result of a Multi Get request.
// MgetResponse is the outcome of a Multi GET API request.
type MgetResponse struct {
Docs []*GetResult `json:"docs,omitempty"`
}
================================================
FILE: mget_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"testing"
)
func TestMultiGet(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
tweet2 := tweet{User: "olivere", Message: "Another unrelated topic."}
tweet3 := tweet{User: "sandrae", Message: "Cycling is fun."}
// Add some documents
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("3").BodyJson(&tweet3).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Count documents
count, err := client.Count(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if count != 3 {
t.Errorf("expected Count = %d; got %d", 3, count)
}
// Get documents 1 and 3
res, err := client.MultiGet().
Add(NewMultiGetItem().Index(testIndexName).Id("1")).
Add(NewMultiGetItem().Index(testIndexName).Id("3")).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected result to be != nil; got nil")
}
if res.Docs == nil {
t.Fatal("expected result docs to be != nil; got nil")
}
if len(res.Docs) != 2 {
t.Fatalf("expected to have 2 docs; got %d", len(res.Docs))
}
item := res.Docs[0]
if item.Error != nil {
t.Errorf("expected no error on item 0; got %v", item.Error)
}
if item.Source == nil {
t.Errorf("expected Source != nil; got %v", item.Source)
}
var doc tweet
if err := json.Unmarshal(item.Source, &doc); err != nil {
t.Fatalf("expected to unmarshal item Source; got %v", err)
}
if doc.Message != tweet1.Message {
t.Errorf("expected Message of first tweet to be %q; got %q", tweet1.Message, doc.Message)
}
item = res.Docs[1]
if item.Error != nil {
t.Errorf("expected no error on item 1; got %v", item.Error)
}
if item.Source == nil {
t.Errorf("expected Source != nil; got %v", item.Source)
}
if err := json.Unmarshal(item.Source, &doc); err != nil {
t.Fatalf("expected to unmarshal item Source; got %v", err)
}
if doc.Message != tweet3.Message {
t.Errorf("expected Message of second tweet to be %q; got %q", tweet3.Message, doc.Message)
}
}
================================================
FILE: msearch.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
)
// MultiSearch executes one or more searches in one roundtrip.
type MultiSearchService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
requests []*SearchRequest
indices []string
maxConcurrentRequests *int
preFilterShardSize *int
}
func NewMultiSearchService(client *Client) *MultiSearchService {
builder := &MultiSearchService{
client: client,
}
return builder
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *MultiSearchService) Pretty(pretty bool) *MultiSearchService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *MultiSearchService) Human(human bool) *MultiSearchService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *MultiSearchService) ErrorTrace(errorTrace bool) *MultiSearchService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *MultiSearchService) FilterPath(filterPath ...string) *MultiSearchService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *MultiSearchService) Header(name string, value string) *MultiSearchService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *MultiSearchService) Headers(headers http.Header) *MultiSearchService {
s.headers = headers
return s
}
func (s *MultiSearchService) Add(requests ...*SearchRequest) *MultiSearchService {
s.requests = append(s.requests, requests...)
return s
}
func (s *MultiSearchService) Index(indices ...string) *MultiSearchService {
s.indices = append(s.indices, indices...)
return s
}
func (s *MultiSearchService) MaxConcurrentSearches(max int) *MultiSearchService {
s.maxConcurrentRequests = &max
return s
}
func (s *MultiSearchService) PreFilterShardSize(size int) *MultiSearchService {
s.preFilterShardSize = &size
return s
}
func (s *MultiSearchService) Do(ctx context.Context) (*MultiSearchResult, error) {
// Build url
path := "/_msearch"
// Parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if v := s.maxConcurrentRequests; v != nil {
params.Set("max_concurrent_searches", fmt.Sprintf("%v", *v))
}
if v := s.preFilterShardSize; v != nil {
params.Set("pre_filter_shard_size", fmt.Sprintf("%v", *v))
}
// Set body
var lines []string
for _, sr := range s.requests {
// Set default indices if not specified in the request
if !sr.HasIndices() && len(s.indices) > 0 {
sr = sr.Index(s.indices...)
}
header, err := json.Marshal(sr.header())
if err != nil {
return nil, err
}
body, err := sr.Body()
if err != nil {
return nil, err
}
lines = append(lines, string(header))
lines = append(lines, body)
}
body := strings.Join(lines, "\n") + "\n" // add trailing \n
// Get response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Body: body,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return result
ret := new(MultiSearchResult)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// MultiSearchResult is the outcome of running a multi-search operation.
type MultiSearchResult struct {
TookInMillis int64 `json:"took,omitempty"` // search time in milliseconds
Responses []*SearchResult `json:"responses,omitempty"`
}
================================================
FILE: msearch_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
_ "net/http"
"testing"
)
func TestMultiSearch(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
// client := setupTestClientAndCreateIndexAndLog(t)
tweet1 := tweet{
User: "olivere",
Message: "Welcome to Golang and Elasticsearch.",
Tags: []string{"golang", "elasticsearch"},
}
tweet2 := tweet{
User: "olivere",
Message: "Another unrelated topic.",
Tags: []string{"golang"},
}
tweet3 := tweet{
User: "sandrae",
Message: "Cycling is fun.",
Tags: []string{"sports", "cycling"},
}
// Add all documents
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("3").BodyJson(&tweet3).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Spawn two search queries with one roundtrip
q1 := NewMatchAllQuery()
q2 := NewTermQuery("tags", "golang")
sreq1 := NewSearchRequest().Index(testIndexName, testIndexName2).
Source(NewSearchSource().Query(q1).Size(10))
sreq2 := NewSearchRequest().Index(testIndexName).
Source(NewSearchSource().Query(q2))
searchResult, err := client.MultiSearch().
Add(sreq1, sreq2).
Pretty(true).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.Responses == nil {
t.Fatal("expected responses != nil; got nil")
}
if len(searchResult.Responses) != 2 {
t.Fatalf("expected 2 responses; got %d", len(searchResult.Responses))
}
sres := searchResult.Responses[0]
if sres.Hits == nil {
t.Errorf("expected Hits != nil; got nil")
}
if sres.TotalHits() != 3 {
t.Errorf("expected TotalHits() = %d; got %d", 3, sres.TotalHits())
}
if len(sres.Hits.Hits) != 3 {
t.Errorf("expected len(Hits.Hits) = %d; got %d", 3, len(sres.Hits.Hits))
}
for _, hit := range sres.Hits.Hits {
if hit.Index != testIndexName {
t.Errorf("expected Hits.Hit.Index = %q; got %q", testIndexName, hit.Index)
}
item := make(map[string]interface{})
err := json.Unmarshal(hit.Source, &item)
if err != nil {
t.Fatal(err)
}
}
sres = searchResult.Responses[1]
if sres.Hits == nil {
t.Errorf("expected Hits != nil; got nil")
}
if sres.TotalHits() != 2 {
t.Errorf("expected TotalHits() = %d; got %d", 2, sres.TotalHits())
}
if len(sres.Hits.Hits) != 2 {
t.Errorf("expected len(Hits.Hits) = %d; got %d", 2, len(sres.Hits.Hits))
}
for _, hit := range sres.Hits.Hits {
if hit.Index != testIndexName {
t.Errorf("expected Hits.Hit.Index = %q; got %q", testIndexName, hit.Index)
}
item := make(map[string]interface{})
err := json.Unmarshal(hit.Source, &item)
if err != nil {
t.Fatal(err)
}
}
}
func TestMultiSearchWithStrings(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
// client := setupTestClientAndCreateIndexAndLog(t)
tweet1 := tweet{
User: "olivere",
Message: "Welcome to Golang and Elasticsearch.",
Tags: []string{"golang", "elasticsearch"},
}
tweet2 := tweet{
User: "olivere",
Message: "Another unrelated topic.",
Tags: []string{"golang"},
}
tweet3 := tweet{
User: "sandrae",
Message: "Cycling is fun.",
Tags: []string{"sports", "cycling"},
}
// Add all documents
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("3").BodyJson(&tweet3).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Spawn two search queries with one roundtrip
sreq1 := NewSearchRequest().Index(testIndexName, testIndexName2).
Source(`{"query":{"match_all":{}}}`)
sreq2 := NewSearchRequest().Index(testIndexName).
Source(`{"query":{"term":{"tags":"golang"}}}`)
searchResult, err := client.MultiSearch().
Add(sreq1, sreq2).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.Responses == nil {
t.Fatal("expected responses != nil; got nil")
}
if len(searchResult.Responses) != 2 {
t.Fatalf("expected 2 responses; got %d", len(searchResult.Responses))
}
sres := searchResult.Responses[0]
if sres.Hits == nil {
t.Errorf("expected Hits != nil; got nil")
}
if sres.TotalHits() != 3 {
t.Errorf("expected TotalHits() = %d; got %d", 3, sres.TotalHits())
}
if len(sres.Hits.Hits) != 3 {
t.Errorf("expected len(Hits.Hits) = %d; got %d", 3, len(sres.Hits.Hits))
}
for _, hit := range sres.Hits.Hits {
if hit.Index != testIndexName {
t.Errorf("expected Hits.Hit.Index = %q; got %q", testIndexName, hit.Index)
}
item := make(map[string]interface{})
err := json.Unmarshal(hit.Source, &item)
if err != nil {
t.Fatal(err)
}
}
sres = searchResult.Responses[1]
if sres.Hits == nil {
t.Errorf("expected Hits != nil; got nil")
}
if sres.TotalHits() != 2 {
t.Errorf("expected TotalHits() = %d; got %d", 2, sres.TotalHits())
}
if len(sres.Hits.Hits) != 2 {
t.Errorf("expected len(Hits.Hits) = %d; got %d", 2, len(sres.Hits.Hits))
}
for _, hit := range sres.Hits.Hits {
if hit.Index != testIndexName {
t.Errorf("expected Hits.Hit.Index = %q; got %q", testIndexName, hit.Index)
}
item := make(map[string]interface{})
err := json.Unmarshal(hit.Source, &item)
if err != nil {
t.Fatal(err)
}
}
}
func TestMultiSearchWithOneRequest(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tweet1 := tweet{
User: "olivere",
Message: "Welcome to Golang and Elasticsearch.",
Tags: []string{"golang", "elasticsearch"},
}
tweet2 := tweet{
User: "olivere",
Message: "Another unrelated topic.",
Tags: []string{"golang"},
}
tweet3 := tweet{
User: "sandrae",
Message: "Cycling is fun.",
Tags: []string{"sports", "cycling"},
}
// Add all documents
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("3").BodyJson(&tweet3).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Spawn two search queries with one roundtrip
query := NewMatchAllQuery()
source := NewSearchSource().Query(query).Size(10)
sreq := NewSearchRequest().Source(source)
searchResult, err := client.MultiSearch().
Index(testIndexName).
Add(sreq).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.Responses == nil {
t.Fatal("expected responses != nil; got nil")
}
if len(searchResult.Responses) != 1 {
t.Fatalf("expected 1 responses; got %d", len(searchResult.Responses))
}
sres := searchResult.Responses[0]
if sres.Hits == nil {
t.Errorf("expected Hits != nil; got nil")
}
if sres.TotalHits() != 3 {
t.Errorf("expected TotalHits() = %d; got %d", 3, sres.TotalHits())
}
if len(sres.Hits.Hits) != 3 {
t.Errorf("expected len(Hits.Hits) = %d; got %d", 3, len(sres.Hits.Hits))
}
for _, hit := range sres.Hits.Hits {
if hit.Index != testIndexName {
t.Errorf("expected Hits.Hit.Index = %q; got %q", testIndexName, hit.Index)
}
item := make(map[string]interface{})
err := json.Unmarshal(hit.Source, &item)
if err != nil {
t.Fatal(err)
}
}
}
================================================
FILE: mtermvectors.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// MultiTermvectorService returns information and statistics on terms in the
// fields of a particular document. The document could be stored in the
// index or artificially provided by the user.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-multi-termvectors.html
// for documentation.
type MultiTermvectorService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
index string
typ string
fieldStatistics *bool
fields []string
ids []string
offsets *bool
parent string
payloads *bool
positions *bool
preference string
realtime *bool
routing string
termStatistics *bool
version interface{}
versionType string
bodyJson interface{}
bodyString string
docs []*MultiTermvectorItem
}
// NewMultiTermvectorService creates a new MultiTermvectorService.
func NewMultiTermvectorService(client *Client) *MultiTermvectorService {
return &MultiTermvectorService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *MultiTermvectorService) Pretty(pretty bool) *MultiTermvectorService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *MultiTermvectorService) Human(human bool) *MultiTermvectorService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *MultiTermvectorService) ErrorTrace(errorTrace bool) *MultiTermvectorService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *MultiTermvectorService) FilterPath(filterPath ...string) *MultiTermvectorService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *MultiTermvectorService) Header(name string, value string) *MultiTermvectorService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *MultiTermvectorService) Headers(headers http.Header) *MultiTermvectorService {
s.headers = headers
return s
}
// Add adds documents to MultiTermvectors service.
func (s *MultiTermvectorService) Add(docs ...*MultiTermvectorItem) *MultiTermvectorService {
s.docs = append(s.docs, docs...)
return s
}
// Index in which the document resides.
func (s *MultiTermvectorService) Index(index string) *MultiTermvectorService {
s.index = index
return s
}
// Type of the document.
func (s *MultiTermvectorService) Type(typ string) *MultiTermvectorService {
s.typ = typ
return s
}
// FieldStatistics specifies if document count, sum of document frequencies and sum of total term frequencies should be returned. Applies to all returned documents unless otherwise specified in body "params" or "docs".
func (s *MultiTermvectorService) FieldStatistics(fieldStatistics bool) *MultiTermvectorService {
s.fieldStatistics = &fieldStatistics
return s
}
// Fields is a comma-separated list of fields to return. Applies to all returned documents unless otherwise specified in body "params" or "docs".
func (s *MultiTermvectorService) Fields(fields []string) *MultiTermvectorService {
s.fields = fields
return s
}
// Ids is a comma-separated list of documents ids. You must define ids as parameter or set "ids" or "docs" in the request body.
func (s *MultiTermvectorService) Ids(ids []string) *MultiTermvectorService {
s.ids = ids
return s
}
// Offsets specifies if term offsets should be returned. Applies to all returned documents unless otherwise specified in body "params" or "docs".
func (s *MultiTermvectorService) Offsets(offsets bool) *MultiTermvectorService {
s.offsets = &offsets
return s
}
// Parent id of documents. Applies to all returned documents unless otherwise specified in body "params" or "docs".
func (s *MultiTermvectorService) Parent(parent string) *MultiTermvectorService {
s.parent = parent
return s
}
// Payloads specifies if term payloads should be returned. Applies to all returned documents unless otherwise specified in body "params" or "docs".
func (s *MultiTermvectorService) Payloads(payloads bool) *MultiTermvectorService {
s.payloads = &payloads
return s
}
// Positions specifies if term positions should be returned. Applies to all returned documents unless otherwise specified in body "params" or "docs".
func (s *MultiTermvectorService) Positions(positions bool) *MultiTermvectorService {
s.positions = &positions
return s
}
// Preference specifies the node or shard the operation should be performed on (default: random). Applies to all returned documents unless otherwise specified in body "params" or "docs".
func (s *MultiTermvectorService) Preference(preference string) *MultiTermvectorService {
s.preference = preference
return s
}
// Realtime specifies if requests are real-time as opposed to near-real-time (default: true).
func (s *MultiTermvectorService) Realtime(realtime bool) *MultiTermvectorService {
s.realtime = &realtime
return s
}
// Routing specific routing value. Applies to all returned documents unless otherwise specified in body "params" or "docs".
func (s *MultiTermvectorService) Routing(routing string) *MultiTermvectorService {
s.routing = routing
return s
}
// TermStatistics specifies if total term frequency and document frequency should be returned. Applies to all returned documents unless otherwise specified in body "params" or "docs".
func (s *MultiTermvectorService) TermStatistics(termStatistics bool) *MultiTermvectorService {
s.termStatistics = &termStatistics
return s
}
// Version is explicit version number for concurrency control.
func (s *MultiTermvectorService) Version(version interface{}) *MultiTermvectorService {
s.version = version
return s
}
// VersionType is specific version type.
func (s *MultiTermvectorService) VersionType(versionType string) *MultiTermvectorService {
s.versionType = versionType
return s
}
// BodyJson is documented as: Define ids, documents, parameters or a list of parameters per document here. You must at least provide a list of document ids. See documentation..
func (s *MultiTermvectorService) BodyJson(body interface{}) *MultiTermvectorService {
s.bodyJson = body
return s
}
// BodyString is documented as: Define ids, documents, parameters or a list of parameters per document here. You must at least provide a list of document ids. See documentation..
func (s *MultiTermvectorService) BodyString(body string) *MultiTermvectorService {
s.bodyString = body
return s
}
func (s *MultiTermvectorService) Source() interface{} {
source := make(map[string]interface{})
docs := make([]interface{}, len(s.docs))
for i, doc := range s.docs {
docs[i] = doc.Source()
}
source["docs"] = docs
return source
}
// buildURL builds the URL for the operation.
func (s *MultiTermvectorService) buildURL() (string, url.Values, error) {
var path string
var err error
if s.index != "" && s.typ != "" {
path, err = uritemplates.Expand("/{index}/{type}/_mtermvectors", map[string]string{
"index": s.index,
"type": s.typ,
})
} else if s.index != "" && s.typ == "" {
path, err = uritemplates.Expand("/{index}/_mtermvectors", map[string]string{
"index": s.index,
})
} else {
path = "/_mtermvectors"
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.fieldStatistics != nil {
params.Set("field_statistics", fmt.Sprintf("%v", *s.fieldStatistics))
}
if len(s.fields) > 0 {
params.Set("fields", strings.Join(s.fields, ","))
}
if len(s.ids) > 0 {
params.Set("ids", strings.Join(s.ids, ","))
}
if s.offsets != nil {
params.Set("offsets", fmt.Sprintf("%v", *s.offsets))
}
if s.parent != "" {
params.Set("parent", s.parent)
}
if s.payloads != nil {
params.Set("payloads", fmt.Sprintf("%v", *s.payloads))
}
if s.positions != nil {
params.Set("positions", fmt.Sprintf("%v", *s.positions))
}
if s.preference != "" {
params.Set("preference", s.preference)
}
if s.realtime != nil {
params.Set("realtime", fmt.Sprintf("%v", *s.realtime))
}
if s.routing != "" {
params.Set("routing", s.routing)
}
if s.termStatistics != nil {
params.Set("term_statistics", fmt.Sprintf("%v", *s.termStatistics))
}
if s.version != nil {
params.Set("version", fmt.Sprintf("%v", s.version))
}
if s.versionType != "" {
params.Set("version_type", s.versionType)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *MultiTermvectorService) Validate() error {
var invalid []string
if s.index == "" && s.typ != "" {
invalid = append(invalid, "Index")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *MultiTermvectorService) Do(ctx context.Context) (*MultiTermvectorResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Setup HTTP request body
var body interface{}
if s.bodyJson != nil {
body = s.bodyJson
} else if len(s.bodyString) > 0 {
body = s.bodyString
} else {
body = s.Source()
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Body: body,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(MultiTermvectorResponse)
if err := json.Unmarshal(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// MultiTermvectorResponse is the response of MultiTermvectorService.Do.
type MultiTermvectorResponse struct {
Docs []*TermvectorsResponse `json:"docs"`
}
// -- MultiTermvectorItem --
// MultiTermvectorItem is a single document to retrieve via MultiTermvectorService.
type MultiTermvectorItem struct {
index string
typ string
id string
doc interface{}
fieldStatistics *bool
fields []string
perFieldAnalyzer map[string]string
offsets *bool
parent string
payloads *bool
positions *bool
preference string
realtime *bool
routing string
termStatistics *bool
}
func NewMultiTermvectorItem() *MultiTermvectorItem {
return &MultiTermvectorItem{}
}
func (s *MultiTermvectorItem) Index(index string) *MultiTermvectorItem {
s.index = index
return s
}
func (s *MultiTermvectorItem) Type(typ string) *MultiTermvectorItem {
s.typ = typ
return s
}
func (s *MultiTermvectorItem) Id(id string) *MultiTermvectorItem {
s.id = id
return s
}
// Doc is the document to analyze.
func (s *MultiTermvectorItem) Doc(doc interface{}) *MultiTermvectorItem {
s.doc = doc
return s
}
// FieldStatistics specifies if document count, sum of document frequencies
// and sum of total term frequencies should be returned.
func (s *MultiTermvectorItem) FieldStatistics(fieldStatistics bool) *MultiTermvectorItem {
s.fieldStatistics = &fieldStatistics
return s
}
// Fields a list of fields to return.
func (s *MultiTermvectorItem) Fields(fields ...string) *MultiTermvectorItem {
if s.fields == nil {
s.fields = make([]string, 0)
}
s.fields = append(s.fields, fields...)
return s
}
// PerFieldAnalyzer allows to specify a different analyzer than the one
// at the field.
func (s *MultiTermvectorItem) PerFieldAnalyzer(perFieldAnalyzer map[string]string) *MultiTermvectorItem {
s.perFieldAnalyzer = perFieldAnalyzer
return s
}
// Offsets specifies if term offsets should be returned.
func (s *MultiTermvectorItem) Offsets(offsets bool) *MultiTermvectorItem {
s.offsets = &offsets
return s
}
// Parent id of documents.
func (s *MultiTermvectorItem) Parent(parent string) *MultiTermvectorItem {
s.parent = parent
return s
}
// Payloads specifies if term payloads should be returned.
func (s *MultiTermvectorItem) Payloads(payloads bool) *MultiTermvectorItem {
s.payloads = &payloads
return s
}
// Positions specifies if term positions should be returned.
func (s *MultiTermvectorItem) Positions(positions bool) *MultiTermvectorItem {
s.positions = &positions
return s
}
// Preference specify the node or shard the operation
// should be performed on (default: random).
func (s *MultiTermvectorItem) Preference(preference string) *MultiTermvectorItem {
s.preference = preference
return s
}
// Realtime specifies if request is real-time as opposed to
// near-real-time (default: true).
func (s *MultiTermvectorItem) Realtime(realtime bool) *MultiTermvectorItem {
s.realtime = &realtime
return s
}
// Routing is a specific routing value.
func (s *MultiTermvectorItem) Routing(routing string) *MultiTermvectorItem {
s.routing = routing
return s
}
// TermStatistics specifies if total term frequency and document frequency
// should be returned.
func (s *MultiTermvectorItem) TermStatistics(termStatistics bool) *MultiTermvectorItem {
s.termStatistics = &termStatistics
return s
}
// Source returns the serialized JSON to be sent to Elasticsearch as
// part of a MultiTermvector.
func (s *MultiTermvectorItem) Source() interface{} {
source := make(map[string]interface{})
source["_id"] = s.id
if s.index != "" {
source["_index"] = s.index
}
if s.typ != "" {
source["_type"] = s.typ
}
if s.fields != nil {
source["fields"] = s.fields
}
if s.fieldStatistics != nil {
source["field_statistics"] = fmt.Sprintf("%v", *s.fieldStatistics)
}
if s.offsets != nil {
source["offsets"] = s.offsets
}
if s.parent != "" {
source["parent"] = s.parent
}
if s.payloads != nil {
source["payloads"] = fmt.Sprintf("%v", *s.payloads)
}
if s.positions != nil {
source["positions"] = fmt.Sprintf("%v", *s.positions)
}
if s.preference != "" {
source["preference"] = s.preference
}
if s.realtime != nil {
source["realtime"] = fmt.Sprintf("%v", *s.realtime)
}
if s.routing != "" {
source["routing"] = s.routing
}
if s.termStatistics != nil {
source["term_statistics"] = fmt.Sprintf("%v", *s.termStatistics)
}
if s.doc != nil {
source["doc"] = s.doc
}
if s.perFieldAnalyzer != nil && len(s.perFieldAnalyzer) > 0 {
source["per_field_analyzer"] = s.perFieldAnalyzer
}
return source
}
================================================
FILE: mtermvectors_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestMultiTermVectorsValidateAndBuildURL(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tests := []struct {
Index string
Type string
Expected string
ExpectValidateFailure bool
}{
// #0: No index, no type
{
"",
"",
"/_mtermvectors",
false,
},
// #1: Index only
{
"twitter",
"",
"/twitter/_mtermvectors",
false,
},
// #2: Type without index
{
"",
"doc",
"",
true,
},
// #3: Both index and type
{
"twitter",
"doc",
"/twitter/doc/_mtermvectors",
false,
},
}
for i, test := range tests {
builder := client.MultiTermVectors().Index(test.Index).Type(test.Type)
// Validate
err := builder.Validate()
if err != nil {
if !test.ExpectValidateFailure {
t.Errorf("#%d: expected no error, got: %v", i, err)
continue
}
} else {
if test.ExpectValidateFailure {
t.Errorf("#%d: expected error, got: nil", i)
continue
}
// Build
path, _, err := builder.buildURL()
if err != nil {
t.Errorf("#%d: expected no error, got: %v", i, err)
continue
}
if path != test.Expected {
t.Errorf("#%d: expected %q; got: %q", i, test.Expected, path)
}
}
}
}
func TestMultiTermVectorsWithIds(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
tweet2 := tweet{User: "olivere", Message: "Another unrelated topic."}
tweet3 := tweet{User: "sandrae", Message: "Cycling is fun."}
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("3").BodyJson(&tweet3).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Count documents
count, err := client.Count(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if count != 3 {
t.Errorf("expected Count = %d; got %d", 3, count)
}
// MultiTermVectors by specifying ID by 1 and 3
field := "Message"
res, err := client.MultiTermVectors().
Index(testIndexName).
Add(NewMultiTermvectorItem().Index(testIndexName).Id("1").Fields(field)).
Add(NewMultiTermvectorItem().Index(testIndexName).Id("3").Fields(field)).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected to return information and statistics")
}
if res.Docs == nil {
t.Fatal("expected result docs to be != nil; got nil")
}
if len(res.Docs) != 2 {
t.Fatalf("expected to have 2 docs; got %d", len(res.Docs))
}
}
================================================
FILE: nodes_info.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"time"
"github.com/olivere/elastic/v7/uritemplates"
)
// NodesInfoService allows to retrieve one or more or all of the
// cluster nodes information.
// It is documented at https://www.elastic.co/guide/en/elasticsearch/reference/7.0/cluster-nodes-info.html.
type NodesInfoService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
nodeId []string
metric []string
flatSettings *bool
}
// NewNodesInfoService creates a new NodesInfoService.
func NewNodesInfoService(client *Client) *NodesInfoService {
return &NodesInfoService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *NodesInfoService) Pretty(pretty bool) *NodesInfoService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *NodesInfoService) Human(human bool) *NodesInfoService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *NodesInfoService) ErrorTrace(errorTrace bool) *NodesInfoService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *NodesInfoService) FilterPath(filterPath ...string) *NodesInfoService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *NodesInfoService) Header(name string, value string) *NodesInfoService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *NodesInfoService) Headers(headers http.Header) *NodesInfoService {
s.headers = headers
return s
}
// NodeId is a list of node IDs or names to limit the returned information.
// Use "_local" to return information from the node you're connecting to,
// leave empty to get information from all nodes.
func (s *NodesInfoService) NodeId(nodeId ...string) *NodesInfoService {
s.nodeId = append(s.nodeId, nodeId...)
return s
}
// Metric is a list of metrics you wish returned. Leave empty to return all.
// Valid metrics are: settings, os, process, jvm, thread_pool, network,
// transport, http, and plugins.
func (s *NodesInfoService) Metric(metric ...string) *NodesInfoService {
s.metric = append(s.metric, metric...)
return s
}
// FlatSettings returns settings in flat format (default: false).
func (s *NodesInfoService) FlatSettings(flatSettings bool) *NodesInfoService {
s.flatSettings = &flatSettings
return s
}
// buildURL builds the URL for the operation.
func (s *NodesInfoService) buildURL() (string, url.Values, error) {
var nodeId, metric string
if len(s.nodeId) > 0 {
nodeId = strings.Join(s.nodeId, ",")
} else {
nodeId = "_all"
}
if len(s.metric) > 0 {
metric = strings.Join(s.metric, ",")
} else {
metric = "_all"
}
// Build URL
path, err := uritemplates.Expand("/_nodes/{node_id}/{metric}", map[string]string{
"node_id": nodeId,
"metric": metric,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.flatSettings != nil {
params.Set("flat_settings", fmt.Sprintf("%v", *s.flatSettings))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *NodesInfoService) Validate() error {
return nil
}
// Do executes the operation.
func (s *NodesInfoService) Do(ctx context.Context) (*NodesInfoResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(NodesInfoResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// NodesInfoResponse is the response of NodesInfoService.Do.
type NodesInfoResponse struct {
ClusterName string `json:"cluster_name"`
Nodes map[string]*NodesInfoNode `json:"nodes"`
}
// NodesInfoNode represents information about a node in the cluster.
type NodesInfoNode struct {
// Name of the node, e.g. "Mister Fear"
Name string `json:"name"`
// TransportAddress, e.g. "127.0.0.1:9300"
TransportAddress string `json:"transport_address"`
// Host is the host name, e.g. "macbookair"
Host string `json:"host"`
// IP is the IP address, e.g. "192.168.1.2"
IP string `json:"ip"`
// Version is the Elasticsearch version running on the node, e.g. "1.4.3"
Version string `json:"version"`
// BuildHash is the Elasticsearch build bash, e.g. "36a29a7"
BuildHash string `json:"build_hash"`
// TotalIndexingBuffer represents the total heap allowed to be used to
// hold recently indexed documents before they must be written to disk.
TotalIndexingBuffer int64 `json:"total_indexing_buffer"` // e.g. 16gb
// TotalIndexingBufferInBytes is the same as TotalIndexingBuffer, but
// expressed in bytes.
TotalIndexingBufferInBytes string `json:"total_indexing_buffer_in_bytes"`
// Roles of the node, e.g. [master, ingest, data]
Roles []string `json:"roles"`
// Attributes of the node.
Attributes map[string]string `json:"attributes"`
// Settings of the node, e.g. paths and pidfile.
Settings map[string]interface{} `json:"settings"`
// OS information, e.g. CPU and memory.
OS *NodesInfoNodeOS `json:"os"`
// Process information, e.g. max file descriptors.
Process *NodesInfoNodeProcess `json:"process"`
// JVM information, e.g. VM version.
JVM *NodesInfoNodeJVM `json:"jvm"`
// ThreadPool information.
ThreadPool *NodesInfoNodeThreadPool `json:"thread_pool"`
// Network information.
Transport *NodesInfoNodeTransport `json:"transport"`
// HTTP information.
HTTP *NodesInfoNodeHTTP `json:"http"`
// Plugins information.
Plugins []*NodesInfoNodePlugin `json:"plugins"`
// Modules information.
Modules []*NodesInfoNodeModule `json:"modules"`
// Ingest information.
Ingest *NodesInfoNodeIngest `json:"ingest"`
}
// HasRole returns true if the node fulfills the given role.
func (n *NodesInfoNode) HasRole(role string) bool {
for _, r := range n.Roles {
if r == role {
return true
}
}
return false
}
// IsMaster returns true if the node is a master node.
func (n *NodesInfoNode) IsMaster() bool {
return n.HasRole("master")
}
// IsData returns true if the node is a data node.
func (n *NodesInfoNode) IsData() bool {
return n.HasRole("data")
}
// IsIngest returns true if the node is an ingest node.
func (n *NodesInfoNode) IsIngest() bool {
return n.HasRole("ingest")
}
// NodesInfoNodeOS represents OS-specific details about a node.
type NodesInfoNodeOS struct {
RefreshInterval string `json:"refresh_interval"` // e.g. 1s
RefreshIntervalInMillis int `json:"refresh_interval_in_millis"` // e.g. 1000
Name string `json:"name"` // e.g. Linux
Arch string `json:"arch"` // e.g. amd64
Version string `json:"version"` // e.g. 4.9.87-linuxkit-aufs
AvailableProcessors int `json:"available_processors"` // e.g. 4
AllocatedProcessors int `json:"allocated_processors"` // e.g. 4
}
// NodesInfoNodeProcess represents process-related information.
type NodesInfoNodeProcess struct {
RefreshInterval string `json:"refresh_interval"` // e.g. 1s
RefreshIntervalInMillis int64 `json:"refresh_interval_in_millis"` // e.g. 1000
ID int `json:"id"` // process id, e.g. 87079
Mlockall bool `json:"mlockall"` // e.g. false
}
// NodesInfoNodeJVM represents JVM-related information.
type NodesInfoNodeJVM struct {
PID int `json:"pid"` // process id, e.g. 87079
Version string `json:"version"` // e.g. "1.8.0_161"
VMName string `json:"vm_name"` // e.g. "OpenJDK 64-Bit Server VM"
VMVersion string `json:"vm_version"` // e.g. "25.161-b14"
VMVendor string `json:"vm_vendor"` // e.g. "Oracle Corporation"
StartTime time.Time `json:"start_time"` // e.g. "2018-03-30T11:06:36.644Z"
StartTimeInMillis int64 `json:"start_time_in_millis"` // e.g. 1522407996644
// Mem information
Mem struct {
HeapInit string `json:"heap_init"` // e.g. "1gb"
HeapInitInBytes int `json:"heap_init_in_bytes"` // e.g. 1073741824
HeapMax string `json:"heap_max"` // e.g. "1007.3mb"
HeapMaxInBytes int `json:"heap_max_in_bytes"` // e.g. 1056309248
NonHeapInit string `json:"non_heap_init"` // e.g. "2.4mb"
NonHeapInitInBytes int `json:"non_heap_init_in_bytes"` // e.g. 2555904
NonHeapMax string `json:"non_heap_max"` // e.g. "0b"
NonHeapMaxInBytes int `json:"non_heap_max_in_bytes"` // e.g. 0
DirectMax string `json:"direct_max"` // e.g. "1007.3mb"
DirectMaxInBytes int `json:"direct_max_in_bytes"` // e.g. 1056309248
} `json:"mem"`
GCCollectors []string `json:"gc_collectors"` // e.g. ["ParNew", "ConcurrentMarkSweep"]
MemoryPools []string `json:"memory_pools"` // e.g. ["Code Cache", "Metaspace", "Compressed Class Space", "Par Eden Space", "Par Survivor Space", "CMS Old Gen"]
// UsingCompressedOrdinaryObjectPointers should be a bool, but is a
// string in 6.2.3. We use an interface{} for now so that it won't break
// when this will be fixed in later versions of Elasticsearch.
UsingCompressedOrdinaryObjectPointers interface{} `json:"using_compressed_ordinary_object_pointers"`
InputArguments []string `json:"input_arguments"` // e.g. ["-Xms1g", "-Xmx1g" ...]
}
// NodesInfoNodeThreadPool represents information about the thread pool.
type NodesInfoNodeThreadPool struct {
ForceMerge *NodesInfoNodeThreadPoolSection `json:"force_merge"`
FetchShardStarted *NodesInfoNodeThreadPoolSection `json:"fetch_shard_started"`
Listener *NodesInfoNodeThreadPoolSection `json:"listener"`
Index *NodesInfoNodeThreadPoolSection `json:"index"`
Refresh *NodesInfoNodeThreadPoolSection `json:"refresh"`
Generic *NodesInfoNodeThreadPoolSection `json:"generic"`
Warmer *NodesInfoNodeThreadPoolSection `json:"warmer"`
Search *NodesInfoNodeThreadPoolSection `json:"search"`
Flush *NodesInfoNodeThreadPoolSection `json:"flush"`
FetchShardStore *NodesInfoNodeThreadPoolSection `json:"fetch_shard_store"`
Management *NodesInfoNodeThreadPoolSection `json:"management"`
Get *NodesInfoNodeThreadPoolSection `json:"get"`
Bulk *NodesInfoNodeThreadPoolSection `json:"bulk"`
Snapshot *NodesInfoNodeThreadPoolSection `json:"snapshot"`
Percolate *NodesInfoNodeThreadPoolSection `json:"percolate"` // check
Bench *NodesInfoNodeThreadPoolSection `json:"bench"` // check
Suggest *NodesInfoNodeThreadPoolSection `json:"suggest"` // deprecated
Optimize *NodesInfoNodeThreadPoolSection `json:"optimize"` // deprecated
Merge *NodesInfoNodeThreadPoolSection `json:"merge"` // deprecated
}
// NodesInfoNodeThreadPoolSection represents information about a certain
// type of thread pool, e.g. for indexing or searching.
type NodesInfoNodeThreadPoolSection struct {
Type string `json:"type"` // e.g. fixed, scaling, or fixed_auto_queue_size
Min int `json:"min"` // e.g. 4
Max int `json:"max"` // e.g. 4
KeepAlive string `json:"keep_alive"` // e.g. "5m"
QueueSize interface{} `json:"queue_size"` // e.g. "1k" or -1
}
// NodesInfoNodeTransport represents transport-related information.
type NodesInfoNodeTransport struct {
BoundAddress []string `json:"bound_address"`
PublishAddress string `json:"publish_address"`
Profiles map[string]*NodesInfoNodeTransportProfile `json:"profiles"`
}
// NodesInfoNodeTransportProfile represents a transport profile.
type NodesInfoNodeTransportProfile struct {
BoundAddress []string `json:"bound_address"`
PublishAddress string `json:"publish_address"`
}
// NodesInfoNodeHTTP represents HTTP-related information.
type NodesInfoNodeHTTP struct {
BoundAddress []string `json:"bound_address"` // e.g. ["127.0.0.1:9200", "[fe80::1]:9200", "[::1]:9200"]
PublishAddress string `json:"publish_address"` // e.g. "127.0.0.1:9300"
MaxContentLength string `json:"max_content_length"` // e.g. "100mb"
MaxContentLengthInBytes int64 `json:"max_content_length_in_bytes"`
}
// NodesInfoNodePlugin represents information about a plugin.
type NodesInfoNodePlugin struct {
Name string `json:"name"` // e.g. "ingest-geoip"
Version string `json:"version"` // e.g. "6.2.3"
ElasticsearchVersion string `json:"elasticsearch_version"`
JavaVersion string `json:"java_version"`
Description string `json:"description"` // e.g. "Ingest processor ..."
Classname string `json:"classname"` // e.g. "org.elasticsearch.ingest.geoip.IngestGeoIpPlugin"
ExtendedPlugins []string `json:"extended_plugins"`
HasNativeController bool `json:"has_native_controller"`
RequiresKeystore bool `json:"requires_keystore"`
}
// NodesInfoNodeModule represents information about a module.
type NodesInfoNodeModule struct {
Name string `json:"name"` // e.g. "ingest-geoip"
Version string `json:"version"` // e.g. "6.2.3"
ElasticsearchVersion string `json:"elasticsearch_version"`
JavaVersion string `json:"java_version"`
Description string `json:"description"` // e.g. "Ingest processor ..."
Classname string `json:"classname"` // e.g. "org.elasticsearch.ingest.geoip.IngestGeoIpPlugin"
ExtendedPlugins []string `json:"extended_plugins"`
HasNativeController bool `json:"has_native_controller"`
RequiresKeystore bool `json:"requires_keystore"`
}
// NodesInfoNodeIngest represents information about the ingester.
type NodesInfoNodeIngest struct {
Processors []*NodesInfoNodeIngestProcessorInfo `json:"processors"`
}
// NodesInfoNodeIngestProcessorInfo represents ingest processor info.
type NodesInfoNodeIngestProcessorInfo struct {
Type string `json:"type"` // e.g. append, convert, date etc.
}
================================================
FILE: nodes_info_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestNodesInfoBuildURL(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tests := []struct {
NodeIDs []string
Metrics []string
Expected string
}{
{
nil,
nil,
"/_nodes/_all/_all",
},
{
[]string{},
[]string{},
"/_nodes/_all/_all",
},
{
[]string{"node1"},
[]string{},
"/_nodes/node1/_all",
},
{
[]string{"node1", "node2"},
nil,
"/_nodes/node1%2Cnode2/_all",
},
{
[]string{"node1", "node2"},
[]string{"metric1", "metric2"},
"/_nodes/node1%2Cnode2/metric1%2Cmetric2",
},
}
for i, test := range tests {
path, _, err := client.NodesInfo().NodeId(test.NodeIDs...).Metric(test.Metrics...).buildURL()
if err != nil {
t.Fatalf("case #%d: %v", i+1, err)
}
if path != test.Expected {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.Expected, path)
}
}
}
func TestNodesInfo(t *testing.T) {
client, err := NewClient()
if err != nil {
t.Fatal(err)
}
info, err := client.NodesInfo().Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if info == nil {
t.Fatal("expected nodes info")
}
if info.ClusterName == "" {
t.Errorf("expected cluster name; got: %q", info.ClusterName)
}
if len(info.Nodes) == 0 {
t.Errorf("expected some nodes; got: %d", len(info.Nodes))
}
for id, node := range info.Nodes {
if id == "" {
t.Errorf("expected node id; got: %q", id)
}
if node == nil {
t.Fatalf("expected node info; got: %v", node)
}
if node.IP == "" {
t.Errorf("expected node IP; got: %q", node.IP)
}
}
}
================================================
FILE: nodes_stats.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// NodesStatsService returns node statistics.
// See http://www.elastic.co/guide/en/elasticsearch/reference/7.0/cluster-nodes-stats.html
// for details.
type NodesStatsService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
metric []string
indexMetric []string
nodeId []string
completionFields []string
fielddataFields []string
fields []string
groups *bool
level string
timeout string
types []string
}
// NewNodesStatsService creates a new NodesStatsService.
func NewNodesStatsService(client *Client) *NodesStatsService {
return &NodesStatsService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *NodesStatsService) Pretty(pretty bool) *NodesStatsService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *NodesStatsService) Human(human bool) *NodesStatsService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *NodesStatsService) ErrorTrace(errorTrace bool) *NodesStatsService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *NodesStatsService) FilterPath(filterPath ...string) *NodesStatsService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *NodesStatsService) Header(name string, value string) *NodesStatsService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *NodesStatsService) Headers(headers http.Header) *NodesStatsService {
s.headers = headers
return s
}
// Metric limits the information returned to the specified metrics.
func (s *NodesStatsService) Metric(metric ...string) *NodesStatsService {
s.metric = append(s.metric, metric...)
return s
}
// IndexMetric limits the information returned for `indices` metric
// to the specific index metrics. Isn't used if `indices` (or `all`)
// metric isn't specified..
func (s *NodesStatsService) IndexMetric(indexMetric ...string) *NodesStatsService {
s.indexMetric = append(s.indexMetric, indexMetric...)
return s
}
// NodeId is a list of node IDs or names to limit the returned information;
// use `_local` to return information from the node you're connecting to,
// leave empty to get information from all nodes.
func (s *NodesStatsService) NodeId(nodeId ...string) *NodesStatsService {
s.nodeId = append(s.nodeId, nodeId...)
return s
}
// CompletionFields is a list of fields for `fielddata` and `suggest`
// index metric (supports wildcards).
func (s *NodesStatsService) CompletionFields(completionFields ...string) *NodesStatsService {
s.completionFields = append(s.completionFields, completionFields...)
return s
}
// FielddataFields is a list of fields for `fielddata` index metric (supports wildcards).
func (s *NodesStatsService) FielddataFields(fielddataFields ...string) *NodesStatsService {
s.fielddataFields = append(s.fielddataFields, fielddataFields...)
return s
}
// Fields is a list of fields for `fielddata` and `completion` index metric (supports wildcards).
func (s *NodesStatsService) Fields(fields ...string) *NodesStatsService {
s.fields = append(s.fields, fields...)
return s
}
// Groups is a list of search groups for `search` index metric.
func (s *NodesStatsService) Groups(groups bool) *NodesStatsService {
s.groups = &groups
return s
}
// Level specifies whether to return indices stats aggregated at node, index or shard level.
func (s *NodesStatsService) Level(level string) *NodesStatsService {
s.level = level
return s
}
// Timeout specifies an explicit operation timeout.
func (s *NodesStatsService) Timeout(timeout string) *NodesStatsService {
s.timeout = timeout
return s
}
// Types a list of document types for the `indexing` index metric.
func (s *NodesStatsService) Types(types ...string) *NodesStatsService {
s.types = append(s.types, types...)
return s
}
// buildURL builds the URL for the operation.
func (s *NodesStatsService) buildURL() (string, url.Values, error) {
var err error
var path string
if len(s.nodeId) > 0 && len(s.metric) > 0 && len(s.indexMetric) > 0 {
path, err = uritemplates.Expand("/_nodes/{node_id}/stats/{metric}/{index_metric}", map[string]string{
"index_metric": strings.Join(s.indexMetric, ","),
"node_id": strings.Join(s.nodeId, ","),
"metric": strings.Join(s.metric, ","),
})
} else if len(s.nodeId) > 0 && len(s.metric) > 0 && len(s.indexMetric) == 0 {
path, err = uritemplates.Expand("/_nodes/{node_id}/stats/{metric}", map[string]string{
"node_id": strings.Join(s.nodeId, ","),
"metric": strings.Join(s.metric, ","),
})
} else if len(s.nodeId) > 0 && len(s.metric) == 0 && len(s.indexMetric) > 0 {
path, err = uritemplates.Expand("/_nodes/{node_id}/stats/_all/{index_metric}", map[string]string{
"index_metric": strings.Join(s.indexMetric, ","),
"node_id": strings.Join(s.nodeId, ","),
})
} else if len(s.nodeId) > 0 && len(s.metric) == 0 && len(s.indexMetric) == 0 {
path, err = uritemplates.Expand("/_nodes/{node_id}/stats", map[string]string{
"node_id": strings.Join(s.nodeId, ","),
})
} else if len(s.nodeId) == 0 && len(s.metric) > 0 && len(s.indexMetric) > 0 {
path, err = uritemplates.Expand("/_nodes/stats/{metric}/{index_metric}", map[string]string{
"index_metric": strings.Join(s.indexMetric, ","),
"metric": strings.Join(s.metric, ","),
})
} else if len(s.nodeId) == 0 && len(s.metric) > 0 && len(s.indexMetric) == 0 {
path, err = uritemplates.Expand("/_nodes/stats/{metric}", map[string]string{
"metric": strings.Join(s.metric, ","),
})
} else if len(s.nodeId) == 0 && len(s.metric) == 0 && len(s.indexMetric) > 0 {
path, err = uritemplates.Expand("/_nodes/stats/_all/{index_metric}", map[string]string{
"index_metric": strings.Join(s.indexMetric, ","),
})
} else { // if len(s.nodeId) == 0 && len(s.metric) == 0 && len(s.indexMetric) == 0 {
path = "/_nodes/stats"
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if len(s.completionFields) > 0 {
params.Set("completion_fields", strings.Join(s.completionFields, ","))
}
if len(s.fielddataFields) > 0 {
params.Set("fielddata_fields", strings.Join(s.fielddataFields, ","))
}
if len(s.fields) > 0 {
params.Set("fields", strings.Join(s.fields, ","))
}
if s.groups != nil {
params.Set("groups", fmt.Sprintf("%v", *s.groups))
}
if s.level != "" {
params.Set("level", s.level)
}
if s.timeout != "" {
params.Set("timeout", s.timeout)
}
if len(s.types) > 0 {
params.Set("types", strings.Join(s.types, ","))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *NodesStatsService) Validate() error {
return nil
}
// Do executes the operation.
func (s *NodesStatsService) Do(ctx context.Context) (*NodesStatsResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(NodesStatsResponse)
if err := json.Unmarshal(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// NodesStatsResponse is the response of NodesStatsService.Do.
type NodesStatsResponse struct {
ClusterName string `json:"cluster_name"`
Nodes map[string]*NodesStatsNode `json:"nodes"`
}
type NodesStatsNode struct {
// Timestamp when these stats we're gathered.
Timestamp int64 `json:"timestamp"`
// Name of the node, e.g. "Mister Fear"
Name string `json:"name"`
// TransportAddress, e.g. "127.0.0.1:9300"
TransportAddress string `json:"transport_address"`
// Host is the host name, e.g. "macbookair"
Host string `json:"host"`
// IP is an IP address, e.g. "192.168.1.2"
IP string `json:"ip"`
// Roles is a list of the roles of the node, e.g. master, data, ingest.
Roles []string `json:"roles"`
// Attributes of the node.
Attributes map[string]interface{} `json:"attributes"`
// Indices returns index information.
Indices *NodesStatsIndex `json:"indices"`
// OS information, e.g. CPU and memory.
OS *NodesStatsNodeOS `json:"os"`
// Process information, e.g. max file descriptors.
Process *NodesStatsNodeProcess `json:"process"`
// JVM information, e.g. VM version.
JVM *NodesStatsNodeJVM `json:"jvm"`
// ThreadPool information.
ThreadPool map[string]*NodesStatsNodeThreadPool `json:"thread_pool"`
// FS returns information about the filesystem.
FS *NodesStatsNodeFS `json:"fs"`
// Network information.
Transport *NodesStatsNodeTransport `json:"transport"`
// HTTP information.
HTTP *NodesStatsNodeHTTP `json:"http"`
// Breaker contains information about circuit breakers.
Breaker map[string]*NodesStatsBreaker `json:"breakers"`
// ScriptStats information.
ScriptStats *NodesStatsScriptStats `json:"script"`
// Discovery information.
Discovery *NodesStatsDiscovery `json:"discovery"`
// Ingest information
Ingest *NodesStatsIngest `json:"ingest"`
}
type NodesStatsIndex struct {
Docs *NodesStatsDocsStats `json:"docs"`
Shards *NodesStatsShardCountStats `json:"shards_stats"`
Store *NodesStatsStoreStats `json:"store"`
Indexing *NodesStatsIndexingStats `json:"indexing"`
Get *NodesStatsGetStats `json:"get"`
Search *NodesStatsSearchStats `json:"search"`
Merges *NodesStatsMergeStats `json:"merges"`
Refresh *NodesStatsRefreshStats `json:"refresh"`
Flush *NodesStatsFlushStats `json:"flush"`
Warmer *NodesStatsWarmerStats `json:"warmer"`
QueryCache *NodesStatsQueryCacheStats `json:"query_cache"`
Fielddata *NodesStatsFielddataStats `json:"fielddata"`
Completion *NodesStatsCompletionStats `json:"completion"`
Segments *NodesStatsSegmentsStats `json:"segments"`
Translog *NodesStatsTranslogStats `json:"translog"`
RequestCache *NodesStatsRequestCacheStats `json:"request_cache"`
Recovery NodesStatsRecoveryStats `json:"recovery"`
IndicesLevel map[string]*NodesStatsIndex `json:"indices"` // for level=indices
ShardsLevel map[string]*NodesStatsIndex `json:"shards"` // for level=shards
}
type NodesStatsDocsStats struct {
Count int64 `json:"count"`
Deleted int64 `json:"deleted"`
}
type NodesStatsShardCountStats struct {
TotalCount int64 `json:"total_count"`
}
type NodesStatsStoreStats struct {
Size string `json:"size"`
SizeInBytes int64 `json:"size_in_bytes"`
}
type NodesStatsIndexingStats struct {
IndexTotal int64 `json:"index_total"`
IndexTime string `json:"index_time"`
IndexTimeInMillis int64 `json:"index_time_in_millis"`
IndexCurrent int64 `json:"index_current"`
IndexFailed int64 `json:"index_failed"`
DeleteTotal int64 `json:"delete_total"`
DeleteTime string `json:"delete_time"`
DeleteTimeInMillis int64 `json:"delete_time_in_millis"`
DeleteCurrent int64 `json:"delete_current"`
NoopUpdateTotal int64 `json:"noop_update_total"`
IsThrottled bool `json:"is_throttled"`
ThrottledTime string `json:"throttle_time"` // no typo, see https://github.com/elastic/elasticsearch/blob/ff99bc1d3f8a7ea72718872d214ec2097dfca276/server/src/main/java/org/elasticsearch/index/shard/IndexingStats.java#L244
ThrottledTimeInMillis int64 `json:"throttle_time_in_millis"`
Types map[string]*NodesStatsIndexingStats `json:"types"` // stats for individual types
}
type NodesStatsGetStats struct {
Total int64 `json:"total"`
Time string `json:"get_time"`
TimeInMillis int64 `json:"time_in_millis"`
Exists int64 `json:"exists"`
ExistsTime string `json:"exists_time"`
ExistsTimeInMillis int64 `json:"exists_in_millis"`
Missing int64 `json:"missing"`
MissingTime string `json:"missing_time"`
MissingTimeInMillis int64 `json:"missing_in_millis"`
Current int64 `json:"current"`
}
type NodesStatsSearchStats struct {
OpenContexts int64 `json:"open_contexts"`
QueryTotal int64 `json:"query_total"`
QueryTime string `json:"query_time"`
QueryTimeInMillis int64 `json:"query_time_in_millis"`
QueryCurrent int64 `json:"query_current"`
FetchTotal int64 `json:"fetch_total"`
FetchTime string `json:"fetch_time"`
FetchTimeInMillis int64 `json:"fetch_time_in_millis"`
FetchCurrent int64 `json:"fetch_current"`
ScrollTotal int64 `json:"scroll_total"`
ScrollTime string `json:"scroll_time"`
ScrollTimeInMillis int64 `json:"scroll_time_in_millis"`
ScrollCurrent int64 `json:"scroll_current"`
Groups map[string]*NodesStatsSearchStats `json:"groups"` // stats for individual groups
}
type NodesStatsMergeStats struct {
Current int64 `json:"current"`
CurrentDocs int64 `json:"current_docs"`
CurrentSize string `json:"current_size"`
CurrentSizeInBytes int64 `json:"current_size_in_bytes"`
Total int64 `json:"total"`
TotalTime string `json:"total_time"`
TotalTimeInMillis int64 `json:"total_time_in_millis"`
TotalDocs int64 `json:"total_docs"`
TotalSize string `json:"total_size"`
TotalSizeInBytes int64 `json:"total_size_in_bytes"`
TotalStoppedTime string `json:"total_stopped_time"`
TotalStoppedTimeInMillis int64 `json:"total_stopped_time_in_millis"`
TotalThrottledTime string `json:"total_throttled_time"`
TotalThrottledTimeInMillis int64 `json:"total_throttled_time_in_millis"`
TotalThrottleBytes string `json:"total_auto_throttle"`
TotalThrottleBytesInBytes int64 `json:"total_auto_throttle_in_bytes"`
}
type NodesStatsRefreshStats struct {
Total int64 `json:"total"`
TotalTime string `json:"total_time"`
TotalTimeInMillis int64 `json:"total_time_in_millis"`
}
type NodesStatsFlushStats struct {
Total int64 `json:"total"`
TotalTime string `json:"total_time"`
TotalTimeInMillis int64 `json:"total_time_in_millis"`
}
type NodesStatsWarmerStats struct {
Current int64 `json:"current"`
Total int64 `json:"total"`
TotalTime string `json:"total_time"`
TotalTimeInMillis int64 `json:"total_time_in_millis"`
}
type NodesStatsQueryCacheStats struct {
MemorySize string `json:"memory_size"`
MemorySizeInBytes int64 `json:"memory_size_in_bytes"`
TotalCount int64 `json:"total_count"`
HitCount int64 `json:"hit_count"`
MissCount int64 `json:"miss_count"`
CacheSize int64 `json:"cache_size"`
CacheCount int64 `json:"cache_count"`
Evictions int64 `json:"evictions"`
}
type NodesStatsFielddataStats struct {
MemorySize string `json:"memory_size"`
MemorySizeInBytes int64 `json:"memory_size_in_bytes"`
Evictions int64 `json:"evictions"`
Fields map[string]struct {
MemorySize string `json:"memory_size"`
MemorySizeInBytes int64 `json:"memory_size_in_bytes"`
} `json:"fields"`
}
type NodesStatsCompletionStats struct {
Size string `json:"size"`
SizeInBytes int64 `json:"size_in_bytes"`
Fields map[string]struct {
Size string `json:"size"`
SizeInBytes int64 `json:"size_in_bytes"`
} `json:"fields"`
}
type NodesStatsSegmentsStats struct {
Count int64 `json:"count"`
Memory string `json:"memory"`
MemoryInBytes int64 `json:"memory_in_bytes"`
TermsMemory string `json:"terms_memory"`
TermsMemoryInBytes int64 `json:"terms_memory_in_bytes"`
StoredFieldsMemory string `json:"stored_fields_memory"`
StoredFieldsMemoryInBytes int64 `json:"stored_fields_memory_in_bytes"`
TermVectorsMemory string `json:"term_vectors_memory"`
TermVectorsMemoryInBytes int64 `json:"term_vectors_memory_in_bytes"`
NormsMemory string `json:"norms_memory"`
NormsMemoryInBytes int64 `json:"norms_memory_in_bytes"`
DocValuesMemory string `json:"doc_values_memory"`
DocValuesMemoryInBytes int64 `json:"doc_values_memory_in_bytes"`
IndexWriterMemory string `json:"index_writer_memory"`
IndexWriterMemoryInBytes int64 `json:"index_writer_memory_in_bytes"`
IndexWriterMaxMemory string `json:"index_writer_max_memory"`
IndexWriterMaxMemoryInBytes int64 `json:"index_writer_max_memory_in_bytes"`
VersionMapMemory string `json:"version_map_memory"`
VersionMapMemoryInBytes int64 `json:"version_map_memory_in_bytes"`
FixedBitSetMemory string `json:"fixed_bit_set"` // not a typo
FixedBitSetMemoryInBytes int64 `json:"fixed_bit_set_memory_in_bytes"`
}
type NodesStatsTranslogStats struct {
Operations int64 `json:"operations"`
Size string `json:"size"`
SizeInBytes int64 `json:"size_in_bytes"`
}
type NodesStatsRequestCacheStats struct {
MemorySize string `json:"memory_size"`
MemorySizeInBytes int64 `json:"memory_size_in_bytes"`
Evictions int64 `json:"evictions"`
HitCount int64 `json:"hit_count"`
MissCount int64 `json:"miss_count"`
}
type NodesStatsRecoveryStats struct {
CurrentAsSource int `json:"current_as_source"`
CurrentAsTarget int `json:"current_as_target"`
}
type NodesStatsNodeOS struct {
Timestamp int64 `json:"timestamp"`
CPU *NodesStatsNodeOSCPU `json:"cpu"`
Mem *NodesStatsNodeOSMem `json:"mem"`
Swap *NodesStatsNodeOSSwap `json:"swap"`
}
type NodesStatsNodeOSCPU struct {
Percent int `json:"percent"`
LoadAverage map[string]float64 `json:"load_average"` // keys are: 1m, 5m, and 15m
}
type NodesStatsNodeOSMem struct {
Total string `json:"total"`
TotalInBytes int64 `json:"total_in_bytes"`
Free string `json:"free"`
FreeInBytes int64 `json:"free_in_bytes"`
Used string `json:"used"`
UsedInBytes int64 `json:"used_in_bytes"`
FreePercent int `json:"free_percent"`
UsedPercent int `json:"used_percent"`
}
type NodesStatsNodeOSSwap struct {
Total string `json:"total"`
TotalInBytes int64 `json:"total_in_bytes"`
Free string `json:"free"`
FreeInBytes int64 `json:"free_in_bytes"`
Used string `json:"used"`
UsedInBytes int64 `json:"used_in_bytes"`
}
type NodesStatsNodeProcess struct {
Timestamp int64 `json:"timestamp"`
OpenFileDescriptors int64 `json:"open_file_descriptors"`
MaxFileDescriptors int64 `json:"max_file_descriptors"`
CPU struct {
Percent int `json:"percent"`
Total string `json:"total"`
TotalInMillis int64 `json:"total_in_millis"`
} `json:"cpu"`
Mem struct {
TotalVirtual string `json:"total_virtual"`
TotalVirtualInBytes int64 `json:"total_virtual_in_bytes"`
} `json:"mem"`
}
type NodesStatsNodeJVM struct {
Timestamp int64 `json:"timestamp"`
Uptime string `json:"uptime"`
UptimeInMillis int64 `json:"uptime_in_millis"`
Mem *NodesStatsNodeJVMMem `json:"mem"`
Threads *NodesStatsNodeJVMThreads `json:"threads"`
GC *NodesStatsNodeJVMGC `json:"gc"`
BufferPools map[string]*NodesStatsNodeJVMBufferPool `json:"buffer_pools"`
Classes *NodesStatsNodeJVMClasses `json:"classes"`
}
type NodesStatsNodeJVMMem struct {
HeapUsed string `json:"heap_used"`
HeapUsedInBytes int64 `json:"heap_used_in_bytes"`
HeapUsedPercent int `json:"heap_used_percent"`
HeapCommitted string `json:"heap_committed"`
HeapCommittedInBytes int64 `json:"heap_committed_in_bytes"`
HeapMax string `json:"heap_max"`
HeapMaxInBytes int64 `json:"heap_max_in_bytes"`
NonHeapUsed string `json:"non_heap_used"`
NonHeapUsedInBytes int64 `json:"non_heap_used_in_bytes"`
NonHeapCommitted string `json:"non_heap_committed"`
NonHeapCommittedInBytes int64 `json:"non_heap_committed_in_bytes"`
Pools map[string]struct {
Used string `json:"used"`
UsedInBytes int64 `json:"used_in_bytes"`
Max string `json:"max"`
MaxInBytes int64 `json:"max_in_bytes"`
PeakUsed string `json:"peak_used"`
PeakUsedInBytes int64 `json:"peak_used_in_bytes"`
PeakMax string `json:"peak_max"`
PeakMaxInBytes int64 `json:"peak_max_in_bytes"`
} `json:"pools"`
}
type NodesStatsNodeJVMThreads struct {
Count int64 `json:"count"`
PeakCount int64 `json:"peak_count"`
}
type NodesStatsNodeJVMGC struct {
Collectors map[string]*NodesStatsNodeJVMGCCollector `json:"collectors"`
}
type NodesStatsNodeJVMGCCollector struct {
CollectionCount int64 `json:"collection_count"`
CollectionTime string `json:"collection_time"`
CollectionTimeInMillis int64 `json:"collection_time_in_millis"`
}
type NodesStatsNodeJVMBufferPool struct {
Count int64 `json:"count"`
TotalCapacity string `json:"total_capacity"`
TotalCapacityInBytes int64 `json:"total_capacity_in_bytes"`
}
type NodesStatsNodeJVMClasses struct {
CurrentLoadedCount int64 `json:"current_loaded_count"`
TotalLoadedCount int64 `json:"total_loaded_count"`
TotalUnloadedCount int64 `json:"total_unloaded_count"`
}
type NodesStatsNodeThreadPool struct {
Threads int `json:"threads"`
Queue int `json:"queue"`
Active int `json:"active"`
Rejected int64 `json:"rejected"`
Largest int `json:"largest"`
Completed int64 `json:"completed"`
}
type NodesStatsNodeFS struct {
Timestamp int64 `json:"timestamp"`
Total *NodesStatsNodeFSEntry `json:"total"`
Data []*NodesStatsNodeFSEntry `json:"data"`
IOStats *NodesStatsNodeFSIOStats `json:"io_stats"`
}
type NodesStatsNodeFSEntry struct {
Path string `json:"path"`
Mount string `json:"mount"`
Type string `json:"type"`
Total string `json:"total"`
TotalInBytes int64 `json:"total_in_bytes"`
Free string `json:"free"`
FreeInBytes int64 `json:"free_in_bytes"`
Available string `json:"available"`
AvailableInBytes int64 `json:"available_in_bytes"`
Spins string `json:"spins"`
}
type NodesStatsNodeFSIOStats struct {
Devices []*NodesStatsNodeFSIOStatsEntry `json:"devices"`
Total *NodesStatsNodeFSIOStatsEntry `json:"total"`
}
type NodesStatsNodeFSIOStatsEntry struct {
DeviceName string `json:"device_name"`
Operations int64 `json:"operations"`
ReadOperations int64 `json:"read_operations"`
WriteOperations int64 `json:"write_operations"`
ReadKilobytes int64 `json:"read_kilobytes"`
WriteKilobytes int64 `json:"write_kilobytes"`
}
type NodesStatsNodeTransport struct {
ServerOpen int `json:"server_open"`
RxCount int64 `json:"rx_count"`
RxSize string `json:"rx_size"`
RxSizeInBytes int64 `json:"rx_size_in_bytes"`
TxCount int64 `json:"tx_count"`
TxSize string `json:"tx_size"`
TxSizeInBytes int64 `json:"tx_size_in_bytes"`
}
type NodesStatsNodeHTTP struct {
CurrentOpen int `json:"current_open"`
TotalOpened int `json:"total_opened"`
}
type NodesStatsBreaker struct {
LimitSize string `json:"limit_size"`
LimitSizeInBytes int64 `json:"limit_size_in_bytes"`
EstimatedSize string `json:"estimated_size"`
EstimatedSizeInBytes int64 `json:"estimated_size_in_bytes"`
Overhead float64 `json:"overhead"`
Tripped int64 `json:"tripped"`
}
type NodesStatsScriptStats struct {
Compilations int64 `json:"compilations"`
CacheEvictions int64 `json:"cache_evictions"`
}
type NodesStatsDiscovery struct {
ClusterStateQueue *NodesStatsDiscoveryStats `json:"cluster_state_queue"`
}
type NodesStatsDiscoveryStats struct {
Total int64 `json:"total"`
Pending int64 `json:"pending"`
Committed int64 `json:"committed"`
}
type NodesStatsIngest struct {
Total *NodesStatsIngestStats `json:"total"`
Pipelines interface{} `json:"pipelines"`
}
type NodesStatsIngestStats struct {
Count int64 `json:"count"`
Time string `json:"time"`
TimeInMillis int64 `json:"time_in_millis"`
Current int64 `json:"current"`
Failed int64 `json:"failed"`
}
================================================
FILE: nodes_stats_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestNodesStats(t *testing.T) {
client, err := NewClient() // SetTraceLog(log.New(os.Stdout, "", 0)))
if err != nil {
t.Fatal(err)
}
// TODO(oe) Remove this hack after a fix for https://github.com/elastic/elasticsearch/issues/78311 is released
version, err := client.ElasticsearchVersion(DefaultURL)
if err != nil {
t.Fatal(err)
}
if version == "7.15.0" || version == "7.15.1" {
t.Skipf("skipping NodesStats test for %s because of https://github.com/elastic/elasticsearch/issues/78311", version)
return
}
info, err := client.NodesStats().Human(true).Pretty(true).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if info == nil {
t.Fatal("expected nodes stats")
}
if info.ClusterName == "" {
t.Errorf("expected cluster name; got: %q", info.ClusterName)
}
if len(info.Nodes) == 0 {
t.Errorf("expected some nodes; got: %d", len(info.Nodes))
}
for id, node := range info.Nodes {
if id == "" {
t.Errorf("expected node id; got: %q", id)
}
if node == nil {
t.Fatalf("expected node info; got: %v", node)
}
if len(node.Name) == 0 {
t.Errorf("expected node name; got: %q", node.Name)
}
if node.Timestamp == 0 {
t.Errorf("expected timestamp; got: %q", node.Timestamp)
}
}
}
func TestNodesStatsBuildURL(t *testing.T) {
tests := []struct {
NodeIds []string
Metrics []string
IndexMetrics []string
Expected string
}{
{
NodeIds: nil,
Metrics: nil,
IndexMetrics: nil,
Expected: "/_nodes/stats",
},
{
NodeIds: []string{"node1"},
Metrics: nil,
IndexMetrics: nil,
Expected: "/_nodes/node1/stats",
},
{
NodeIds: []string{"node1", "node2"},
Metrics: nil,
IndexMetrics: nil,
Expected: "/_nodes/node1%2Cnode2/stats",
},
{
NodeIds: nil,
Metrics: []string{"indices"},
IndexMetrics: nil,
Expected: "/_nodes/stats/indices",
},
{
NodeIds: nil,
Metrics: []string{"indices", "jvm"},
IndexMetrics: nil,
Expected: "/_nodes/stats/indices%2Cjvm",
},
{
NodeIds: []string{"node1"},
Metrics: []string{"indices", "jvm"},
IndexMetrics: nil,
Expected: "/_nodes/node1/stats/indices%2Cjvm",
},
{
NodeIds: nil,
Metrics: nil,
IndexMetrics: []string{"fielddata"},
Expected: "/_nodes/stats/_all/fielddata",
},
{
NodeIds: []string{"node1"},
Metrics: nil,
IndexMetrics: []string{"fielddata"},
Expected: "/_nodes/node1/stats/_all/fielddata",
},
{
NodeIds: nil,
Metrics: []string{"indices"},
IndexMetrics: []string{"fielddata"},
Expected: "/_nodes/stats/indices/fielddata",
},
{
NodeIds: []string{"node1"},
Metrics: []string{"indices"},
IndexMetrics: []string{"fielddata"},
Expected: "/_nodes/node1/stats/indices/fielddata",
},
{
NodeIds: []string{"node1", "node2"},
Metrics: []string{"indices", "jvm"},
IndexMetrics: []string{"fielddata", "docs"},
Expected: "/_nodes/node1%2Cnode2/stats/indices%2Cjvm/fielddata%2Cdocs",
},
}
client, err := NewClient()
if err != nil {
t.Fatal(err)
}
for i, tt := range tests {
svc := client.NodesStats().NodeId(tt.NodeIds...).Metric(tt.Metrics...).IndexMetric(tt.IndexMetrics...)
path, _, err := svc.buildURL()
if err != nil {
t.Errorf("#%d: expected no error, got %v", i, err)
} else {
if want, have := tt.Expected, path; want != have {
t.Errorf("#%d: expected %q, got %q", i, want, have)
}
}
}
}
================================================
FILE: percolate_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestPercolate(t *testing.T) {
//client := setupTestClientAndCreateIndex(t, SetErrorLog(log.New(os.Stdout, "", 0)))
//client := setupTestClientAndCreateIndex(t, SetTraceLog(log.New(os.Stdout, "", 0)))
client := setupTestClientAndCreateIndex(t)
// Create query index
createQueryIndex, err := client.CreateIndex(testQueryIndex).Body(testQueryMapping).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if createQueryIndex == nil {
t.Errorf("expected result to be != nil; got: %v", createQueryIndex)
}
// Add a document
_, err = client.Index().
Index(testQueryIndex).
Id("1").
BodyJson(`{"query":{"match":{"message":"bonsai tree"}}}`).
Refresh("wait_for").
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Percolate should return our registered query
pq := NewPercolatorQuery().
Field("query").
Document(doctype{Message: "A new bonsai tree in the office"})
res, err := client.Search(testQueryIndex).Query(pq).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected results != nil; got nil")
}
if res.Hits == nil {
t.Fatal("expected SearchResult.Hits != nil; got nil")
}
if got, want := res.TotalHits(), int64(1); got != want {
t.Fatalf("expected SearchResult.TotalHits() = %d; got %d", want, got)
}
if got, want := len(res.Hits.Hits), 1; got != want {
t.Fatalf("expected len(SearchResult.Hits.Hits) = %d; got %d", want, got)
}
hit := res.Hits.Hits[0]
if hit.Index != testQueryIndex {
t.Fatalf("expected SearchResult.Hits.Hit.Index = %q; got %q", testQueryIndex, hit.Index)
}
got := string(hit.Source)
expected := `{"query":{"match":{"message":"bonsai tree"}}}`
if got != expected {
t.Fatalf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: ping.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"runtime"
"strings"
)
// PingService checks if an Elasticsearch server on a given URL is alive.
// When asked for, it can also return various information about the
// Elasticsearch server, e.g. the Elasticsearch version number.
//
// Ping simply starts a HTTP GET request to the URL of the server.
// If the server responds with HTTP Status code 200 OK, the server is alive.
type PingService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
url string
timeout string
httpHeadOnly bool
}
// PingResult is the result returned from querying the Elasticsearch server.
type PingResult struct {
Name string `json:"name"`
ClusterName string `json:"cluster_name"`
Version struct {
Number string `json:"number"` // e.g. "7.0.0"
BuildFlavor string `json:"build_flavor"` // e.g. "oss" or "default"
BuildType string `json:"build_type"` // e.g. "docker"
BuildHash string `json:"build_hash"` // e.g. "b7e28a7"
BuildDate string `json:"build_date"` // e.g. "2019-04-05T22:55:32.697037Z"
BuildSnapshot bool `json:"build_snapshot"` // e.g. false
LuceneVersion string `json:"lucene_version"` // e.g. "8.0.0"
MinimumWireCompatibilityVersion string `json:"minimum_wire_compatibility_version"` // e.g. "6.7.0"
MinimumIndexCompatibilityVersion string `json:"minimum_index_compatibility_version"` // e.g. "6.0.0-beta1"
} `json:"version"`
TagLine string `json:"tagline"`
}
func NewPingService(client *Client) *PingService {
return &PingService{
client: client,
url: DefaultURL,
httpHeadOnly: false,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *PingService) Pretty(pretty bool) *PingService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *PingService) Human(human bool) *PingService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *PingService) ErrorTrace(errorTrace bool) *PingService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *PingService) FilterPath(filterPath ...string) *PingService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *PingService) Header(name string, value string) *PingService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *PingService) Headers(headers http.Header) *PingService {
s.headers = headers
return s
}
func (s *PingService) URL(url string) *PingService {
s.url = url
return s
}
func (s *PingService) Timeout(timeout string) *PingService {
s.timeout = timeout
return s
}
// HeadOnly makes the service to only return the status code in Do;
// the PingResult will be nil.
func (s *PingService) HttpHeadOnly(httpHeadOnly bool) *PingService {
s.httpHeadOnly = httpHeadOnly
return s
}
// Do returns the PingResult, the HTTP status code of the Elasticsearch
// server, and an error.
func (s *PingService) Do(ctx context.Context) (*PingResult, int, error) {
s.client.mu.RLock()
basicAuth := s.client.basicAuthUsername != "" || s.client.basicAuthPassword != ""
basicAuthUsername := s.client.basicAuthUsername
basicAuthPassword := s.client.basicAuthPassword
defaultHeaders := s.client.headers
s.client.mu.RUnlock()
url_ := strings.TrimSuffix(s.url, "/") + "/"
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.timeout != "" {
params.Set("timeout", s.timeout)
}
if len(params) > 0 {
url_ += "?" + params.Encode()
}
var method string
if s.httpHeadOnly {
method = "HEAD"
} else {
method = "GET"
}
// Notice: This service must NOT use PerformRequest!
req, err := NewRequest(method, url_)
if err != nil {
return nil, 0, err
}
for key, values := range s.headers {
for _, v := range values {
req.Header.Add(key, v)
}
}
if len(defaultHeaders) > 0 {
for key, values := range defaultHeaders {
for _, v := range values {
req.Header.Add(key, v)
}
}
}
if basicAuth {
req.SetBasicAuth(basicAuthUsername, basicAuthPassword)
}
if req.Header.Get("User-Agent") == "" {
req.Header.Add("User-Agent", "elastic/"+Version+" ("+runtime.GOOS+"-"+runtime.GOARCH+")")
}
res, err := s.client.c.Do((*http.Request)(req).WithContext(ctx))
if err != nil {
return nil, 0, err
}
defer res.Body.Close()
var ret *PingResult
if !s.httpHeadOnly {
ret = new(PingResult)
if err := json.NewDecoder(res.Body).Decode(ret); err != nil {
return nil, res.StatusCode, err
}
}
return ret, res.StatusCode, nil
}
================================================
FILE: ping_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"net/http"
"testing"
)
func TestPingGet(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
res, code, err := client.Ping(DefaultURL).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if code != http.StatusOK {
t.Errorf("expected status code = %d; got %d", http.StatusOK, code)
}
if res == nil {
t.Fatalf("expected to return result, got: %v", res)
}
if res.Name == "" {
t.Errorf("expected Name != %q; got %q", "", res.Name)
}
if res.Version.Number == "" {
t.Errorf("expected Version.Number != %q; got %q", "", res.Version.Number)
}
}
func TestPingHead(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
res, code, err := client.Ping(DefaultURL).HttpHeadOnly(true).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if code != http.StatusOK {
t.Errorf("expected status code = %d; got %d", http.StatusOK, code)
}
if res != nil {
t.Errorf("expected not to return result, got: %v", res)
}
}
func TestPingHeadFailure(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
res, code, err := client.
Ping("http://127.0.0.1:9299").
HttpHeadOnly(true).
Do(context.TODO())
if err == nil {
t.Error("expected error, got nil")
}
if code == http.StatusOK {
t.Errorf("expected status code != %d; got %d", http.StatusOK, code)
}
if res != nil {
t.Errorf("expected not to return result, got: %v", res)
}
}
================================================
FILE: pit.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// PointInTime is a lightweight view into the state of the data that existed
// when initiated. It can be created with OpenPointInTime API and be used
// when searching, e.g. in Search API or with SearchSource.
type PointInTime struct {
// Id that uniquely identifies the point in time, as created with the
// OpenPointInTime API.
Id string `json:"id,omitempty"`
// KeepAlive is the time for which this specific PointInTime will be
// kept alive by Elasticsearch.
KeepAlive string `json:"keep_alive,omitempty"`
}
// NewPointInTime creates a new PointInTime.
func NewPointInTime(id string) *PointInTime {
return &PointInTime{
Id: id,
}
}
// NewPointInTimeWithKeepAlive creates a new PointInTime with the given
// time to keep alive.
func NewPointInTimeWithKeepAlive(id, keepAlive string) *PointInTime {
return &PointInTime{
Id: id,
KeepAlive: keepAlive,
}
}
// Source generates the JSON serializable fragment for the PointInTime.
func (pit *PointInTime) Source() (interface{}, error) {
if pit == nil {
return nil, nil
}
m := map[string]interface{}{
"id": pit.Id,
}
if pit.KeepAlive != "" {
m["keep_alive"] = pit.KeepAlive
}
return m, nil
}
================================================
FILE: pit_close.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
)
// ClosePointInTimeService removes a point in time.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.x/point-in-time-api.html
// for details.
type ClosePointInTimeService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
id string
bodyJson interface{}
bodyString string
}
// NewClosePointInTimeService creates a new ClosePointInTimeService.
func NewClosePointInTimeService(client *Client) *ClosePointInTimeService {
return &ClosePointInTimeService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *ClosePointInTimeService) Pretty(pretty bool) *ClosePointInTimeService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *ClosePointInTimeService) Human(human bool) *ClosePointInTimeService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *ClosePointInTimeService) ErrorTrace(errorTrace bool) *ClosePointInTimeService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *ClosePointInTimeService) FilterPath(filterPath ...string) *ClosePointInTimeService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *ClosePointInTimeService) Header(name string, value string) *ClosePointInTimeService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *ClosePointInTimeService) Headers(headers http.Header) *ClosePointInTimeService {
s.headers = headers
return s
}
// ID to close.
func (s *ClosePointInTimeService) ID(id string) *ClosePointInTimeService {
s.id = id
return s
}
// BodyJson is the document as a serializable JSON interface.
func (s *ClosePointInTimeService) BodyJson(body interface{}) *ClosePointInTimeService {
s.bodyJson = body
return s
}
// BodyString is the document encoded as a string.
func (s *ClosePointInTimeService) BodyString(body string) *ClosePointInTimeService {
s.bodyString = body
return s
}
// buildURL builds the URL for the operation.
func (s *ClosePointInTimeService) buildURL() (string, string, url.Values, error) {
var (
method = "DELETE"
path = "/_pit"
)
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
return method, path, params, nil
}
// Validate checks if the operation is valid.
func (s *ClosePointInTimeService) Validate() error {
return nil
}
// Do executes the operation.
func (s *ClosePointInTimeService) Do(ctx context.Context) (*ClosePointInTimeResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
method, path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Setup HTTP request body
var body interface{}
if s.id != "" {
body = map[string]interface{}{
"id": s.id,
}
} else if s.bodyJson != nil {
body = s.bodyJson
} else {
body = s.bodyString
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: method,
Path: path,
Params: params,
Body: body,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(ClosePointInTimeResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// ClosePointInTimeResponse is the result of closing a point in time.
type ClosePointInTimeResponse struct {
Succeeded bool `json:"succeeded,omitempty"`
NumFreed int `json:"num_freed,omitempty"`
}
================================================
FILE: pit_open.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// OpenPointInTimeService opens a point in time that can be used in subsequent
// searches.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.x/point-in-time-api.html
// for details.
type OpenPointInTimeService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
index []string
preference string
routing string
ignoreUnavailable *bool
expandWildcards string
keepAlive string
bodyJson interface{}
bodyString string
}
// NewOpenPointInTimeService creates a new OpenPointInTimeService.
func NewOpenPointInTimeService(client *Client) *OpenPointInTimeService {
return &OpenPointInTimeService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *OpenPointInTimeService) Pretty(pretty bool) *OpenPointInTimeService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *OpenPointInTimeService) Human(human bool) *OpenPointInTimeService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *OpenPointInTimeService) ErrorTrace(errorTrace bool) *OpenPointInTimeService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *OpenPointInTimeService) FilterPath(filterPath ...string) *OpenPointInTimeService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *OpenPointInTimeService) Header(name string, value string) *OpenPointInTimeService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *OpenPointInTimeService) Headers(headers http.Header) *OpenPointInTimeService {
s.headers = headers
return s
}
// Preference specifies the node or shard the operation should be performed on.
func (s *OpenPointInTimeService) Preference(preference string) *OpenPointInTimeService {
s.preference = preference
return s
}
// Index is the name of the index (or indices).
func (s *OpenPointInTimeService) Index(index ...string) *OpenPointInTimeService {
s.index = index
return s
}
// Routing is a specific routing value.
func (s *OpenPointInTimeService) Routing(routing string) *OpenPointInTimeService {
s.routing = routing
return s
}
// IgnoreUnavailable indicates whether specified concrete indices should be
// ignored when unavailable (missing or closed).
func (s *OpenPointInTimeService) IgnoreUnavailable(ignoreUnavailable bool) *OpenPointInTimeService {
s.ignoreUnavailable = &ignoreUnavailable
return s
}
// ExpandWildcards indicates whether to expand wildcard expression to
// concrete indices that are open, closed or both.
func (s *OpenPointInTimeService) ExpandWildcards(expandWildcards string) *OpenPointInTimeService {
s.expandWildcards = expandWildcards
return s
}
// KeepAlive indicates the specific time to live for the point in time.
func (s *OpenPointInTimeService) KeepAlive(keepAlive string) *OpenPointInTimeService {
s.keepAlive = keepAlive
return s
}
// BodyJson is the document as a serializable JSON interface.
func (s *OpenPointInTimeService) BodyJson(body interface{}) *OpenPointInTimeService {
s.bodyJson = body
return s
}
// BodyString is the document encoded as a string.
func (s *OpenPointInTimeService) BodyString(body string) *OpenPointInTimeService {
s.bodyString = body
return s
}
// buildURL builds the URL for the operation.
func (s *OpenPointInTimeService) buildURL() (string, string, url.Values, error) {
var err error
var method, path string
if len(s.index) > 0 {
method = "POST"
path, err = uritemplates.Expand("/{index}/_pit", map[string]string{
"index": strings.Join(s.index, ","),
})
} else {
method = "POST"
path = "/_pit"
}
if err != nil {
return "", "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.preference != "" {
params.Set("preference", s.preference)
}
if s.routing != "" {
params.Set("routing", s.routing)
}
if s.ignoreUnavailable != nil {
params.Set("ignore_unavailable", fmt.Sprintf("%v", *s.ignoreUnavailable))
}
if s.expandWildcards != "" {
params.Set("expand_wildcards", s.expandWildcards)
}
if s.keepAlive != "" {
params.Set("keep_alive", s.keepAlive)
}
return method, path, params, nil
}
// Validate checks if the operation is valid.
func (s *OpenPointInTimeService) Validate() error {
var invalid []string
if len(s.index) == 0 {
invalid = append(invalid, "Index")
}
if s.keepAlive == "" {
invalid = append(invalid, "KeepAlive")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *OpenPointInTimeService) Do(ctx context.Context) (*OpenPointInTimeResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
method, path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Setup HTTP request body
var body interface{}
if s.bodyJson != nil {
body = s.bodyJson
} else {
body = s.bodyString
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: method,
Path: path,
Params: params,
Body: body,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(OpenPointInTimeResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// OpenPointInTimeResponse is the result of opening a point in time.
type OpenPointInTimeResponse struct {
Id string `json:"id,omitempty"`
}
================================================
FILE: pit_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestPointInTimeOpenAndClose(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t) //, SetTraceLog(log.New(os.Stdout, "", 0)))
// Create a Point In Time
openResp, err := client.OpenPointInTime(testIndexName).
KeepAlive("1m").
Pretty(true).
Do(context.Background())
if err != nil {
t.Fatal(err)
}
if openResp == nil {
t.Fatal("expected non-nil Point In Time")
}
if openResp.Id == "" {
t.Fatal("expected non-blank Point In Time ID")
}
// Close the Point in Time
closeResp, err := client.ClosePointInTime(openResp.Id).Pretty(true).Do(context.Background())
if err != nil {
t.Fatal(err)
}
if closeResp == nil {
t.Fatal("expected non-nil Point In Time")
}
if want, have := true, closeResp.Succeeded; want != have {
t.Fatalf("want Succeeded=%v, have %v", want, have)
}
if want, have := 1, closeResp.NumFreed; want != have {
t.Fatalf("want NumFreed=%v, have %v", want, have)
}
}
func TestPointInTimeLifecycle(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t) //, SetTraceLog(log.New(os.Stdout, "", 0)))
// Create a Point In Time
pitResp, err := client.OpenPointInTime().
Index(testIndexName).
KeepAlive("1m").
Pretty(true).
Do(context.Background())
if err != nil {
t.Fatal(err)
}
if pitResp == nil {
t.Fatal("expected non-nil Point In Time")
}
if pitResp.Id == "" {
t.Fatal("expected non-blank Point In Time ID")
}
// We remove the documents here, but will be able to still search with
// the PIT previously created
_, err = client.DeleteByQuery(testIndexName).
Query(NewMatchAllQuery()).
Refresh("true").
Do(context.Background())
if err != nil {
t.Fatal(err)
}
// Search with the Point in Time ID
searchResult, err := client.Search().
// Index(testIndexName). // <-- you may not use indices with PointInTime!
Query(NewMatchAllQuery()).
PointInTime(NewPointInTimeWithKeepAlive(pitResp.Id, "1m")).
Size(100).
Pretty(true).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.Hits == nil {
t.Errorf("expected SearchResult.Hits != nil; got nil")
}
if got, want := searchResult.TotalHits(), int64(3); got != want {
t.Errorf("expected SearchResult.TotalHits() = %d; got %d", want, got)
}
}
================================================
FILE: plugins.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import "context"
// HasPlugin indicates whether the cluster has the named plugin.
func (c *Client) HasPlugin(name string) (bool, error) {
plugins, err := c.Plugins()
if err != nil {
return false, nil
}
for _, plugin := range plugins {
if plugin == name {
return true, nil
}
}
return false, nil
}
// Plugins returns the list of all registered plugins.
func (c *Client) Plugins() ([]string, error) {
stats, err := c.ClusterStats().Do(context.Background())
if err != nil {
return nil, err
}
if stats == nil {
return nil, err
}
if stats.Nodes == nil {
return nil, err
}
var plugins []string
for _, plugin := range stats.Nodes.Plugins {
plugins = append(plugins, plugin.Name)
}
return plugins, nil
}
================================================
FILE: plugins_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import "testing"
func TestClientPlugins(t *testing.T) {
client, err := NewClient()
if err != nil {
t.Fatal(err)
}
_, err = client.Plugins()
if err != nil {
t.Fatal(err)
}
}
func TestClientHasPlugin(t *testing.T) {
client, err := NewClient()
if err != nil {
t.Fatal(err)
}
found, err := client.HasPlugin("no-such-plugin")
if err != nil {
t.Fatal(err)
}
if found {
t.Fatalf("expected to not find plugin %q", "no-such-plugin")
}
}
================================================
FILE: query.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// Query represents the generic query interface. A query's sole purpose
// is to return the source of the query as a JSON-serializable object.
// Returning map[string]interface{} is the norm for queries.
type Query interface {
// Source returns the JSON-serializable query request.
Source() (interface{}, error)
}
================================================
FILE: recipes/aws-connect/.gitignore
================================================
/aws-connect
================================================
FILE: recipes/aws-connect/main.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
// Connect simply connects to Elasticsearch Service on AWS.
//
// Example
//
// aws-connect -url=https://search-xxxxx-yyyyy.eu-central-1.es.amazonaws.com
//
package main
import (
"flag"
"fmt"
"log"
"github.com/olivere/env"
awsauth "github.com/smartystreets/go-aws-auth"
"github.com/olivere/elastic/v7"
"github.com/olivere/elastic/v7/aws"
)
func main() {
var (
accessKey = flag.String("access-key", env.String("", "AWS_ACCESS_KEY"), "Access Key ID")
secretKey = flag.String("secret-key", env.String("", "AWS_SECRET_KEY"), "Secret access key")
url = flag.String("url", "http://localhost:9200", "Elasticsearch URL")
sniff = flag.Bool("sniff", false, "Enable or disable sniffing")
)
flag.Parse()
log.SetFlags(0)
if *url == "" {
*url = "http://127.0.0.1:9200"
}
if *accessKey == "" {
log.Fatal("missing -access-key or AWS_ACCESS_KEY environment variable")
}
if *secretKey == "" {
log.Fatal("missing -secret-key or AWS_SECRET_KEY environment variable")
}
signingClient := aws.NewV4SigningClient(awsauth.Credentials{
AccessKeyID: *accessKey,
SecretAccessKey: *secretKey,
})
// Create an Elasticsearch client
client, err := elastic.NewClient(
elastic.SetURL(*url),
elastic.SetSniff(*sniff),
elastic.SetHealthcheck(*sniff),
elastic.SetHttpClient(signingClient),
)
if err != nil {
log.Fatal(err)
}
_ = client
// Just a status message
fmt.Println("Connection succeeded")
}
================================================
FILE: recipes/aws-connect-v4/.gitignore
================================================
/aws-connect-v4
================================================
FILE: recipes/aws-connect-v4/main.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
// Connect simply connects to Elasticsearch Service on AWS.
//
// Example
//
// aws-connect-v4 -url=https://search-xxxxx-yyyyy.eu-central-1.es.amazonaws.com
//
package main
import (
"flag"
"fmt"
"log"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/olivere/env"
"github.com/olivere/elastic/v7"
aws "github.com/olivere/elastic/v7/aws/v4"
)
func main() {
var (
accessKey = flag.String("access-key", env.String("", "AWS_ACCESS_KEY", "AWS_ACCESS_KEY_ID"), "Access Key ID")
secretKey = flag.String("secret-key", env.String("", "AWS_SECRET_KEY", "AWS_SECRET_ACCESS_KEY"), "Secret access key")
url = flag.String("url", "", "Elasticsearch URL")
sniff = flag.Bool("sniff", false, "Enable or disable sniffing")
region = flag.String("region", "eu-west-1", "AWS Region name")
)
flag.Parse()
log.SetFlags(0)
if *url == "" {
log.Fatal("please specify a URL with -url")
}
if *accessKey == "" {
log.Fatal("missing -access-key or AWS_ACCESS_KEY environment variable")
}
if *secretKey == "" {
log.Fatal("missing -secret-key or AWS_SECRET_KEY environment variable")
}
if *region == "" {
log.Fatal("please specify an AWS region with -region")
}
signingClient := aws.NewV4SigningClient(credentials.NewStaticCredentials(
*accessKey,
*secretKey,
"",
), *region)
// Create an Elasticsearch client
client, err := elastic.NewClient(
elastic.SetURL(*url),
elastic.SetSniff(*sniff),
elastic.SetHealthcheck(*sniff),
elastic.SetHttpClient(signingClient),
)
if err != nil {
log.Fatal(err)
}
_ = client
// Just a status message
fmt.Println("Connection succeeded")
}
================================================
FILE: recipes/aws-mapping-v4/.gitignore
================================================
/aws-mapping-v4
================================================
FILE: recipes/aws-mapping-v4/main.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
// Connect creates an index with a mapping with different data types.
//
// Example
//
//
// aws-mapping-v4 -url=https://search-xxxxx-yyyyy.eu-central-1.es.amazonaws.com -index=twitter -type=tweet -sniff=false
//
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"log"
"os"
"time"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/olivere/env"
"github.com/olivere/elastic/v7"
aws "github.com/olivere/elastic/v7/aws/v4"
)
const (
mapping = `
{
"settings":{
"number_of_shards":1,
"number_of_replicas":0
},
"mappings":{
"_doc":{
"properties":{
"user":{
"type":"keyword"
},
"message":{
"type":"text"
},
"retweets":{
"type":"integer"
},
"created":{
"type":"date"
},
"attributes":{
"type":"object"
}
}
}
}
}
`
)
// Tweet is just an example document.
type Tweet struct {
User string `json:"user"`
Message string `json:"message"`
Retweets int `json:"retweets"`
Created time.Time `json:"created"`
Attrs map[string]interface{} `json:"attributes,omitempty"`
}
func main() {
var (
accessKey = flag.String("access-key", env.String("", "AWS_ACCESS_KEY", "AWS_ACCESS_KEY_ID"), "Access Key ID")
secretKey = flag.String("secret-key", env.String("", "AWS_SECRET_KEY", "AWS_SECRET_ACCESS_KEY"), "Secret access key")
url = flag.String("url", "", "Elasticsearch URL")
sniff = flag.Bool("sniff", false, "Enable or disable sniffing")
trace = flag.Bool("trace", false, "Enable or disable tracing")
index = flag.String("index", "", "Index name")
region = flag.String("region", "eu-west-1", "AWS Region name")
)
flag.Parse()
log.SetFlags(log.LstdFlags | log.Lshortfile)
if *url == "" {
log.Fatal("please specify a URL with -url")
}
if *index == "" {
log.Fatal("please specify an index name with -index")
}
if *region == "" {
log.Fatal("please specify an AWS region with -region")
}
// Create an Elasticsearch client
signingClient := aws.NewV4SigningClient(credentials.NewStaticCredentials(
*accessKey,
*secretKey,
"",
), *region)
// Create an Elasticsearch client
opts := []elastic.ClientOptionFunc{
elastic.SetURL(*url),
elastic.SetSniff(*sniff),
elastic.SetHealthcheck(*sniff),
elastic.SetHttpClient(signingClient),
}
if *trace {
opts = append(opts, elastic.SetTraceLog(log.New(os.Stdout, "", 0)))
}
client, err := elastic.NewClient(opts...)
if err != nil {
log.Fatal(err)
}
// Check if index already exists. We'll drop it then.
// Next, we create a fresh index/mapping.
ctx := context.Background()
exists, err := client.IndexExists(*index).Pretty(true).Do(ctx)
if err != nil {
log.Fatal(err)
}
if exists {
_, err := client.DeleteIndex(*index).Pretty(true).Do(ctx)
if err != nil {
log.Fatal(err)
}
}
_, err = client.CreateIndex(*index).Body(mapping).Pretty(true).Do(ctx)
if err != nil {
log.Fatal(err)
}
// Add a tweet
{
tweet := Tweet{
User: "olivere",
Message: "Welcome to Go and Elasticsearch.",
Retweets: 0,
Created: time.Now(),
Attrs: map[string]interface{}{
"views": 17,
"vip": true,
},
}
_, err := client.Index().
Index(*index).
Type("_doc").
Id("1").
BodyJson(&tweet).
Refresh("true").
Pretty(true).
Do(context.TODO())
if err != nil {
log.Fatal(err)
}
}
// Read the tweet
{
doc, err := client.Get().
Index(*index).
Type("_doc").
Id("1").
Pretty(true).
Do(context.TODO())
if err != nil {
log.Fatal(err)
}
var tweet Tweet
if err = json.Unmarshal(doc.Source, &tweet); err != nil {
log.Fatal(err)
}
fmt.Printf("%s at %s: %s (%d retweets)\n",
tweet.User,
tweet.Created,
tweet.Message,
tweet.Retweets,
)
fmt.Printf(" %v\n", tweet.Attrs)
}
}
================================================
FILE: recipes/bulk_insert/.gitignore
================================================
/bulk_insert
================================================
FILE: recipes/bulk_insert/bulk_insert.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
// BulkInsert illustrates how to bulk insert documents into Elasticsearch.
//
// It uses two goroutines to do so. The first creates a simple document
// and sends it to the second via a channel. The second goroutine collects
// those documents, creates a bulk request that is added to a Bulk service
// and committed to Elasticsearch after reaching a number of documents.
// The number of documents after which a commit happens can be specified
// via the "bulk-size" flag.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-bulk.html
// for details on the Bulk API in Elasticsearch.
//
// Example
//
// Bulk index 100.000 documents into the index "warehouse", type "product",
// committing every set of 1.000 documents.
//
// bulk_insert -index=warehouse -type=product -n=100000 -bulk-size=1000
//
package main
import (
"context"
"encoding/base64"
"errors"
"flag"
"fmt"
"log"
"math/rand"
"sync/atomic"
"time"
"golang.org/x/sync/errgroup"
"github.com/olivere/elastic/v7"
)
func main() {
var (
url = flag.String("url", "http://localhost:9200", "Elasticsearch URL")
index = flag.String("index", "", "Elasticsearch index name")
typ = flag.String("type", "", "Elasticsearch type name")
sniff = flag.Bool("sniff", true, "Enable or disable sniffing")
n = flag.Int("n", 0, "Number of documents to bulk insert")
bulkSize = flag.Int("bulk-size", 0, "Number of documents to collect before committing")
)
flag.Parse()
log.SetFlags(0)
rand.Seed(time.Now().UnixNano())
if *url == "" {
log.Fatal("missing url parameter")
}
if *index == "" {
log.Fatal("missing index parameter")
}
if *typ == "" {
log.Fatal("missing type parameter")
}
if *n <= 0 {
log.Fatal("n must be a positive number")
}
if *bulkSize <= 0 {
log.Fatal("bulk-size must be a positive number")
}
// Create an Elasticsearch client
client, err := elastic.NewClient(elastic.SetURL(*url), elastic.SetSniff(*sniff))
if err != nil {
log.Fatal(err)
}
// Setup a group of goroutines from the excellent errgroup package
g, ctx := errgroup.WithContext(context.TODO())
// The first goroutine will emit documents and send it to the second goroutine
// via the docsc channel.
// The second Goroutine will simply bulk insert the documents.
type doc struct {
ID string `json:"id"`
Timestamp time.Time `json:"@timestamp"`
}
docsc := make(chan doc)
begin := time.Now()
// Goroutine to create documents
g.Go(func() error {
defer close(docsc)
buf := make([]byte, 32)
for i := 0; i < *n; i++ {
// Generate a random ID
_, err := rand.Read(buf)
if err != nil {
return err
}
id := base64.URLEncoding.EncodeToString(buf)
// Construct the document
d := doc{
ID: id,
Timestamp: time.Now(),
}
// Send over to 2nd goroutine, or cancel
select {
case docsc <- d:
case <-ctx.Done():
return ctx.Err()
}
}
return nil
})
// Second goroutine will consume the documents sent from the first and bulk insert into ES
var total uint64
g.Go(func() error {
bulk := client.Bulk().Index(*index).Type(*typ)
for d := range docsc {
// Simple progress
current := atomic.AddUint64(&total, 1)
dur := time.Since(begin).Seconds()
sec := int(dur)
pps := int64(float64(current) / dur)
fmt.Printf("%10d | %6d req/s | %02d:%02d\r", current, pps, sec/60, sec%60)
// Enqueue the document
bulk.Add(elastic.NewBulkIndexRequest().Id(d.ID).Doc(d))
if bulk.NumberOfActions() >= *bulkSize {
// Commit
res, err := bulk.Do(ctx)
if err != nil {
return err
}
if res.Errors {
// Look up the failed documents with res.Failed(), and e.g. recommit
return errors.New("bulk commit failed")
}
// "bulk" is reset after Do, so you can reuse it
}
select {
default:
case <-ctx.Done():
return ctx.Err()
}
}
// Commit the final batch before exiting
if bulk.NumberOfActions() > 0 {
_, err = bulk.Do(ctx)
if err != nil {
return err
}
}
return nil
})
// Wait until all goroutines are finished
if err := g.Wait(); err != nil {
log.Fatal(err)
}
// Final results
dur := time.Since(begin).Seconds()
sec := int(dur)
pps := int64(float64(total) / dur)
fmt.Printf("%10d | %6d req/s | %02d:%02d\n", total, pps, sec/60, sec%60)
}
================================================
FILE: recipes/bulk_processor/.gitignore
================================================
/bulk_processor
================================================
FILE: recipes/bulk_processor/main.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
// BulkProcessor runs a bulk processing job that fills an index
// given certain criteria like flush interval etc.
//
// Example
//
// bulk_processor -url=http://127.0.0.1:9200/bulk-processor-test?sniff=false -n=100000 -flush-interval=1s
//
package main
import (
"context"
"flag"
"fmt"
"log"
"math/rand"
"os"
"os/signal"
"sync/atomic"
"syscall"
"time"
"github.com/google/uuid"
"github.com/olivere/elastic/v7"
"github.com/olivere/elastic/v7/config"
)
func main() {
var (
url = flag.String("url", "http://localhost:9200/bulk-processor-test", "Elasticsearch URL")
numWorkers = flag.Int("num-workers", 4, "Number of workers")
n = flag.Int64("n", -1, "Number of documents to process (-1 for unlimited)")
flushInterval = flag.Duration("flush-interval", 1*time.Second, "Flush interval")
bulkActions = flag.Int("bulk-actions", 0, "Number of bulk actions before committing")
bulkSize = flag.Int("bulk-size", 0, "Size of bulk requests before committing")
)
flag.Parse()
log.SetFlags(0)
rand.Seed(time.Now().UnixNano())
// Parse configuration from URL
cfg, err := config.Parse(*url)
if err != nil {
log.Fatal(err)
}
// Create an Elasticsearch client from the parsed config
client, err := elastic.NewClientFromConfig(cfg)
if err != nil {
log.Fatal(err)
}
// Drop old index
exists, err := client.IndexExists(cfg.Index).Do(context.Background())
if err != nil {
log.Fatal(err)
}
if exists {
_, err = client.DeleteIndex(cfg.Index).Do(context.Background())
if err != nil {
log.Fatal(err)
}
}
// Create processor
bulkp := elastic.NewBulkProcessorService(client).
Name("bulk-test-processor").
Stats(true).
Backoff(elastic.StopBackoff{}).
FlushInterval(*flushInterval).
Workers(*numWorkers)
if *bulkActions > 0 {
bulkp = bulkp.BulkActions(*bulkActions)
}
if *bulkSize > 0 {
bulkp = bulkp.BulkSize(*bulkSize)
}
p, err := bulkp.Do(context.Background())
if err != nil {
log.Fatal(err)
}
var created int64
errc := make(chan error, 1)
go func() {
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
<-c
errc <- nil
}()
go func() {
defer func() {
if err := p.Close(); err != nil {
errc <- err
}
}()
type Doc struct {
Timestamp time.Time `json:"@timestamp"`
}
for {
current := atomic.AddInt64(&created, 1)
if *n > 0 && current >= *n {
errc <- nil
return
}
r := elastic.NewBulkIndexRequest().
Index(cfg.Index).
Id(uuid.New().String()).
Doc(Doc{Timestamp: time.Now()})
p.Add(r)
time.Sleep(time.Duration(rand.Intn(1000)) * time.Microsecond)
}
}()
go func() {
t := time.NewTicker(1 * time.Second)
defer t.Stop()
for range t.C {
stats := p.Stats()
written := atomic.LoadInt64(&created)
var queued int64
for _, w := range stats.Workers {
queued += w.Queued
}
fmt.Printf("Queued=%5d Written=%8d Succeeded=%8d Failed=%8d Committed=%6d Flushed=%6d\n",
queued,
written,
stats.Succeeded,
stats.Failed,
stats.Committed,
stats.Flushed,
)
}
}()
if err := <-errc; err != nil {
log.Fatal(err)
}
}
================================================
FILE: recipes/connect/.gitignore
================================================
/connect
================================================
FILE: recipes/connect/connect.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
// Connect simply connects to Elasticsearch.
//
// Example
//
//
// connect -url=http://127.0.0.1:9200 -sniff=false
//
package main
import (
"flag"
"fmt"
"log"
"github.com/olivere/elastic/v7"
)
func main() {
var (
url = flag.String("url", "http://localhost:9200", "Elasticsearch URL")
sniff = flag.Bool("sniff", true, "Enable or disable sniffing")
)
flag.Parse()
log.SetFlags(0)
if *url == "" {
*url = "http://127.0.0.1:9200"
}
// Create an Elasticsearch client
client, err := elastic.NewClient(elastic.SetURL(*url), elastic.SetSniff(*sniff))
if err != nil {
log.Fatal(err)
}
_ = client
// Just a status message
fmt.Println("Connection succeeded")
}
================================================
FILE: recipes/connect_with_config/.gitignore
================================================
/connect_with_config
================================================
FILE: recipes/connect_with_config/connect_with_config.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
// Connect simply connects to Elasticsearch.
//
// Example
//
//
// connect "http://127.0.0.1:9200/test-index?sniff=false&healthcheck=false"
//
package main
import (
"flag"
"fmt"
"log"
"runtime"
"github.com/olivere/elastic/v7"
"github.com/olivere/elastic/v7/config"
)
func main() {
flag.Parse()
log.SetFlags(0)
url := flag.Arg(0)
if url == "" {
url = "http://127.0.0.1:9200"
}
// Parse the URL with the config package
cfg, err := config.Parse(url)
if err != nil {
log.Fatal(err)
}
// Create an Elasticsearch client
client, err := elastic.NewClientFromConfig(cfg)
if err != nil {
log.Fatal(err)
}
// Get ES version
esversion, err := client.ElasticsearchVersion(cfg.URL)
if err != nil {
log.Fatal(err)
}
// Just a status message
fmt.Printf("Connection succeeded with %v, Elastic %v and Elasticsearch %s\n",
runtime.Version(),
elastic.Version,
esversion,
)
}
================================================
FILE: recipes/go.mod
================================================
module github.com/olivere/elastic/v7/recipes
go 1.17
require (
github.com/aws/aws-sdk-go v1.43.21
github.com/google/uuid v1.3.0
github.com/olivere/elastic/v7 v7.0.0-00010101000000-000000000000
github.com/olivere/env v1.1.0
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9
github.com/uber/jaeger-client-go v2.25.0+incompatible
github.com/uber/jaeger-lib v2.2.0+incompatible
go.opentelemetry.io/otel v1.5.0
go.opentelemetry.io/otel/exporters/jaeger v1.5.0
go.opentelemetry.io/otel/sdk v1.5.0
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
)
require (
github.com/VividCortex/gohistogram v1.0.0 // indirect
github.com/go-kit/kit v0.10.0 // indirect
github.com/go-logr/logr v1.2.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/smartystreets/gunit v1.4.3 // indirect
go.opentelemetry.io/otel/trace v1.5.0 // indirect
go.uber.org/atomic v1.6.0 // indirect
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
)
replace github.com/olivere/elastic/v7 => ../../elastic
================================================
FILE: recipes/go.sum
================================================
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.43.21 h1:E4S2eX3d2gKJyI/ISrcIrSwXwqjIvCK85gtBMt4sAPE=
github.com/aws/aws-sdk-go v1.43.21/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.10.0 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logr/logr v1.2.2 h1:ahHml/yUpnlb96Rp8HCvtYVPY8ZYpxq3g7UYchIYwbs=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/olivere/env v1.1.0 h1:owp/uwMwhru5668JjMDp8UTG3JGT27GTCk4ufYQfaTw=
github.com/olivere/env v1.1.0/go.mod h1:zaoXy53SjZfxqZBGiGrZCkuVLYPdwrc+vArPuUVhJdQ=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.1.1 h1:T/YLemO5Yp7KPzS+lVtu+WsHn8yoSwTfItdAd1r3cck=
github.com/smartystreets/assertions v1.1.1/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 h1:hp2CYQUINdZMHdvTdXtPOY2ainKl4IoMcpAXEf2xj3Q=
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smartystreets/gunit v1.4.2/go.mod h1:ZjM1ozSIMJlAz/ay4SG8PeKF00ckUp+zMHZXV9/bvak=
github.com/smartystreets/gunit v1.4.3 h1:yhnYXSXKK/R5vaUVGZD1zw4CQZqwtor7pp0VXBbMDrc=
github.com/smartystreets/gunit v1.4.3/go.mod h1:ciyZ4dVTN2S8npQ4g9lMdARLX5XboUpcq5O7N6GlQmw=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/uber/jaeger-client-go v2.25.0+incompatible h1:IxcNZ7WRY1Y3G4poYlx24szfsn/3LvK9QHCq9oQw8+U=
github.com/uber/jaeger-client-go v2.25.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
github.com/uber/jaeger-lib v2.2.0+incompatible h1:MxZXOiR2JuoANZ3J6DE/U0kSFv/eJ/GfSYVCjK7dyaw=
github.com/uber/jaeger-lib v2.2.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.opentelemetry.io/otel v1.5.0 h1:DhCU8oR2sJH9rfnwPdoV/+BJ7UIN5kXHL8DuSGrPU8E=
go.opentelemetry.io/otel v1.5.0/go.mod h1:Jm/m+rNp/z0eqJc74H7LPwQ3G87qkU/AnnAydAjSAHk=
go.opentelemetry.io/otel/exporters/jaeger v1.5.0 h1:ZR7nhLSfLufS5AHk/iN11Q+W9XYwsJrVZ1Frb833d+Y=
go.opentelemetry.io/otel/exporters/jaeger v1.5.0/go.mod h1:rSeUArMBRe1eQLo1T0WxOazohN1M2mYThWJQmn1BjRQ=
go.opentelemetry.io/otel/sdk v1.5.0 h1:QKhWBbcOC9fDCZKCfPFjWTWpfIlJR+i9xiUDYrLVmZs=
go.opentelemetry.io/otel/sdk v1.5.0/go.mod h1:CU4J1v+7iEljnm1G14QjdFWOXUyYLHVh0Lh+/BTYyFg=
go.opentelemetry.io/otel/trace v1.5.0 h1:AKQZ9zJsBRFAp7zLdyGNkqG2rToCDIt3i5tcLzQlbmU=
go.opentelemetry.io/otel/trace v1.5.0/go.mod h1:sq55kfhjXYr1zVSyexg0w1mpa03AYXR5eyTkB9NPPdE=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114 h1:DnSr2mCsxyCE6ZgIkmcWUQY2R5cH/6wL7eIxEmQOMSE=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
================================================
FILE: recipes/mapping/.gitignore
================================================
/mapping
================================================
FILE: recipes/mapping/mapping.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
// Connect creates an index with a mapping with different data types.
//
// Example
//
// mapping -url=http://127.0.0.1:9200 -index=twitter
//
package main
import (
"bytes"
"context"
"encoding/json"
"flag"
"fmt"
"html/template"
"log"
"time"
"github.com/olivere/elastic/v7"
)
const (
mapping = `
{
"settings":{
"number_of_shards": {{.NumberOfShards}},
"number_of_replicas": {{.NumberOfReplicas}}
},
"mappings":{
"properties":{
"user":{
"type":"keyword"
},
"message":{
"type":"text"
},
"retweets":{
"type":"integer"
},
"created":{
"type":"date"
},
"attributes":{
"type":"object"
}
}
}
}
`
)
// Tweet is just an example document.
type Tweet struct {
User string `json:"user"`
Message string `json:"message"`
Retweets int `json:"retweets"`
Created time.Time `json:"created"`
Attrs map[string]interface{} `json:"attributes,omitempty"`
}
func main() {
var (
url = flag.String("url", "http://localhost:9200", "Elasticsearch URL")
sniff = flag.Bool("sniff", true, "Enable or disable sniffing")
index = flag.String("index", "", "Index name")
shards = flag.Int("shards", 1, "Number of shards")
replicas = flag.Int("replicas", 0, "Number of replicas")
)
flag.Parse()
log.SetFlags(0)
if *url == "" {
*url = "http://127.0.0.1:9200"
}
if *index == "" {
log.Fatal("please specify an index name -index")
}
// Create an Elasticsearch client
client, err := elastic.NewClient(elastic.SetURL(*url), elastic.SetSniff(*sniff))
if err != nil {
log.Fatal(err)
}
_ = client
// Check if index already exists. We'll drop it then.
// Next, we create a fresh index/mapping.
ctx := context.Background()
exists, err := client.IndexExists(*index).Do(ctx)
if err != nil {
log.Fatal(err)
}
if exists {
_, err := client.DeleteIndex(*index).Do(ctx)
if err != nil {
log.Fatal(err)
}
}
// Dynamically create the index with the specified number of shards/replicas
tmpl, err := template.New("T").Parse(mapping)
if err != nil {
log.Fatal(err)
}
var body bytes.Buffer
err = tmpl.ExecuteTemplate(&body, "T", struct {
NumberOfShards int
NumberOfReplicas int
}{
NumberOfShards: *shards,
NumberOfReplicas: *replicas,
})
if err != nil {
log.Fatal(err)
}
_, err = client.CreateIndex(*index).BodyString(body.String()).Do(ctx)
if err != nil {
log.Fatal(err)
}
// Add a tweet
{
tweet := Tweet{
User: "olivere",
Message: "Welcome to Go and Elasticsearch.",
Retweets: 0,
Created: time.Now(),
Attrs: map[string]interface{}{
"views": 17,
"vip": true,
},
}
_, err := client.Index().
Index(*index).
Id("1").
BodyJson(&tweet).
Refresh("true").
Do(context.TODO())
if err != nil {
log.Fatal(err)
}
}
// Read the tweet
{
doc, err := client.Get().
Index(*index).
Id("1").
Do(context.TODO())
if err != nil {
log.Fatal(err)
}
var tweet Tweet
if err = json.Unmarshal(doc.Source, &tweet); err != nil {
log.Fatal(err)
}
fmt.Printf("%s at %s: %s (%d retweets)\n",
tweet.User,
tweet.Created,
tweet.Message,
tweet.Retweets,
)
fmt.Printf(" %v\n", tweet.Attrs)
}
}
================================================
FILE: recipes/middleware/.gitignore
================================================
/middleware
================================================
FILE: recipes/middleware/main.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
// Middleware via HTTP RoundTripper.
//
// Example
//
// middleware "http://127.0.0.1:9200/test-index?sniff=false&healthcheck=false"
//
package main
import (
"flag"
"fmt"
"log"
"net/http"
"sync/atomic"
"github.com/olivere/elastic/v7"
)
// CountingTransport will count requests.
type CountingTransport struct {
N int64 // number of requests passing this transport
next http.RoundTripper // next round-tripper or http.DefaultTransport if nil
}
// RoundTrip implements a transport that will count requests.
func (tr *CountingTransport) RoundTrip(r *http.Request) (*http.Response, error) {
atomic.AddInt64(&tr.N, 1)
if tr.next != nil {
return tr.next.RoundTrip(r)
}
return http.DefaultTransport.RoundTrip(r)
}
func main() {
var (
url = flag.String("url", "http://localhost:9200", "Elasticsearch URL")
sniff = flag.Bool("sniff", true, "Enable or disable sniffing")
)
flag.Parse()
log.SetFlags(0)
if *url == "" {
*url = "http://127.0.0.1:9200"
}
tr := &CountingTransport{}
// Create an Elasticsearch client
client, err := elastic.NewClient(
elastic.SetURL(*url),
elastic.SetSniff(*sniff),
elastic.SetHttpClient(&http.Client{
Transport: tr,
}),
)
if err != nil {
log.Fatal(err)
}
// Get ES version
indices, err := client.IndexNames()
if err != nil {
log.Fatal(err)
}
for _, index := range indices {
fmt.Println(index)
}
// Just a status message
fmt.Printf("%d requests executed.\n", tr.N)
}
================================================
FILE: recipes/scroll/.gitignore
================================================
/scroll
================================================
FILE: recipes/scroll/scroll.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
// Scroll illustrates scrolling through a set of documents.
//
// Example
//
// Scroll through an index called "products".
// Use "_uid" as the default field:
//
// scroll -index=products -size=100
//
package main
import (
"context"
"flag"
"fmt"
"io"
"log"
"sync/atomic"
"time"
"golang.org/x/sync/errgroup"
"github.com/olivere/elastic/v7"
)
func main() {
var (
url = flag.String("url", "http://localhost:9200", "Elasticsearch URL")
index = flag.String("index", "", "Elasticsearch index name")
size = flag.Int("size", 100, "Slice of documents to get per scroll")
sniff = flag.Bool("sniff", true, "Enable or disable sniffing")
)
flag.Parse()
log.SetFlags(0)
if *url == "" {
log.Fatal("missing url parameter")
}
if *index == "" {
log.Fatal("missing index parameter")
}
if *size <= 0 {
log.Fatal("size must be greater than zero")
}
// Create an Elasticsearch client
client, err := elastic.NewClient(elastic.SetURL(*url), elastic.SetSniff(*sniff))
if err != nil {
log.Fatal(err)
}
// Setup a group of goroutines from the excellent errgroup package
g, ctx := errgroup.WithContext(context.TODO())
// Hits channel will be sent to from the first set of goroutines and consumed by the second
type hit struct {
Slice int
Hit elastic.SearchHit
}
hitsc := make(chan hit)
begin := time.Now()
// Start goroutine for this sliced scroll
g.Go(func() error {
defer close(hitsc)
// Prepare the query
var query elastic.Query
query = elastic.NewMatchAllQuery()
svc := client.Scroll(*index).Query(query)
for {
res, err := svc.Do(ctx)
if err == io.EOF {
break
}
if err != nil {
return err
}
for _, searchHit := range res.Hits.Hits {
// Pass the hit to the hits channel, which will be consumed below
select {
case hitsc <- hit{Hit: *searchHit}:
case <-ctx.Done():
return ctx.Err()
}
}
}
return nil
})
// Second goroutine will consume the hits sent from the workers in first set of goroutines
var total uint64
g.Go(func() error {
for range hitsc {
// We simply count the hits here.
current := atomic.AddUint64(&total, 1)
sec := int(time.Since(begin).Seconds())
fmt.Printf("%8d | %02d:%02d\r", current, sec/60, sec%60)
select {
default:
case <-ctx.Done():
return ctx.Err()
}
}
return nil
})
// Wait until all goroutines are finished
if err := g.Wait(); err != nil {
log.Fatal(err)
}
fmt.Printf("Scrolled through a total of %d documents in %v\n", total, time.Since(begin))
}
================================================
FILE: recipes/search_with_point_in_time/.gitignore
================================================
/search_with_point_in_time
================================================
FILE: recipes/search_with_point_in_time/search.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
// Search illustrates how to search using the Point in Time API.
//
// Example
//
// Scroll through an index called "products".
//
// ./search_with_point_in_time -index=products -size=100
//
// If you don't have an index, use the "populate" command to fill one.
//
// ./search_with_point_in_time -index=products populate
//
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"log"
"github.com/olivere/elastic/v7"
)
func main() {
var (
url = flag.String("url", "http://localhost:9200", "Elasticsearch URL")
index = flag.String("index", "", "Elasticsearch index name")
size = flag.Int("size", 10, "Slice of documents to get per scroll")
sniff = flag.Bool("sniff", true, "Enable or disable sniffing")
)
flag.Parse()
log.SetFlags(0)
if *url == "" {
log.Fatal("missing url parameter")
}
if *index == "" {
log.Fatal("missing index parameter")
}
if *size <= 0 {
log.Fatal("size must be greater than zero")
}
// Create an Elasticsearch client
client, err := elastic.NewClient(elastic.SetURL(*url), elastic.SetSniff(*sniff))
if err != nil {
log.Fatal(err)
}
// Use "search ... populate" to populate the index with random data
if flag.Arg(0) == "populate" {
if err := populate(client, *index); err != nil {
log.Fatal(err)
}
return
}
// Open a Point in Time
pit, err := client.OpenPointInTime(*index).KeepAlive("2m").Do(context.Background())
if err != nil {
log.Fatal(err)
}
// Notice that at this point we could even delete the documents
// from the index: The Point in Time will make sure that we still
// get the results when the Point in Time has been created.
//
// Notice that you must not pass an index, a routing, or a preference
// to the Search API: Those values are taken from the Point in Time.
res, err := client.Search().
Query(
// Return random results
elastic.NewFunctionScoreQuery().AddScoreFunc(elastic.NewRandomFunction()),
).
Size(*size).
PointInTime(
elastic.NewPointInTimeWithKeepAlive(pit.Id, "2m"),
).
Do(context.Background())
if err != nil {
log.Fatal(err)
}
for _, hit := range res.Hits.Hits {
var doc map[string]interface{}
if err := json.Unmarshal(hit.Source, &doc); err != nil {
log.Fatal(err)
}
fmt.Printf("%v\n", doc)
}
}
// populate will fill an example index.
func populate(client *elastic.Client, indexName string) error {
bulk := client.Bulk().Index(indexName)
for i := 0; i < 10000; i++ {
doc := map[string]interface{}{
"name": fmt.Sprintf("Product %d", i+1),
}
bulk = bulk.Add(elastic.NewBulkIndexRequest().
Id(fmt.Sprint(i)).
Doc(doc),
)
if bulk.NumberOfActions() >= 100 {
_, err := bulk.Do(context.Background())
if err != nil {
return err
}
// bulk is reset after Do, so you can reuse it
// We ignore indexing errors here though!
}
}
if bulk.NumberOfActions() > 0 {
_, err := bulk.Do(context.Background())
if err != nil {
return err
}
}
return nil
}
================================================
FILE: recipes/sliced_scroll/.gitignore
================================================
/sliced_scroll
================================================
FILE: recipes/sliced_scroll/sliced_scroll.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
// SlicedScroll illustrates scrolling through a set of documents
// in parallel. It uses the sliced scrolling feature introduced
// in Elasticsearch 5.0 to create a number of Goroutines, each
// scrolling through a slice of the total results. A second goroutine
// receives the hits from the set of goroutines scrolling through
// the slices and simply counts the total number and the number of
// documents received per slice.
//
// The speedup of sliced scrolling can be significant but is very
// dependent on the specific use case.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-request-scroll.html#sliced-scroll
// for details on sliced scrolling in Elasticsearch.
//
// Example
//
// Scroll with 4 parallel slices through an index called "products".
// Use "_uid" as the default field:
//
// sliced_scroll -index=products -n=4
//
package main
import (
"context"
"flag"
"fmt"
"io"
"log"
"sync"
"sync/atomic"
"time"
"golang.org/x/sync/errgroup"
"github.com/olivere/elastic/v7"
)
func main() {
var (
url = flag.String("url", "http://localhost:9200", "Elasticsearch URL")
index = flag.String("index", "", "Elasticsearch index name")
field = flag.String("field", "", "Slice field (must be numeric)")
numSlices = flag.Int("n", 2, "Number of slices to use in parallel")
sniff = flag.Bool("sniff", true, "Enable or disable sniffing")
)
flag.Parse()
log.SetFlags(0)
if *url == "" {
log.Fatal("missing url parameter")
}
if *index == "" {
log.Fatal("missing index parameter")
}
if *numSlices <= 0 {
log.Fatal("n must be greater than zero")
}
// Create an Elasticsearch client
client, err := elastic.NewClient(elastic.SetURL(*url), elastic.SetSniff(*sniff))
if err != nil {
log.Fatal(err)
}
// Setup a group of goroutines from the excellent errgroup package
g, ctx := errgroup.WithContext(context.TODO())
// Hits channel will be sent to from the first set of goroutines and consumed by the second
type hit struct {
Slice int
Hit elastic.SearchHit
}
hitsc := make(chan hit)
begin := time.Now()
// Start a number of goroutines to parallelize scrolling
var wg sync.WaitGroup
for i := 0; i < *numSlices; i++ {
wg.Add(1)
slice := i
// Prepare the query
var query elastic.Query
query = elastic.NewMatchAllQuery()
// Prepare the slice
sliceQuery := elastic.NewSliceQuery().Id(i).Max(*numSlices)
if *field != "" {
sliceQuery = sliceQuery.Field(*field)
}
// Start goroutine for this sliced scroll
g.Go(func() error {
defer wg.Done()
svc := client.Scroll(*index).Query(query).Slice(sliceQuery)
for {
res, err := svc.Do(ctx)
if err == io.EOF {
break
}
if err != nil {
return err
}
for _, searchHit := range res.Hits.Hits {
// Pass the hit to the hits channel, which will be consumed below
select {
case hitsc <- hit{Slice: slice, Hit: *searchHit}:
case <-ctx.Done():
return ctx.Err()
}
}
}
return nil
})
}
go func() {
// Wait until all scrolling is done
wg.Wait()
close(hitsc)
}()
// Second goroutine will consume the hits sent from the workers in first set of goroutines
var total uint64
totals := make([]uint64, *numSlices)
g.Go(func() error {
for hit := range hitsc {
// We simply count the hits here.
atomic.AddUint64(&totals[hit.Slice], 1)
current := atomic.AddUint64(&total, 1)
sec := int(time.Since(begin).Seconds())
fmt.Printf("%8d | %02d:%02d\r", current, sec/60, sec%60)
select {
default:
case <-ctx.Done():
return ctx.Err()
}
}
return nil
})
// Wait until all goroutines are finished
if err := g.Wait(); err != nil {
log.Fatal(err)
}
fmt.Printf("Scrolled through a total of %d documents in %v\n", total, time.Since(begin))
for i := 0; i < *numSlices; i++ {
fmt.Printf("Slice %2d received %d documents\n", i, totals[i])
}
}
================================================
FILE: recipes/suggesters/completion/.gitignore
================================================
/completion
================================================
FILE: recipes/suggesters/completion/main.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
// Connect creates an index with a mapping with different data types.
//
// Example
//
//
// ./completion -url=http://127.0.0.1:9200 -index=cities
//
// For more details and experimentation, take a look at the official
// documentation at https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-suggesters-completion.html.
package main
import (
"bufio"
"context"
"encoding/json"
"flag"
"fmt"
"log"
"os"
"strings"
"github.com/olivere/elastic/v7"
)
const (
mapping = `
{
"settings":{
"number_of_shards":1,
"number_of_replicas":0
},
"mappings":{
"properties":{
"name":{
"type":"keyword"
},
"name_suggest":{
"type":"completion"
}
}
}
}
`
)
// City is used in this example as an index document for suggestions.
type City struct {
Name string `json:"name,omitempty"`
NameSuggest *elastic.SuggestField `json:"name_suggest,omitempty"`
}
var (
cities = []string{
"Amsterdam",
"Athens",
"Berlin",
"Barcelona",
"Brussels",
"Dublin",
"Helsinki",
"Madrid",
"Lisbon",
"London",
"Oslo",
"Paris",
"Stockholm",
"Rome",
"Valletta",
"Vienna",
}
)
func main() {
var (
url = flag.String("url", "http://localhost:9200", "Elasticsearch URL")
sniff = flag.Bool("sniff", true, "Enable or disable sniffing")
index = flag.String("index", "", "Index name")
)
flag.Parse()
log.SetFlags(0)
if *url == "" {
*url = "http://127.0.0.1:9200"
}
if *index == "" {
log.Fatal("please specify an index name -index")
}
// Create an Elasticsearch client
client, err := elastic.NewClient(
elastic.SetURL(*url),
elastic.SetSniff(*sniff),
// elastic.SetTraceLog(log.New(os.Stdout, "", 0)), // uncomment to see the wire protocol
)
if err != nil {
log.Fatal(err)
}
_ = client
// Check if index already exists. We'll drop it then.
// Next, we create a fresh index/mapping.
ctx := context.Background()
exists, err := client.IndexExists(*index).Do(ctx)
if err != nil {
log.Fatal(err)
}
if exists {
_, err := client.DeleteIndex(*index).Do(ctx)
if err != nil {
log.Fatal(err)
}
}
_, err = client.CreateIndex(*index).Body(mapping).Do(ctx)
if err != nil {
log.Fatal(err)
}
// Add some cities
for _, name := range cities {
fmt.Printf("Add %s...\n", name)
_, err := client.Index().
Index(*index).
BodyJson(&City{
Name: name,
NameSuggest: elastic.NewSuggestField(name),
}).
Refresh("true").
Do(context.Background())
if err != nil {
log.Fatal(err)
}
}
// Allow suggestions
reader := bufio.NewReader(os.Stdin)
for {
fmt.Print("Enter a city name: ")
name, _ := reader.ReadString('\n')
name = strings.TrimRight(name, "\n")
if name == "" {
break
}
res, err := client.Search().
Index(*index).
Suggester(
elastic.NewCompletionSuggester("name_suggestion").
Field("name_suggest").
Text(name),
).
Size(0). // we dont' want the hits, just the suggestions
Pretty(true).
Do(context.Background())
if err != nil {
log.Fatal(err)
}
suggestions, found := res.Suggest["name_suggestion"]
if !found {
fmt.Printf("%d matches found:\n", 0)
continue
}
for _, suggestion := range suggestions {
fmt.Printf("%d suggestions found for text %q:\n", len(suggestion.Options), suggestion.Text)
for _, opt := range suggestion.Options {
fmt.Printf("* %s\n", opt.Text)
// The document's source is in opt.Source
var city City
if err = json.Unmarshal(opt.Source, &city); err != nil {
log.Fatal(err)
}
_ = city
}
}
}
}
================================================
FILE: recipes/text_vs_keyword/.gitignore
================================================
/text_vs_keyword
================================================
FILE: recipes/text_vs_keyword/main.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
// Connect illustrates exact matches vs fulltext queries with
// "text" and "keyword" types and Get as well as Term- and Match Query.
//
// Example
//
// go run main.go -url=http://127.0.0.1:9200 -index=testindex
// go run main.go -url=http://127.0.0.1:9200 -index=test -trace
//
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"log"
"os"
"github.com/olivere/elastic/v7"
)
const (
mapping = `
{
"settings":{
"number_of_shards":1,
"number_of_replicas":0
},
"mappings":{
"properties":{
"user":{
"type":"keyword"
},
"title":{
"type":"text",
"fields": {
"keyword": {
"type": "keyword"
}
}
}
}
}
}
`
)
// Doc is just an example document.
type Doc struct {
ID string `json:"-"` // we use it as an ID
User string `json:"user"`
Title string `json:"title"`
}
func main() {
var (
url = flag.String("url", "http://localhost:9200", "Elasticsearch URL")
sniff = flag.Bool("sniff", true, "Enable or disable sniffing")
index = flag.String("index", "", "Index name")
trace = flag.Bool("trace", false, "Enable or disable trace output")
)
flag.Parse()
log.SetFlags(0)
if *url == "" {
*url = "http://127.0.0.1:9200"
}
if *index == "" {
log.Fatal("please specify an index name -index")
}
// Create an Elasticsearch client
options := []elastic.ClientOptionFunc{
elastic.SetURL(*url),
elastic.SetSniff(*sniff),
}
if *trace {
options = append(options, elastic.SetTraceLog(
log.New(os.Stdout, "", 0),
))
}
client, err := elastic.NewClient(options...)
if err != nil {
log.Fatal(err)
}
// Check if index already exists. We'll drop it then.
// Next, we create a fresh index/mapping.
ctx := context.Background()
exists, err := client.IndexExists(*index).Do(ctx)
if err != nil {
log.Fatal(err)
}
if exists {
_, err := client.DeleteIndex(*index).Do(ctx)
if err != nil {
log.Fatal(err)
}
}
_, err = client.CreateIndex(*index).Body(mapping).Do(ctx)
if err != nil {
log.Fatal(err)
}
// Add some documents
{
docs := []Doc{
{
ID: "one",
User: "olivere",
Title: "Go and Elasticsearch.",
},
{
ID: "two",
User: "pepper",
Title: "Amsterdam is nice.",
},
{
ID: "three",
User: "salt",
Title: "So is Barcelona.",
},
}
for _, doc := range docs {
_, err := client.Index().
Index(*index).
Id(doc.ID).
BodyJson(doc).
Refresh("true").
Do(context.TODO())
if err != nil {
log.Fatal(err)
}
}
}
// Read a document by ID. This isn't searching, but looking
// up a document by its unique identifier. Same with MultiGet.
{
src, err := client.Get().
Index(*index).
Id("one").
Do(context.TODO())
if err != nil {
log.Fatal(err)
}
var doc Doc
if err = json.Unmarshal(src.Source, &doc); err != nil {
log.Fatal(err)
}
fmt.Printf("%s: %s\n",
doc.User,
doc.Title,
)
}
// Read a document by exact query on keyword field
// It will find the document because "user" is of type "keyword"
// and there will be an exact match on the value of "salt".
{
resp, err := client.Search().
Index(*index).
Query(elastic.NewTermQuery("user", "salt")).
Do(context.TODO())
if err != nil {
log.Fatal(err)
}
if want, have := int64(1), resp.TotalHits(); want != have {
log.Fatalf("expected %d hits, got %d", want, have)
}
var doc Doc
if err = json.Unmarshal(resp.Hits.Hits[0].Source, &doc); err != nil {
log.Fatal(err)
}
fmt.Printf("%s: %s\n",
doc.User,
doc.Title,
)
}
// Search for documents by "fulltext" query on text field.
// This works fine because the MatchQuery will match on "Amsterdam".
// Notice that the full title of the found document is "Amsterdam is nice.",
// so an exact query on the "title" field for "Amsterdam" wouldn't find anything.
{
resp, err := client.Search().
Index(*index).
Query(elastic.NewMatchQuery("title", "Amsterdam")).
Do(context.TODO())
if err != nil {
log.Fatal(err)
}
if want, have := int64(1), resp.TotalHits(); want != have {
log.Fatalf("expected %d hits, got %d", want, have)
}
var doc Doc
if err = json.Unmarshal(resp.Hits.Hits[0].Source, &doc); err != nil {
log.Fatal(err)
}
fmt.Printf("%s: %s\n",
doc.User,
doc.Title,
)
}
// Search for documents by exact query on text field. This case will fail,
// because "title" is a field of type "text", and there is no exact match
// for the term "Amsterdam" on any "title" field.
{
resp, err := client.Search().
Index(*index).
Query(elastic.NewTermQuery("title", "Amsterdam")).
Do(context.TODO())
if err != nil {
log.Fatal(err)
}
if want, have := int64(0), resp.TotalHits(); want != have {
log.Fatalf("expected %d hits, got %d", want, have)
}
}
// Notice how this works again. We now search for an exact match by
// using "title.keyword", a multi-field. Notice that we can only find
// the exact match if we use "Amsterdam is nice.". Using the term
// "Amsterdam" would yield no result, again. Doing a match query for
// "Amsterdam is nice." will yield two results BTW, because there is
// a match for the word "is" in "So is Barcelona." as well (albeit with
// a significantly lower score).
{
resp, err := client.Search().
Index(*index).
Query(elastic.NewMatchQuery("title.keyword", "Amsterdam is nice.")).
Do(context.TODO())
if err != nil {
log.Fatal(err)
}
if want, have := int64(1), resp.TotalHits(); want != have {
log.Fatalf("expected %d hits, got %d", want, have)
}
var doc Doc
if err = json.Unmarshal(resp.Hits.Hits[0].Source, &doc); err != nil {
log.Fatal(err)
}
fmt.Printf("%s: %s\n",
doc.User,
doc.Title,
)
}
}
================================================
FILE: recipes/tracing/.gitignore
================================================
/tracing
================================================
FILE: recipes/tracing/README.md
================================================
# Illustrate Elastic OpenTracing
Run Jaeger (any other supported tracers work of course):
```sh
./run-tracer.sh
```
Then open the web UI:
```sh
open http://localhost:16686
```
Then run the example, e.g.:
```sh
go build
./tracing -index=test -type=doc -n=100000 -bulk-size=100
```
================================================
FILE: recipes/tracing/otel/.gitignore
================================================
/tracing
================================================
FILE: recipes/tracing/otel/README.md
================================================
# Tracing with OpenTelemetry
Run Jaeger (any other supported tracers work of course):
```sh
./run-tracer.sh
```
Then open the web UI:
```sh
open http://localhost:16686
```
Then run the example, e.g.:
```sh
go build
./tracing -index=test -type=doc -n=100000 -bulk-size=100
```
================================================
FILE: recipes/tracing/otel/run-tracer.sh
================================================
#!/bin/sh
docker run -d --name jaeger \
-e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
-p 5775:5775/udp \
-p 6831:6831/udp \
-p 6832:6832/udp \
-p 5778:5778 \
-p 16686:16686 \
-p 14268:14268 \
-p 9411:9411 \
jaegertracing/all-in-one:latest
================================================
FILE: recipes/tracing/otel/tracing.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
// Tracing is the same as the bulk_insert recipe, but adds
// OpenTracing support.
//
// Example
//
// Bulk index 100.000 documents into the index "warehouse", type "product",
// committing every set of 1.000 documents.
//
// ./run-tracer.sh
// ./tracing -index=warehouse -type=product -n=100000 -bulk-size=1000
//
package main
import (
"context"
"encoding/base64"
"errors"
"flag"
"fmt"
"log"
"math/rand"
"net/http"
"sync/atomic"
"time"
"golang.org/x/sync/errgroup"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/jaeger"
"go.opentelemetry.io/otel/sdk/resource"
tracesdk "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.7.0"
"github.com/olivere/elastic/v7"
"github.com/olivere/elastic/v7/trace/opentelemetry"
)
func main() {
var (
url = flag.String("url", "http://localhost:9200", "Elasticsearch URL")
index = flag.String("index", "", "Elasticsearch index name")
typ = flag.String("type", "", "Elasticsearch type name")
sniff = flag.Bool("sniff", true, "Enable or disable sniffing")
n = flag.Int("n", 0, "Number of documents to bulk insert")
bulkSize = flag.Int("bulk-size", 0, "Number of documents to collect before committing")
)
flag.Parse()
log.SetFlags(0)
rand.Seed(time.Now().UnixNano())
if *url == "" {
log.Fatal("missing url parameter")
}
if *index == "" {
log.Fatal("missing index parameter")
}
if *typ == "" {
log.Fatal("missing type parameter")
}
if *n <= 0 {
log.Fatal("n must be a positive number")
}
if *bulkSize <= 0 {
log.Fatal("bulk-size must be a positive number")
}
opts := []elastic.ClientOptionFunc{
elastic.SetURL(*url),
elastic.SetSniff(*sniff),
}
// Initialize Jaeger tracing
exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint("http://localhost:14268/api/traces")))
if err != nil {
log.Fatal(err)
}
tp := tracesdk.NewTraceProvider(
tracesdk.WithBatcher(exp),
tracesdk.WithResource(resource.NewWithAttributes(
semconv.ServiceNameKey.String("elastic-tracing"),
)),
)
defer func() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := tp.Shutdown(ctx); err != nil {
log.Fatal(err)
}
}()
otel.SetTracerProvider(tp)
httpClient := &http.Client{
Transport: opentelemetry.NewTransport(),
}
opts = append(opts, elastic.SetHttpClient(httpClient))
// Create an Elasticsearch client
client, err := elastic.NewClient(opts...)
if err != nil {
log.Fatal(err)
}
// Setup a group of goroutines from the excellent errgroup package
g, ctx := errgroup.WithContext(context.TODO())
// The first goroutine will emit documents and send it to the second goroutine
// via the docsc channel.
// The second Goroutine will simply bulk insert the documents.
type doc struct {
ID string `json:"id"`
Timestamp time.Time `json:"@timestamp"`
}
docsc := make(chan doc)
begin := time.Now()
// Goroutine to create documents
g.Go(func() error {
defer close(docsc)
buf := make([]byte, 32)
for i := 0; i < *n; i++ {
// Generate a random ID
_, err := rand.Read(buf)
if err != nil {
return err
}
id := base64.URLEncoding.EncodeToString(buf)
// Construct the document
d := doc{
ID: id,
Timestamp: time.Now(),
}
// Send over to 2nd goroutine, or cancel
select {
case docsc <- d:
case <-ctx.Done():
return ctx.Err()
}
}
return nil
})
// Second goroutine will consume the documents sent from the first and bulk insert into ES
var total uint64
g.Go(func() error {
bulk := client.Bulk().Index(*index).Type(*typ)
for d := range docsc {
// Simple progress
current := atomic.AddUint64(&total, 1)
dur := time.Since(begin).Seconds()
sec := int(dur)
pps := int64(float64(current) / dur)
fmt.Printf("%10d | %6d req/s | %02d:%02d\r", current, pps, sec/60, sec%60)
// Enqueue the document
bulk.Add(elastic.NewBulkIndexRequest().Id(d.ID).Doc(d))
if bulk.NumberOfActions() >= *bulkSize {
// Commit
res, err := bulk.Do(ctx)
if err != nil {
return err
}
if res.Errors {
// Look up the failed documents with res.Failed(), and e.g. recommit
return errors.New("bulk commit failed")
}
// "bulk" is reset after Do, so you can reuse it
}
select {
default:
case <-ctx.Done():
return ctx.Err()
}
}
// Commit the final batch before exiting
if bulk.NumberOfActions() > 0 {
_, err = bulk.Do(ctx)
if err != nil {
return err
}
}
return nil
})
// Wait until all goroutines are finished
if err := g.Wait(); err != nil {
log.Fatal(err)
}
// Final results
dur := time.Since(begin).Seconds()
sec := int(dur)
pps := int64(float64(total) / dur)
fmt.Printf("%10d | %6d req/s | %02d:%02d\n", total, pps, sec/60, sec%60)
}
================================================
FILE: recipes/tracing/run-tracer.sh
================================================
#!/bin/sh
docker run -d --name jaeger \
-e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
-p 5775:5775/udp \
-p 6831:6831/udp \
-p 6832:6832/udp \
-p 5778:5778 \
-p 16686:16686 \
-p 14268:14268 \
-p 9411:9411 \
jaegertracing/all-in-one:latest
================================================
FILE: recipes/tracing/tracing.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
// Tracing is the same as the bulk_insert recipe, but adds
// OpenTracing support.
//
// Example
//
// Bulk index 100.000 documents into the index "warehouse", type "product",
// committing every set of 1.000 documents.
//
// ./run-tracer.sh
// ./tracing -index=warehouse -type=product -n=100000 -bulk-size=1000
//
package main
import (
"context"
"encoding/base64"
"errors"
"flag"
"fmt"
"log"
"math/rand"
"net/http"
"sync/atomic"
"time"
jaegerconfig "github.com/uber/jaeger-client-go/config"
jaegerlog "github.com/uber/jaeger-client-go/log"
jaegerexpvar "github.com/uber/jaeger-lib/metrics/expvar"
"golang.org/x/sync/errgroup"
"github.com/olivere/elastic/v7"
"github.com/olivere/elastic/v7/trace/opentracing"
)
func main() {
var (
url = flag.String("url", "http://localhost:9200", "Elasticsearch URL")
index = flag.String("index", "", "Elasticsearch index name")
typ = flag.String("type", "", "Elasticsearch type name")
sniff = flag.Bool("sniff", true, "Enable or disable sniffing")
n = flag.Int("n", 0, "Number of documents to bulk insert")
bulkSize = flag.Int("bulk-size", 0, "Number of documents to collect before committing")
)
flag.Parse()
log.SetFlags(0)
rand.Seed(time.Now().UnixNano())
if *url == "" {
log.Fatal("missing url parameter")
}
if *index == "" {
log.Fatal("missing index parameter")
}
if *typ == "" {
log.Fatal("missing type parameter")
}
if *n <= 0 {
log.Fatal("n must be a positive number")
}
if *bulkSize <= 0 {
log.Fatal("bulk-size must be a positive number")
}
opts := []elastic.ClientOptionFunc{
elastic.SetURL(*url),
elastic.SetSniff(*sniff),
}
// Initialize Jaeger tracing
cfg := jaegerconfig.Configuration{}
closer, err := cfg.InitGlobalTracer(
"elastic-tracing",
jaegerconfig.Logger(jaegerlog.StdLogger),
jaegerconfig.Metrics(jaegerexpvar.NewFactory(10)),
)
if err != nil {
log.Fatalf("unable to initialize jaeger tracer: %v", err)
}
defer closer.Close()
httpClient := &http.Client{
Transport: opentracing.NewTransport(),
}
opts = append(opts, elastic.SetHttpClient(httpClient))
// Create an Elasticsearch client
client, err := elastic.NewClient(opts...)
if err != nil {
log.Fatal(err)
}
// Setup a group of goroutines from the excellent errgroup package
g, ctx := errgroup.WithContext(context.TODO())
// The first goroutine will emit documents and send it to the second goroutine
// via the docsc channel.
// The second Goroutine will simply bulk insert the documents.
type doc struct {
ID string `json:"id"`
Timestamp time.Time `json:"@timestamp"`
}
docsc := make(chan doc)
begin := time.Now()
// Goroutine to create documents
g.Go(func() error {
defer close(docsc)
buf := make([]byte, 32)
for i := 0; i < *n; i++ {
// Generate a random ID
_, err := rand.Read(buf)
if err != nil {
return err
}
id := base64.URLEncoding.EncodeToString(buf)
// Construct the document
d := doc{
ID: id,
Timestamp: time.Now(),
}
// Send over to 2nd goroutine, or cancel
select {
case docsc <- d:
case <-ctx.Done():
return ctx.Err()
}
}
return nil
})
// Second goroutine will consume the documents sent from the first and bulk insert into ES
var total uint64
g.Go(func() error {
bulk := client.Bulk().Index(*index).Type(*typ)
for d := range docsc {
// Simple progress
current := atomic.AddUint64(&total, 1)
dur := time.Since(begin).Seconds()
sec := int(dur)
pps := int64(float64(current) / dur)
fmt.Printf("%10d | %6d req/s | %02d:%02d\r", current, pps, sec/60, sec%60)
// Enqueue the document
bulk.Add(elastic.NewBulkIndexRequest().Id(d.ID).Doc(d))
if bulk.NumberOfActions() >= *bulkSize {
// Commit
res, err := bulk.Do(ctx)
if err != nil {
return err
}
if res.Errors {
// Look up the failed documents with res.Failed(), and e.g. recommit
return errors.New("bulk commit failed")
}
// "bulk" is reset after Do, so you can reuse it
}
select {
default:
case <-ctx.Done():
return ctx.Err()
}
}
// Commit the final batch before exiting
if bulk.NumberOfActions() > 0 {
_, err = bulk.Do(ctx)
if err != nil {
return err
}
}
return nil
})
// Wait until all goroutines are finished
if err := g.Wait(); err != nil {
log.Fatal(err)
}
// Final results
dur := time.Since(begin).Seconds()
sec := int(dur)
pps := int64(float64(total) / dur)
fmt.Printf("%10d | %6d req/s | %02d:%02d\n", total, pps, sec/60, sec%60)
}
================================================
FILE: reindex.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"errors"
"fmt"
"net/http"
"net/url"
"strings"
)
// ReindexService is a method to copy documents from one index to another.
// It is documented at https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-reindex.html.
type ReindexService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
refresh string
timeout string
waitForActiveShards string
waitForCompletion *bool
requestsPerSecond *int
slices interface{}
body interface{}
source *ReindexSource
destination *ReindexDestination
conflicts string
size *int
script *Script
}
// NewReindexService creates a new ReindexService.
func NewReindexService(client *Client) *ReindexService {
return &ReindexService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *ReindexService) Pretty(pretty bool) *ReindexService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *ReindexService) Human(human bool) *ReindexService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *ReindexService) ErrorTrace(errorTrace bool) *ReindexService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *ReindexService) FilterPath(filterPath ...string) *ReindexService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *ReindexService) Header(name string, value string) *ReindexService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *ReindexService) Headers(headers http.Header) *ReindexService {
s.headers = headers
return s
}
// WaitForActiveShards sets the number of shard copies that must be active before
// proceeding with the reindex operation. Defaults to 1, meaning the primary shard only.
// Set to `all` for all shard copies, otherwise set to any non-negative value less than or
// equal to the total number of copies for the shard (number of replicas + 1).
func (s *ReindexService) WaitForActiveShards(waitForActiveShards string) *ReindexService {
s.waitForActiveShards = waitForActiveShards
return s
}
// RequestsPerSecond specifies the throttle to set on this request in sub-requests per second.
// -1 means set no throttle as does "unlimited" which is the only non-float this accepts.
func (s *ReindexService) RequestsPerSecond(requestsPerSecond int) *ReindexService {
s.requestsPerSecond = &requestsPerSecond
return s
}
// Slices specifies the number of slices this task should be divided into. Defaults to 1.
// It used to be a number, but can be set to "auto" as of 6.7.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-reindex.html#docs-reindex-slice
// for details.
func (s *ReindexService) Slices(slices interface{}) *ReindexService {
s.slices = slices
return s
}
// Refresh indicates whether Elasticsearch should refresh the effected indexes
// immediately.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-refresh.html
// for details.
func (s *ReindexService) Refresh(refresh string) *ReindexService {
s.refresh = refresh
return s
}
// Timeout is the time each individual bulk request should wait for shards
// that are unavailable.
func (s *ReindexService) Timeout(timeout string) *ReindexService {
s.timeout = timeout
return s
}
// WaitForCompletion indicates whether Elasticsearch should block until the
// reindex is complete.
func (s *ReindexService) WaitForCompletion(waitForCompletion bool) *ReindexService {
s.waitForCompletion = &waitForCompletion
return s
}
// Source specifies the source of the reindexing process.
func (s *ReindexService) Source(source *ReindexSource) *ReindexService {
s.source = source
return s
}
// SourceIndex specifies the source index of the reindexing process.
func (s *ReindexService) SourceIndex(index string) *ReindexService {
if s.source == nil {
s.source = NewReindexSource()
}
s.source = s.source.Index(index)
return s
}
// Destination specifies the destination of the reindexing process.
func (s *ReindexService) Destination(destination *ReindexDestination) *ReindexService {
s.destination = destination
return s
}
// DestinationIndex specifies the destination index of the reindexing process.
func (s *ReindexService) DestinationIndex(index string) *ReindexService {
if s.destination == nil {
s.destination = NewReindexDestination()
}
s.destination = s.destination.Index(index)
return s
}
// DestinationIndexAndType specifies both the destination index and type
// of the reindexing process.
func (s *ReindexService) DestinationIndexAndType(index, typ string) *ReindexService {
if s.destination == nil {
s.destination = NewReindexDestination()
}
s.destination = s.destination.Index(index)
s.destination = s.destination.Type(typ)
return s
}
// Conflicts indicates what to do when the process detects version conflicts.
// Possible values are "proceed" and "abort".
func (s *ReindexService) Conflicts(conflicts string) *ReindexService {
s.conflicts = conflicts
return s
}
// AbortOnVersionConflict aborts the request on version conflicts.
// It is an alias to setting Conflicts("abort").
func (s *ReindexService) AbortOnVersionConflict() *ReindexService {
s.conflicts = "abort"
return s
}
// ProceedOnVersionConflict aborts the request on version conflicts.
// It is an alias to setting Conflicts("proceed").
func (s *ReindexService) ProceedOnVersionConflict() *ReindexService {
s.conflicts = "proceed"
return s
}
// Size sets an upper limit for the number of processed documents.
func (s *ReindexService) Size(size int) *ReindexService {
s.size = &size
return s
}
// Script allows for modification of the documents as they are reindexed
// from source to destination.
func (s *ReindexService) Script(script *Script) *ReindexService {
s.script = script
return s
}
// Body specifies the body of the request to send to Elasticsearch.
// It overrides settings specified with other setters, e.g. Query.
func (s *ReindexService) Body(body interface{}) *ReindexService {
s.body = body
return s
}
// buildURL builds the URL for the operation.
func (s *ReindexService) buildURL() (string, url.Values, error) {
// Build URL path
path := "/_reindex"
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.refresh != "" {
params.Set("refresh", s.refresh)
}
if s.timeout != "" {
params.Set("timeout", s.timeout)
}
if s.requestsPerSecond != nil {
params.Set("requests_per_second", fmt.Sprintf("%v", *s.requestsPerSecond))
}
if s.slices != nil {
params.Set("slices", fmt.Sprintf("%v", s.slices))
}
if s.waitForActiveShards != "" {
params.Set("wait_for_active_shards", s.waitForActiveShards)
}
if s.waitForCompletion != nil {
params.Set("wait_for_completion", fmt.Sprintf("%v", *s.waitForCompletion))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *ReindexService) Validate() error {
var invalid []string
if s.body != nil {
return nil
}
if s.source == nil {
invalid = append(invalid, "Source")
} else {
if len(s.source.request.indices) == 0 {
invalid = append(invalid, "Source.Index")
}
}
if s.destination == nil {
invalid = append(invalid, "Destination")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// getBody returns the body part of the document request.
func (s *ReindexService) getBody() (interface{}, error) {
if s.body != nil {
return s.body, nil
}
body := make(map[string]interface{})
if s.conflicts != "" {
body["conflicts"] = s.conflicts
}
if s.size != nil {
body["size"] = *s.size
}
if s.script != nil {
out, err := s.script.Source()
if err != nil {
return nil, err
}
body["script"] = out
}
src, err := s.source.Source()
if err != nil {
return nil, err
}
body["source"] = src
dst, err := s.destination.Source()
if err != nil {
return nil, err
}
body["dest"] = dst
return body, nil
}
// Do executes the operation.
func (s *ReindexService) Do(ctx context.Context) (*BulkIndexByScrollResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Setup HTTP request body
body, err := s.getBody()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "POST",
Path: path,
Params: params,
Body: body,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(BulkIndexByScrollResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
ret.Header = res.Header
return ret, nil
}
// DoAsync executes the reindexing operation asynchronously by starting a new task.
// Callers need to use the Task Management API to watch the outcome of the reindexing
// operation.
func (s *ReindexService) DoAsync(ctx context.Context) (*StartTaskResult, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// DoAsync only makes sense with WaitForCompletion set to false
if s.waitForCompletion != nil && *s.waitForCompletion {
return nil, fmt.Errorf("cannot start a task with WaitForCompletion set to true")
}
f := false
s.waitForCompletion = &f
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Setup HTTP request body
body, err := s.getBody()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "POST",
Path: path,
Params: params,
Body: body,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(StartTaskResult)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
ret.Header = res.Header
return ret, nil
}
// -- Source of Reindex --
// ReindexSource specifies the source of a Reindex process.
type ReindexSource struct {
request *SearchRequest
remoteInfo *ReindexRemoteInfo
}
// NewReindexSource creates a new ReindexSource.
func NewReindexSource() *ReindexSource {
return &ReindexSource{
request: NewSearchRequest(),
}
}
// Request specifies the search request used for source.
func (r *ReindexSource) Request(request *SearchRequest) *ReindexSource {
if request == nil {
r.request = NewSearchRequest()
} else {
r.request = request
}
return r
}
// SearchType is the search operation type. Possible values are
// "query_then_fetch" and "dfs_query_then_fetch".
func (r *ReindexSource) SearchType(searchType string) *ReindexSource {
r.request = r.request.SearchType(searchType)
return r
}
func (r *ReindexSource) SearchTypeDfsQueryThenFetch() *ReindexSource {
r.request = r.request.SearchType("dfs_query_then_fetch")
return r
}
func (r *ReindexSource) SearchTypeQueryThenFetch() *ReindexSource {
r.request = r.request.SearchType("query_then_fetch")
return r
}
func (r *ReindexSource) Index(indices ...string) *ReindexSource {
r.request = r.request.Index(indices...)
return r
}
func (r *ReindexSource) Type(types ...string) *ReindexSource {
r.request = r.request.Type(types...)
return r
}
func (r *ReindexSource) Preference(preference string) *ReindexSource {
r.request = r.request.Preference(preference)
return r
}
func (r *ReindexSource) RequestCache(requestCache bool) *ReindexSource {
r.request = r.request.RequestCache(requestCache)
return r
}
func (r *ReindexSource) Scroll(scroll string) *ReindexSource {
r.request = r.request.Scroll(scroll)
return r
}
func (r *ReindexSource) Query(query Query) *ReindexSource {
r.request = r.request.Query(query)
return r
}
// Sort adds a sort order.
func (r *ReindexSource) Sort(field string, ascending bool) *ReindexSource {
r.request = r.request.Sort(field, ascending)
return r
}
// SortWithInfo adds a sort order.
func (r *ReindexSource) SortWithInfo(info SortInfo) *ReindexSource {
r.request = r.request.SortWithInfo(info)
return r
}
// SortBy adds a sort order.
func (r *ReindexSource) SortBy(sorter ...Sorter) *ReindexSource {
r.request = r.request.SortBy(sorter...)
return r
}
// FetchSource indicates whether the response should contain the stored
// _source for every hit.
func (r *ReindexSource) FetchSource(fetchSource bool) *ReindexSource {
r.request = r.request.FetchSource(fetchSource)
return r
}
// FetchSourceIncludeExclude specifies that _source should be returned
// with each hit, where "include" and "exclude" serve as a simple wildcard
// matcher that gets applied to its fields
// (e.g. include := []string{"obj1.*","obj2.*"}, exclude := []string{"description.*"}).
func (r *ReindexSource) FetchSourceIncludeExclude(include, exclude []string) *ReindexSource {
r.request = r.request.FetchSourceIncludeExclude(include, exclude)
return r
}
// FetchSourceContext indicates how the _source should be fetched.
func (r *ReindexSource) FetchSourceContext(fsc *FetchSourceContext) *ReindexSource {
r.request = r.request.FetchSourceContext(fsc)
return r
}
// RemoteInfo sets up reindexing from a remote cluster.
func (r *ReindexSource) RemoteInfo(ri *ReindexRemoteInfo) *ReindexSource {
r.remoteInfo = ri
return r
}
// Source returns a serializable JSON request for the request.
func (r *ReindexSource) Source() (interface{}, error) {
src, err := r.request.sourceAsMap()
if err != nil {
return nil, err
}
source, ok := src.(map[string]interface{})
if !ok {
return nil, errors.New("unable to use SearchRequest as map[string]interface{}")
}
switch len(r.request.indices) {
case 1:
source["index"] = r.request.indices[0]
default:
source["index"] = r.request.indices
}
switch len(r.request.types) {
case 0:
case 1:
source["type"] = r.request.types[0]
default:
source["type"] = r.request.types
}
if r.remoteInfo != nil {
src, err := r.remoteInfo.Source()
if err != nil {
return nil, err
}
source["remote"] = src
}
return source, nil
}
// ReindexRemoteInfo contains information for reindexing from a remote cluster.
type ReindexRemoteInfo struct {
host string
username string
password string
socketTimeout string // e.g. "1m" or "30s"
connectTimeout string // e.g. "1m" or "30s"
}
// NewReindexRemoteInfo creates a new ReindexRemoteInfo.
func NewReindexRemoteInfo() *ReindexRemoteInfo {
return &ReindexRemoteInfo{}
}
// Host sets the host information of the remote cluster.
// It must be of the form "http(s)://:"
func (ri *ReindexRemoteInfo) Host(host string) *ReindexRemoteInfo {
ri.host = host
return ri
}
// Username sets the username to authenticate with the remote cluster.
func (ri *ReindexRemoteInfo) Username(username string) *ReindexRemoteInfo {
ri.username = username
return ri
}
// Password sets the password to authenticate with the remote cluster.
func (ri *ReindexRemoteInfo) Password(password string) *ReindexRemoteInfo {
ri.password = password
return ri
}
// SocketTimeout sets the socket timeout to connect with the remote cluster.
// Use ES compatible values like e.g. "30s" or "1m".
func (ri *ReindexRemoteInfo) SocketTimeout(timeout string) *ReindexRemoteInfo {
ri.socketTimeout = timeout
return ri
}
// ConnectTimeout sets the connection timeout to connect with the remote cluster.
// Use ES compatible values like e.g. "30s" or "1m".
func (ri *ReindexRemoteInfo) ConnectTimeout(timeout string) *ReindexRemoteInfo {
ri.connectTimeout = timeout
return ri
}
// Source returns the serializable JSON data for the request.
func (ri *ReindexRemoteInfo) Source() (interface{}, error) {
res := make(map[string]interface{})
res["host"] = ri.host
if len(ri.username) > 0 {
res["username"] = ri.username
}
if len(ri.password) > 0 {
res["password"] = ri.password
}
if len(ri.socketTimeout) > 0 {
res["socket_timeout"] = ri.socketTimeout
}
if len(ri.connectTimeout) > 0 {
res["connect_timeout"] = ri.connectTimeout
}
return res, nil
}
// -- Destination of Reindex --
// ReindexDestination is the destination of a Reindex API call.
// It is basically the meta data of a BulkIndexRequest.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-reindex.html
// for details.
type ReindexDestination struct {
index string
typ string
routing string
parent string
opType string
version int64 // default is MATCH_ANY
versionType string // default is "internal"
pipeline string
}
// NewReindexDestination returns a new ReindexDestination.
func NewReindexDestination() *ReindexDestination {
return &ReindexDestination{}
}
// Index specifies name of the Elasticsearch index to use as the destination
// of a reindexing process.
func (r *ReindexDestination) Index(index string) *ReindexDestination {
r.index = index
return r
}
// Type specifies the Elasticsearch type to use for reindexing.
func (r *ReindexDestination) Type(typ string) *ReindexDestination {
r.typ = typ
return r
}
// Routing specifies a routing value for the reindexing request.
// It can be "keep", "discard", or start with "=". The latter specifies
// the routing on the bulk request.
func (r *ReindexDestination) Routing(routing string) *ReindexDestination {
r.routing = routing
return r
}
// Keep sets the routing on the bulk request sent for each match to the routing
// of the match (the default).
func (r *ReindexDestination) Keep() *ReindexDestination {
r.routing = "keep"
return r
}
// Discard sets the routing on the bulk request sent for each match to null.
func (r *ReindexDestination) Discard() *ReindexDestination {
r.routing = "discard"
return r
}
// Parent specifies the identifier of the parent document (if available).
func (r *ReindexDestination) Parent(parent string) *ReindexDestination {
r.parent = parent
return r
}
// OpType specifies if this request should follow create-only or upsert
// behavior. This follows the OpType of the standard document index API.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-index_.html#operation-type
// for details.
func (r *ReindexDestination) OpType(opType string) *ReindexDestination {
r.opType = opType
return r
}
// Version indicates the version of the document as part of an optimistic
// concurrency model.
func (r *ReindexDestination) Version(version int64) *ReindexDestination {
r.version = version
return r
}
// VersionType specifies how versions are created.
func (r *ReindexDestination) VersionType(versionType string) *ReindexDestination {
r.versionType = versionType
return r
}
// Pipeline specifies the pipeline to use for reindexing.
func (r *ReindexDestination) Pipeline(pipeline string) *ReindexDestination {
r.pipeline = pipeline
return r
}
// Source returns a serializable JSON request for the request.
func (r *ReindexDestination) Source() (interface{}, error) {
source := make(map[string]interface{})
if r.index != "" {
source["index"] = r.index
}
if r.typ != "" {
source["type"] = r.typ
}
if r.routing != "" {
source["routing"] = r.routing
}
if r.opType != "" {
source["op_type"] = r.opType
}
if r.parent != "" {
source["parent"] = r.parent
}
if r.version > 0 {
source["version"] = r.version
}
if r.versionType != "" {
source["version_type"] = r.versionType
}
if r.pipeline != "" {
source["pipeline"] = r.pipeline
}
return source, nil
}
================================================
FILE: reindex_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"testing"
)
func TestReindexSourceWithBodyMap(t *testing.T) {
client := setupTestClient(t)
out, err := client.Reindex().Body(map[string]interface{}{
"source": map[string]interface{}{
"index": "twitter",
},
"dest": map[string]interface{}{
"index": "new_twitter",
},
}).getBody()
if err != nil {
t.Fatal(err)
}
b, err := json.Marshal(out)
if err != nil {
t.Fatal(err)
}
got := string(b)
want := `{"dest":{"index":"new_twitter"},"source":{"index":"twitter"}}`
if got != want {
t.Fatalf("\ngot %s\nwant %s", got, want)
}
}
func TestReindexSourceWithBodyString(t *testing.T) {
client := setupTestClient(t)
got, err := client.Reindex().Body(`{"source":{"index":"twitter"},"dest":{"index":"new_twitter"}}`).getBody()
if err != nil {
t.Fatal(err)
}
want := `{"source":{"index":"twitter"},"dest":{"index":"new_twitter"}}`
if got != want {
t.Fatalf("\ngot %s\nwant %s", got, want)
}
}
func TestReindexSourceWithSourceIndexAndDestinationIndex(t *testing.T) {
client := setupTestClient(t)
out, err := client.Reindex().SourceIndex("twitter").DestinationIndex("new_twitter").getBody()
if err != nil {
t.Fatal(err)
}
b, err := json.Marshal(out)
if err != nil {
t.Fatal(err)
}
got := string(b)
want := `{"dest":{"index":"new_twitter"},"source":{"index":"twitter"}}`
if got != want {
t.Fatalf("\ngot %s\nwant %s", got, want)
}
}
func TestReindexSourceWithSourceAndDestinationAndVersionType(t *testing.T) {
client := setupTestClient(t)
src := NewReindexSource().Index("twitter")
dst := NewReindexDestination().Index("new_twitter").VersionType("external")
out, err := client.Reindex().Source(src).Destination(dst).getBody()
if err != nil {
t.Fatal(err)
}
b, err := json.Marshal(out)
if err != nil {
t.Fatal(err)
}
got := string(b)
want := `{"dest":{"index":"new_twitter","version_type":"external"},"source":{"index":"twitter"}}`
if got != want {
t.Fatalf("\ngot %s\nwant %s", got, want)
}
}
func TestReindexSourceWithSourceAndRemoteAndDestination(t *testing.T) {
client := setupTestClient(t)
src := NewReindexSource().Index("twitter").RemoteInfo(
NewReindexRemoteInfo().Host("http://otherhost:9200").
Username("alice").
Password("secret").
ConnectTimeout("10s").
SocketTimeout("1m"),
)
dst := NewReindexDestination().Index("new_twitter")
out, err := client.Reindex().Source(src).Destination(dst).getBody()
if err != nil {
t.Fatal(err)
}
b, err := json.Marshal(out)
if err != nil {
t.Fatal(err)
}
got := string(b)
want := `{"dest":{"index":"new_twitter"},"source":{"index":"twitter","remote":{"connect_timeout":"10s","host":"http://otherhost:9200","password":"secret","socket_timeout":"1m","username":"alice"}}}`
if got != want {
t.Fatalf("\ngot %s\nwant %s", got, want)
}
}
func TestReindexSourceWithSourceAndDestinationAndOpTypeAndPipeline(t *testing.T) {
client := setupTestClient(t)
src := NewReindexSource().Index("twitter")
dst := NewReindexDestination().Index("new_twitter").OpType("create").Pipeline("some_ingest_pipeline")
out, err := client.Reindex().Source(src).Destination(dst).getBody()
if err != nil {
t.Fatal(err)
}
b, err := json.Marshal(out)
if err != nil {
t.Fatal(err)
}
got := string(b)
want := `{"dest":{"index":"new_twitter","op_type":"create","pipeline":"some_ingest_pipeline"},"source":{"index":"twitter"}}`
if got != want {
t.Fatalf("\ngot %s\nwant %s", got, want)
}
}
func TestReindexSourceWithConflictsProceed(t *testing.T) {
client := setupTestClient(t)
src := NewReindexSource().Index("twitter")
dst := NewReindexDestination().Index("new_twitter").OpType("create")
out, err := client.Reindex().Conflicts("proceed").Source(src).Destination(dst).getBody()
if err != nil {
t.Fatal(err)
}
b, err := json.Marshal(out)
if err != nil {
t.Fatal(err)
}
got := string(b)
want := `{"conflicts":"proceed","dest":{"index":"new_twitter","op_type":"create"},"source":{"index":"twitter"}}`
if got != want {
t.Fatalf("\ngot %s\nwant %s", got, want)
}
}
func TestReindexSourceWithProceedOnVersionConflict(t *testing.T) {
client := setupTestClient(t)
src := NewReindexSource().Index("twitter")
dst := NewReindexDestination().Index("new_twitter").OpType("create")
out, err := client.Reindex().ProceedOnVersionConflict().Source(src).Destination(dst).getBody()
if err != nil {
t.Fatal(err)
}
b, err := json.Marshal(out)
if err != nil {
t.Fatal(err)
}
got := string(b)
want := `{"conflicts":"proceed","dest":{"index":"new_twitter","op_type":"create"},"source":{"index":"twitter"}}`
if got != want {
t.Fatalf("\ngot %s\nwant %s", got, want)
}
}
func TestReindexSourceWithQuery(t *testing.T) {
client := setupTestClient(t)
src := NewReindexSource().Index("twitter").Query(NewTermQuery("user", "olivere"))
dst := NewReindexDestination().Index("new_twitter")
out, err := client.Reindex().Source(src).Destination(dst).getBody()
if err != nil {
t.Fatal(err)
}
b, err := json.Marshal(out)
if err != nil {
t.Fatal(err)
}
got := string(b)
want := `{"dest":{"index":"new_twitter"},"source":{"index":"twitter","query":{"term":{"user":"olivere"}}}}`
if got != want {
t.Fatalf("\ngot %s\nwant %s", got, want)
}
}
func TestReindexSourceWithMultipleSourceIndicesAndTypes(t *testing.T) {
client := setupTestClient(t)
src := NewReindexSource().Index("twitter", "blog").Type("doc", "post")
dst := NewReindexDestination().Index("all_together")
out, err := client.Reindex().Source(src).Destination(dst).getBody()
if err != nil {
t.Fatal(err)
}
b, err := json.Marshal(out)
if err != nil {
t.Fatal(err)
}
got := string(b)
want := `{"dest":{"index":"all_together"},"source":{"index":["twitter","blog"],"type":["doc","post"]}}`
if got != want {
t.Fatalf("\ngot %s\nwant %s", got, want)
}
}
func TestReindexSourceWithSourceAndSize(t *testing.T) {
client := setupTestClient(t)
src := NewReindexSource().Index("twitter").Sort("date", false)
dst := NewReindexDestination().Index("new_twitter")
out, err := client.Reindex().Size(10000).Source(src).Destination(dst).getBody()
if err != nil {
t.Fatal(err)
}
b, err := json.Marshal(out)
if err != nil {
t.Fatal(err)
}
got := string(b)
want := `{"dest":{"index":"new_twitter"},"size":10000,"source":{"index":"twitter","sort":[{"date":{"order":"desc"}}]}}`
if got != want {
t.Fatalf("\ngot %s\nwant %s", got, want)
}
}
func TestReindexSourceWithScript(t *testing.T) {
client := setupTestClient(t)
src := NewReindexSource().Index("twitter")
dst := NewReindexDestination().Index("new_twitter").VersionType("external")
scr := NewScriptInline("if (ctx._source.foo == 'bar') {ctx._version++; ctx._source.remove('foo')}")
out, err := client.Reindex().Source(src).Destination(dst).Script(scr).getBody()
if err != nil {
t.Fatal(err)
}
b, err := json.Marshal(out)
if err != nil {
t.Fatal(err)
}
got := string(b)
want := `{"dest":{"index":"new_twitter","version_type":"external"},"script":{"source":"if (ctx._source.foo == 'bar') {ctx._version++; ctx._source.remove('foo')}"},"source":{"index":"twitter"}}`
if got != want {
t.Fatalf("\ngot %s\nwant %s", got, want)
}
}
func TestReindexSourceWithRouting(t *testing.T) {
client := setupTestClient(t)
src := NewReindexSource().Index("source").Query(NewMatchQuery("company", "cat"))
dst := NewReindexDestination().Index("dest").Routing("=cat")
out, err := client.Reindex().Source(src).Destination(dst).getBody()
if err != nil {
t.Fatal(err)
}
b, err := json.Marshal(out)
if err != nil {
t.Fatal(err)
}
got := string(b)
want := `{"dest":{"index":"dest","routing":"=cat"},"source":{"index":"source","query":{"match":{"company":{"query":"cat"}}}}}`
if got != want {
t.Fatalf("\ngot %s\nwant %s", got, want)
}
}
func TestReindexSourceWithSourceFilter(t *testing.T) {
client := setupTestClient(t)
src := NewReindexSource().Index("twitter").
FetchSourceIncludeExclude([]string{"obj1.*", "obj2.*"}, []string{"*.description"})
dst := NewReindexDestination().Index("new_twitter")
out, err := client.Reindex().Source(src).Destination(dst).getBody()
if err != nil {
t.Fatal(err)
}
b, err := json.Marshal(out)
if err != nil {
t.Fatal(err)
}
got := string(b)
want := `{"dest":{"index":"new_twitter"},"source":{"_source":{"excludes":["*.description"],"includes":["obj1.*","obj2.*"]},"index":"twitter"}}`
if got != want {
t.Fatalf("\ngot %s\nwant %s", got, want)
}
}
func TestReindex(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t) // , SetTraceLog(log.New(os.Stdout, "", 0)))
sourceCount, err := client.Count(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if sourceCount <= 0 {
t.Fatalf("expected more than %d documents; got: %d", 0, sourceCount)
}
targetCount, err := client.Count(testIndexName2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if targetCount != 0 {
t.Fatalf("expected %d documents; got: %d", 0, targetCount)
}
// Simple copying
src := NewReindexSource().Index(testIndexName)
dst := NewReindexDestination().Index(testIndexName2)
res, err := client.Reindex().
Source(src).
Destination(dst).
Refresh("true").
Header("X-Opaque-Id", "987654").
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected result != nil")
}
if res.Total != sourceCount {
t.Errorf("expected %d, got %d", sourceCount, res.Total)
}
if res.Updated != 0 {
t.Errorf("expected %d, got %d", 0, res.Updated)
}
if res.Created != sourceCount {
t.Errorf("expected %d, got %d", sourceCount, res.Created)
}
if want, have := "987654", res.Header.Get("X-Opaque-Id"); want != have {
t.Fatalf("expected HTTP header %#v; got: %#v", want, have)
}
targetCount, err = client.Count(testIndexName2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if targetCount != sourceCount {
t.Fatalf("expected %d documents; got: %d", sourceCount, targetCount)
}
}
func TestReindexAsync(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t) //, SetTraceLog(log.New(os.Stdout, "", 0)))
sourceCount, err := client.Count(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if sourceCount <= 0 {
t.Fatalf("expected more than %d documents; got: %d", 0, sourceCount)
}
targetCount, err := client.Count(testIndexName2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if targetCount != 0 {
t.Fatalf("expected %d documents; got: %d", 0, targetCount)
}
// Simple copying
src := NewReindexSource().Index(testIndexName)
dst := NewReindexDestination().Index(testIndexName2)
res, err := client.Reindex().
Source(src).
Destination(dst).
Slices("auto").
Header("X-Opaque-Id", "987654").
DoAsync(context.TODO())
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected result != nil")
}
if res.TaskId == "" {
t.Errorf("expected a task id, got %+v", res)
}
if want, have := "987654", res.Header.Get("X-Opaque-Id"); want != have {
t.Fatalf("expected HTTP header %#v; got: %#v", want, have)
}
tasksGetTask := client.TasksGetTask()
taskStatus, err := tasksGetTask.TaskId(res.TaskId).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if taskStatus == nil {
t.Fatal("expected task status result != nil")
}
}
func TestReindexWithWaitForCompletionTrueCannotBeStarted(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t)
esversion, err := client.ElasticsearchVersion(DefaultURL)
if err != nil {
t.Fatal(err)
}
if esversion < "2.3.0" {
t.Skipf("Elasticsearch %v does not support Reindex API yet", esversion)
}
sourceCount, err := client.Count(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if sourceCount <= 0 {
t.Fatalf("expected more than %d documents; got: %d", 0, sourceCount)
}
targetCount, err := client.Count(testIndexName2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if targetCount != 0 {
t.Fatalf("expected %d documents; got: %d", 0, targetCount)
}
// DoAsync should fail when WaitForCompletion is true
src := NewReindexSource().Index(testIndexName)
dst := NewReindexDestination().Index(testIndexName2)
_, err = client.Reindex().Source(src).Destination(dst).WaitForCompletion(true).DoAsync(context.TODO())
if err == nil {
t.Fatal("error should have been returned")
}
}
================================================
FILE: request.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"bytes"
"compress/gzip"
"encoding/json"
"io"
"io/ioutil"
"net/http"
"strings"
)
// Elasticsearch-specific HTTP request
type Request http.Request
// NewRequest is a http.Request and adds features such as encoding the body.
func NewRequest(method, url string) (*Request, error) {
req, err := http.NewRequest(method, url, nil)
if err != nil {
return nil, err
}
req.Header.Add("Accept", "application/json")
req.Header.Set("Content-Type", "application/json")
return (*Request)(req), nil
}
// SetBasicAuth wraps http.Request's SetBasicAuth.
func (r *Request) SetBasicAuth(username, password string) {
((*http.Request)(r)).SetBasicAuth(username, password)
}
// SetBody encodes the body in the request. You may pass a flag to
// compress the request via gzip.
func (r *Request) SetBody(body interface{}, gzipCompress bool) error {
switch b := body.(type) {
case string:
if gzipCompress {
return r.setBodyGzip(b)
}
return r.setBodyString(b)
default:
if gzipCompress {
return r.setBodyGzip(body)
}
return r.setBodyJson(body)
}
}
// setBodyJson encodes the body as a struct to be marshaled via json.Marshal.
func (r *Request) setBodyJson(data interface{}) error {
body, err := json.Marshal(data)
if err != nil {
return err
}
r.Header.Set("Content-Type", "application/json")
r.setBodyReader(bytes.NewReader(body))
return nil
}
// setBodyString encodes the body as a string.
func (r *Request) setBodyString(body string) error {
return r.setBodyReader(strings.NewReader(body))
}
// setBodyGzip gzip's the body. It accepts both strings and structs as body.
// The latter will be encoded via json.Marshal.
func (r *Request) setBodyGzip(body interface{}) error {
switch b := body.(type) {
case string:
buf := new(bytes.Buffer)
w := gzip.NewWriter(buf)
if _, err := w.Write([]byte(b)); err != nil {
return err
}
if err := w.Close(); err != nil {
return err
}
r.Header.Add("Content-Encoding", "gzip")
r.Header.Add("Vary", "Accept-Encoding")
return r.setBodyReader(bytes.NewReader(buf.Bytes()))
default:
data, err := json.Marshal(b)
if err != nil {
return err
}
buf := new(bytes.Buffer)
w := gzip.NewWriter(buf)
if _, err := w.Write(data); err != nil {
return err
}
if err := w.Close(); err != nil {
return err
}
r.Header.Add("Content-Encoding", "gzip")
r.Header.Add("Vary", "Accept-Encoding")
r.Header.Set("Content-Type", "application/json")
return r.setBodyReader(bytes.NewReader(buf.Bytes()))
}
}
// setBodyReader writes the body from an io.Reader.
func (r *Request) setBodyReader(body io.Reader) error {
rc, ok := body.(io.ReadCloser)
if !ok && body != nil {
rc = ioutil.NopCloser(body)
}
r.Body = rc
if body != nil {
switch v := body.(type) {
case *strings.Reader:
r.ContentLength = int64(v.Len())
case *bytes.Buffer:
r.ContentLength = int64(v.Len())
}
}
return nil
}
================================================
FILE: request_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import "testing"
var testReq *Request // used as a temporary variable to avoid compiler optimizations in tests/benchmarks
func TestRequestSetContentType(t *testing.T) {
req, err := NewRequest("GET", "/")
if err != nil {
t.Fatal(err)
}
if want, have := "application/json", req.Header.Get("Content-Type"); want != have {
t.Fatalf("want Content-Type=%q, have %q", want, have)
}
req.Header.Set("Content-Type", "application/x-ndjson")
if want, have := "application/x-ndjson", req.Header.Get("Content-Type"); want != have {
t.Fatalf("want Content-Type=%q, have %q", want, have)
}
if want, have := "", req.Header.Get("User-Agent"); want != have {
t.Fatalf("want User-Agent=%q, have %q", want, have)
}
}
func BenchmarkRequestSetBodyString(b *testing.B) {
req, err := NewRequest("GET", "/")
if err != nil {
b.Fatal(err)
}
for i := 0; i < b.N; i++ {
body := `{"query":{"match_all":{}}}`
err = req.SetBody(body, false)
if err != nil {
b.Fatal(err)
}
}
testReq = req
b.ReportAllocs()
}
func BenchmarkRequestSetBodyStringGzip(b *testing.B) {
req, err := NewRequest("GET", "/")
if err != nil {
b.Fatal(err)
}
for i := 0; i < b.N; i++ {
body := `{"query":{"match_all":{}}}`
err = req.SetBody(body, true)
if err != nil {
b.Fatal(err)
}
}
testReq = req
b.ReportAllocs()
}
func BenchmarkRequestSetBodyBytes(b *testing.B) {
req, err := NewRequest("GET", "/")
if err != nil {
b.Fatal(err)
}
for i := 0; i < b.N; i++ {
body := []byte(`{"query":{"match_all":{}}}`)
err = req.SetBody(body, false)
if err != nil {
b.Fatal(err)
}
}
testReq = req
b.ReportAllocs()
}
func BenchmarkRequestSetBodyBytesGzip(b *testing.B) {
req, err := NewRequest("GET", "/")
if err != nil {
b.Fatal(err)
}
for i := 0; i < b.N; i++ {
body := []byte(`{"query":{"match_all":{}}}`)
err = req.SetBody(body, true)
if err != nil {
b.Fatal(err)
}
}
testReq = req
b.ReportAllocs()
}
func BenchmarkRequestSetBodyMap(b *testing.B) {
req, err := NewRequest("GET", "/")
if err != nil {
b.Fatal(err)
}
for i := 0; i < b.N; i++ {
body := map[string]interface{}{
"query": map[string]interface{}{
"match_all": map[string]interface{}{},
},
}
err = req.SetBody(body, false)
if err != nil {
b.Fatal(err)
}
}
testReq = req
b.ReportAllocs()
}
func BenchmarkRequestSetBodyMapGzip(b *testing.B) {
req, err := NewRequest("GET", "/")
if err != nil {
b.Fatal(err)
}
for i := 0; i < b.N; i++ {
body := map[string]interface{}{
"query": map[string]interface{}{
"match_all": map[string]interface{}{},
},
}
err = req.SetBody(body, true)
if err != nil {
b.Fatal(err)
}
}
testReq = req
b.ReportAllocs()
}
================================================
FILE: rescore.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
type Rescore struct {
rescorer Rescorer
windowSize *int
defaultRescoreWindowSize *int
}
func NewRescore() *Rescore {
return &Rescore{}
}
func (r *Rescore) WindowSize(windowSize int) *Rescore {
r.windowSize = &windowSize
return r
}
func (r *Rescore) IsEmpty() bool {
return r.rescorer == nil
}
func (r *Rescore) Rescorer(rescorer Rescorer) *Rescore {
r.rescorer = rescorer
return r
}
func (r *Rescore) Source() (interface{}, error) {
source := make(map[string]interface{})
if r.windowSize != nil {
source["window_size"] = *r.windowSize
} else if r.defaultRescoreWindowSize != nil {
source["window_size"] = *r.defaultRescoreWindowSize
}
rescorerSrc, err := r.rescorer.Source()
if err != nil {
return nil, err
}
source[r.rescorer.Name()] = rescorerSrc
return source, nil
}
================================================
FILE: rescorer.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
type Rescorer interface {
Name() string
Source() (interface{}, error)
}
// -- Query Rescorer --
type QueryRescorer struct {
query Query
rescoreQueryWeight *float64
queryWeight *float64
scoreMode string
}
func NewQueryRescorer(query Query) *QueryRescorer {
return &QueryRescorer{
query: query,
}
}
func (r *QueryRescorer) Name() string {
return "query"
}
func (r *QueryRescorer) RescoreQueryWeight(rescoreQueryWeight float64) *QueryRescorer {
r.rescoreQueryWeight = &rescoreQueryWeight
return r
}
func (r *QueryRescorer) QueryWeight(queryWeight float64) *QueryRescorer {
r.queryWeight = &queryWeight
return r
}
func (r *QueryRescorer) ScoreMode(scoreMode string) *QueryRescorer {
r.scoreMode = scoreMode
return r
}
func (r *QueryRescorer) Source() (interface{}, error) {
rescoreQuery, err := r.query.Source()
if err != nil {
return nil, err
}
source := make(map[string]interface{})
source["rescore_query"] = rescoreQuery
if r.queryWeight != nil {
source["query_weight"] = *r.queryWeight
}
if r.rescoreQueryWeight != nil {
source["rescore_query_weight"] = *r.rescoreQueryWeight
}
if r.scoreMode != "" {
source["score_mode"] = r.scoreMode
}
return source, nil
}
================================================
FILE: response.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"errors"
"io"
"io/ioutil"
"net/http"
)
var (
// ErrResponseSize is raised if a response body exceeds the given max body size.
ErrResponseSize = errors.New("elastic: response size too large")
)
// Response represents a response from Elasticsearch.
type Response struct {
// StatusCode is the HTTP status code, e.g. 200.
StatusCode int
// Header is the HTTP header from the HTTP response.
// Keys in the map are canonicalized (see http.CanonicalHeaderKey).
Header http.Header
// Body is the deserialized response body. Only available if streaming is disabled.
Body json.RawMessage
// DeprecationWarnings lists all deprecation warnings returned from
// Elasticsearch.
DeprecationWarnings []string
// BodyReader is the body as a reader. Only available if streaming is enabled.
BodyReader io.ReadCloser
}
// newResponse creates a new response from the HTTP response.
func (c *Client) newResponse(res *http.Response, maxBodySize int64, stream bool) (*Response, error) {
r := &Response{
StatusCode: res.StatusCode,
Header: res.Header,
DeprecationWarnings: res.Header["Warning"],
}
if stream {
r.BodyReader = res.Body
} else if res.Body != nil {
body := io.Reader(res.Body)
if maxBodySize > 0 {
if res.ContentLength > maxBodySize {
return nil, ErrResponseSize
}
body = io.LimitReader(body, maxBodySize+1)
}
slurp, err := ioutil.ReadAll(body)
if err != nil {
return nil, err
}
if maxBodySize > 0 && int64(len(slurp)) > maxBodySize {
return nil, ErrResponseSize
}
// HEAD requests return a body but no content
if len(slurp) > 0 {
r.Body = json.RawMessage(slurp)
}
}
return r, nil
}
================================================
FILE: response_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"testing"
)
func BenchmarkResponse(b *testing.B) {
c := &Client{
decoder: &DefaultDecoder{},
}
var resp *Response
for n := 0; n < b.N; n++ {
iteration := fmt.Sprint(n)
body := fmt.Sprintf(`{"n":%d}`, n)
res := &http.Response{
Header: http.Header{
"X-Iteration": []string{iteration},
},
Body: ioutil.NopCloser(bytes.NewBufferString(body)),
StatusCode: http.StatusOK,
}
var err error
resp, err = c.newResponse(res, 0, false)
if err != nil {
b.Fatal(err)
}
/*
if want, have := body, string(resp.Body); want != have {
b.Fatalf("want %q, have %q", want, have)
}
//*/
/*
if want, have := iteration, resp.Header.Get("X-Iteration"); want != have {
b.Fatalf("want %q, have %q", want, have)
}
//*/
}
_ = resp
}
================================================
FILE: retrier.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"net/http"
"time"
)
// RetrierFunc specifies the signature of a Retry function, and is an adapter
// to allow the use of ordinary Retry functions. If f is a function with the
// appropriate signature, RetrierFunc(f) is a Retrier that calls f.
type RetrierFunc func(context.Context, int, *http.Request, *http.Response, error) (time.Duration, bool, error)
// Retry calls f.
func (f RetrierFunc) Retry(ctx context.Context, retry int, req *http.Request, resp *http.Response, err error) (time.Duration, bool, error) {
return f(ctx, retry, req, resp, err)
}
// Retrier decides whether to retry a failed HTTP request with Elasticsearch.
type Retrier interface {
// Retry is called when a request has failed. It decides whether to retry
// the call, how long to wait for the next call, or whether to return an
// error (which will be returned to the service that started the HTTP
// request in the first place).
//
// Callers may also use this to inspect the HTTP request/response and
// the error that happened. Additional data can be passed through via
// the context.
Retry(ctx context.Context, retry int, req *http.Request, resp *http.Response, err error) (time.Duration, bool, error)
}
// -- StopRetrier --
// StopRetrier is an implementation that does no retries.
type StopRetrier struct {
}
// NewStopRetrier returns a retrier that does no retries.
func NewStopRetrier() *StopRetrier {
return &StopRetrier{}
}
// Retry does not retry.
func (r *StopRetrier) Retry(ctx context.Context, retry int, req *http.Request, resp *http.Response, err error) (time.Duration, bool, error) {
return 0, false, nil
}
// -- BackoffRetrier --
// BackoffRetrier is an implementation that does nothing but return nil on Retry.
type BackoffRetrier struct {
backoff Backoff
}
// NewBackoffRetrier returns a retrier that uses the given backoff strategy.
func NewBackoffRetrier(backoff Backoff) *BackoffRetrier {
return &BackoffRetrier{backoff: backoff}
}
// Retry calls into the backoff strategy and its wait interval.
func (r *BackoffRetrier) Retry(ctx context.Context, retry int, req *http.Request, resp *http.Response, err error) (time.Duration, bool, error) {
wait, goahead := r.backoff.Next(retry)
return wait, goahead, nil
}
================================================
FILE: retrier_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"errors"
"net/http"
"sync/atomic"
"testing"
"time"
)
type testRetrier struct {
Retrier
N int64
Err error
}
func (r *testRetrier) Retry(ctx context.Context, retry int, req *http.Request, resp *http.Response, err error) (time.Duration, bool, error) {
atomic.AddInt64(&r.N, 1)
if r.Err != nil {
return 0, false, r.Err
}
return r.Retrier.Retry(ctx, retry, req, resp, err)
}
func TestStopRetrier(t *testing.T) {
r := NewStopRetrier()
wait, ok, err := r.Retry(context.TODO(), 1, nil, nil, nil)
if want, got := 0*time.Second, wait; want != got {
t.Fatalf("expected %v, got %v", want, got)
}
if want, got := false, ok; want != got {
t.Fatalf("expected %v, got %v", want, got)
}
if err != nil {
t.Fatalf("expected nil, got %v", err)
}
}
func TestRetrier(t *testing.T) {
var numFailedReqs int
fail := func(r *http.Request) (*http.Response, error) {
numFailedReqs += 1
//return &http.Response{Request: r, StatusCode: 400}, nil
return nil, errors.New("request failed")
}
tr := &failingTransport{path: "/fail", fail: fail}
httpClient := &http.Client{Transport: tr}
retrier := &testRetrier{
Retrier: NewBackoffRetrier(NewSimpleBackoff(100, 100, 100, 100, 100)),
}
client, err := NewClient(
SetHttpClient(httpClient),
SetMaxRetries(5),
SetHealthcheck(false),
SetRetrier(retrier))
if err != nil {
t.Fatal(err)
}
res, err := client.PerformRequest(context.TODO(), PerformRequestOptions{
Method: "GET",
Path: "/fail",
})
if err == nil {
t.Fatal("expected error")
}
if res != nil {
t.Fatal("expected no response")
}
// Connection should be marked as dead after it failed
if numFailedReqs != 5 {
t.Errorf("expected %d failed requests; got: %d", 5, numFailedReqs)
}
if retrier.N != 5 {
t.Errorf("expected %d Retrier calls; got: %d", 5, retrier.N)
}
}
func TestRetrierWithError(t *testing.T) {
var numFailedReqs int
fail := func(r *http.Request) (*http.Response, error) {
numFailedReqs += 1
//return &http.Response{Request: r, StatusCode: 400}, nil
return nil, errors.New("request failed")
}
tr := &failingTransport{path: "/fail", fail: fail}
httpClient := &http.Client{Transport: tr}
kaboom := errors.New("kaboom")
retrier := &testRetrier{
Err: kaboom,
Retrier: NewBackoffRetrier(NewSimpleBackoff(100, 100, 100, 100, 100)),
}
client, err := NewClient(
SetHttpClient(httpClient),
SetMaxRetries(5),
SetHealthcheck(false),
SetRetrier(retrier))
if err != nil {
t.Fatal(err)
}
res, err := client.PerformRequest(context.TODO(), PerformRequestOptions{
Method: "GET",
Path: "/fail",
})
if err != kaboom {
t.Fatalf("expected %v, got %v", kaboom, err)
}
if res != nil {
t.Fatal("expected no response")
}
if numFailedReqs != 1 {
t.Errorf("expected %d failed requests; got: %d", 1, numFailedReqs)
}
if retrier.N != 1 {
t.Errorf("expected %d Retrier calls; got: %d", 1, retrier.N)
}
}
func TestRetrierOnPerformRequest(t *testing.T) {
var numFailedReqs int
fail := func(r *http.Request) (*http.Response, error) {
numFailedReqs += 1
//return &http.Response{Request: r, StatusCode: 400}, nil
return nil, errors.New("request failed")
}
tr := &failingTransport{path: "/fail", fail: fail}
httpClient := &http.Client{Transport: tr}
defaultRetrier := &testRetrier{
Retrier: NewStopRetrier(),
}
requestRetrier := &testRetrier{
Retrier: NewStopRetrier(),
}
client, err := NewClient(
SetHttpClient(httpClient),
SetHealthcheck(false),
SetRetrier(defaultRetrier))
if err != nil {
t.Fatal(err)
}
res, err := client.PerformRequest(context.TODO(), PerformRequestOptions{
Method: "GET",
Path: "/fail",
Retrier: requestRetrier,
})
if err == nil {
t.Fatal("expected error")
}
if res != nil {
t.Fatal("expected no response")
}
if want, have := int64(0), defaultRetrier.N; want != have {
t.Errorf("defaultRetrier: expected %d calls; got: %d", want, have)
}
if want, have := int64(1), requestRetrier.N; want != have {
t.Errorf("requestRetrier: expected %d calls; got: %d", want, have)
}
}
================================================
FILE: retry.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
// This file is based on code (c) 2014 Cenk Altı and governed by the MIT license.
// See https://github.com/cenkalti/backoff for original source.
package elastic
import "time"
// An Operation is executing by Retry() or RetryNotify().
// The operation will be retried using a backoff policy if it returns an error.
type Operation func() error
// Notify is a notify-on-error function. It receives error returned
// from an operation.
//
// Notice that if the backoff policy stated to stop retrying,
// the notify function isn't called.
type Notify func(error)
// Retry the function f until it does not return error or BackOff stops.
// f is guaranteed to be run at least once.
// It is the caller's responsibility to reset b after Retry returns.
//
// Retry sleeps the goroutine for the duration returned by BackOff after a
// failed operation returns.
func Retry(o Operation, b Backoff) error { return RetryNotify(o, b, nil) }
// RetryNotify calls notify function with the error and wait duration
// for each failed attempt before sleep.
func RetryNotify(operation Operation, b Backoff, notify Notify) error {
var err error
var wait time.Duration
var retry bool
var n int
for {
if err = operation(); err == nil {
return nil
}
n++
wait, retry = b.Next(n)
if !retry {
return err
}
if notify != nil {
notify(err)
}
time.Sleep(wait)
}
}
================================================
FILE: retry_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
// This file is based on code that is (c) 2014 Cenk Altı and governed
// by the MIT license.
// See https://github.com/cenkalti/backoff for original source.
package elastic
import (
"errors"
"testing"
"time"
)
func TestRetry(t *testing.T) {
const successOn = 3
var i = 0
// This function is successful on "successOn" calls.
f := func() error {
i++
// t.Logf("function is called %d. time\n", i)
if i == successOn {
// t.Log("OK")
return nil
}
// t.Log("error")
return errors.New("error")
}
min := time.Duration(8) * time.Millisecond
max := time.Duration(256) * time.Millisecond
err := Retry(f, NewExponentialBackoff(min, max))
if err != nil {
t.Errorf("unexpected error: %s", err.Error())
}
if i != successOn {
t.Errorf("invalid number of retries: %d", i)
}
}
================================================
FILE: runtime_mappings.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// RuntimeMappings specify fields that are evaluated at query time.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.14/runtime.html
// for details.
type RuntimeMappings map[string]interface{}
// Source deserializes the runtime mappings.
func (m *RuntimeMappings) Source() (interface{}, error) {
return m, nil
}
================================================
FILE: runtime_mappings_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"testing"
"time"
)
func TestRuntimeMappingsSource(t *testing.T) {
rm := RuntimeMappings{
"day_of_week": map[string]interface{}{
"type": "keyword",
},
}
src, err := rm.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatal(err)
}
expected := `{"day_of_week":{"type":"keyword"}}`
if want, have := expected, string(data); want != have {
t.Fatalf("want %s, have %s", want, have)
}
}
func TestRuntimeMappings(t *testing.T) {
client := setupTestClient(t) //, SetTraceLog(log.New(os.Stdout, "", 0)))
ctx := context.Background()
indexName := testIndexName
// Create index
createIndex, err := client.CreateIndex(indexName).Do(ctx)
if err != nil {
t.Fatal(err)
}
if createIndex == nil {
t.Errorf("expected result to be != nil; got: %v", createIndex)
}
mapping := `{
"dynamic": "runtime",
"properties": {
"@timestamp": {
"type":"date"
}
},
"runtime": {
"day_of_week": {
"type": "keyword",
"script": {
"source": "emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"
}
}
}
}`
type Doc struct {
Timestamp time.Time `json:"@timestamp"`
}
type DynamicDoc struct {
Timestamp time.Time `json:"@timestamp"`
DayOfWeek string `json:"day_of_week"`
}
// Create mapping
putResp, err := client.PutMapping().
Index(indexName).
BodyString(mapping).
Do(ctx)
if err != nil {
t.Fatalf("expected put mapping to succeed; got: %v", err)
}
if putResp == nil {
t.Fatalf("expected put mapping response; got: %v", putResp)
}
if !putResp.Acknowledged {
t.Fatalf("expected put mapping ack; got: %v", putResp.Acknowledged)
}
// Add a document
timestamp := time.Date(2021, 1, 17, 23, 24, 25, 26, time.UTC)
indexResult, err := client.Index().
Index(indexName).
Id("1").
BodyJson(&Doc{
Timestamp: timestamp,
}).
Refresh("wait_for").
Do(ctx)
if err != nil {
t.Fatal(err)
}
if indexResult == nil {
t.Errorf("expected result to be != nil; got: %v", indexResult)
}
// Execute a search to check for runtime fields
searchResp, err := client.Search(indexName).
Query(NewMatchAllQuery()).
DocvalueFields("@timestamp", "day_of_week").
Do(ctx)
if err != nil {
t.Fatal(err)
}
if searchResp == nil {
t.Errorf("expected result to be != nil; got: %v", searchResp)
}
if want, have := int64(1), searchResp.TotalHits(); want != have {
t.Fatalf("expected %d search hits, got %d", want, have)
}
// The hit should not have the "day_of_week"
hit := searchResp.Hits.Hits[0]
var doc DynamicDoc
if err := json.Unmarshal(hit.Source, &doc); err != nil {
t.Fatalf("unable to deserialize hit: %v", err)
}
if want, have := timestamp, doc.Timestamp; want != have {
t.Fatalf("expected timestamp=%v, got %v", want, have)
}
if want, have := "", doc.DayOfWeek; want != have {
t.Fatalf("expected day_of_week=%q, got %q", want, have)
}
// The fields should include a "day_of_week" of ["Sunday"]
dayOfWeekIntfSlice, ok := hit.Fields["day_of_week"].([]interface{})
if !ok {
t.Fatalf("expected a slice of strings, got %T", hit.Fields["day_of_week"])
}
if want, have := 1, len(dayOfWeekIntfSlice); want != have {
t.Fatalf("expected a slice of size %d, have %d", want, have)
}
dayOfWeek, ok := dayOfWeekIntfSlice[0].(string)
if !ok {
t.Fatalf("expected an element of string, got %T", dayOfWeekIntfSlice[0])
}
if want, have := "Sunday", dayOfWeek; want != have {
t.Fatalf("expected day_of_week=%q, have %q", want, have)
}
}
================================================
FILE: script.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"errors"
"fmt"
"strings"
)
// Script holds all the parameters necessary to compile or find in cache
// and then execute a script.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/modules-scripting.html
// for details of scripting.
type Script struct {
script string
typ string
lang string
params map[string]interface{}
}
// NewScript creates and initializes a new Script. By default, it is of
// type "inline". Use NewScriptStored for a stored script (where type is "id").
func NewScript(script string) *Script {
return &Script{
script: script,
typ: "inline",
params: make(map[string]interface{}),
}
}
// NewScriptInline creates and initializes a new inline script, i.e. code.
func NewScriptInline(script string) *Script {
return NewScript(script).Type("inline")
}
// NewScriptStored creates and initializes a new stored script.
func NewScriptStored(script string) *Script {
return NewScript(script).Type("id")
}
// Script is either the cache key of the script to be compiled/executed
// or the actual script source code for inline scripts. For indexed
// scripts this is the id used in the request. For file scripts this is
// the file name.
func (s *Script) Script(script string) *Script {
s.script = script
return s
}
// Type sets the type of script: "inline" or "id".
func (s *Script) Type(typ string) *Script {
s.typ = typ
return s
}
// Lang sets the language of the script. The default scripting language
// is Painless ("painless").
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/modules-scripting.html
// for details.
func (s *Script) Lang(lang string) *Script {
s.lang = lang
return s
}
// Param adds a key/value pair to the parameters that this script will be executed with.
func (s *Script) Param(name string, value interface{}) *Script {
if s.params == nil {
s.params = make(map[string]interface{})
}
s.params[name] = value
return s
}
// Params sets the map of parameters this script will be executed with.
func (s *Script) Params(params map[string]interface{}) *Script {
s.params = params
return s
}
// Source returns the JSON serializable data for this Script.
func (s *Script) Source() (interface{}, error) {
if s.typ == "" && s.lang == "" && len(s.params) == 0 {
return s.script, nil
}
source := make(map[string]interface{})
// Beginning with 6.0, the type can only be "source" or "id"
if s.typ == "" || s.typ == "inline" {
src, err := s.rawScriptSource(s.script)
if err != nil {
return nil, err
}
source["source"] = src
} else {
source["id"] = s.script
}
if s.lang != "" {
source["lang"] = s.lang
}
if len(s.params) > 0 {
source["params"] = s.params
}
return source, nil
}
// rawScriptSource returns an embeddable script. If it uses a short
// script form, e.g. "ctx._source.likes++" (without the quotes), it
// is quoted. Otherwise it returns the raw script that will be directly
// embedded into the JSON data.
func (s *Script) rawScriptSource(script string) (interface{}, error) {
v := strings.TrimSpace(script)
if !strings.HasPrefix(v, "{") && !strings.HasPrefix(v, `"`) {
v = fmt.Sprintf("%q", v)
}
raw := json.RawMessage(v)
return &raw, nil
}
// -- Script Field --
// ScriptField is a single script field.
type ScriptField struct {
FieldName string // name of the field
script *Script
ignoreFailure *bool // used in e.g. ScriptSource
}
// NewScriptField creates and initializes a new ScriptField.
func NewScriptField(fieldName string, script *Script) *ScriptField {
return &ScriptField{FieldName: fieldName, script: script}
}
// IgnoreFailure indicates whether to ignore failures. It is used
// in e.g. ScriptSource.
func (f *ScriptField) IgnoreFailure(ignore bool) *ScriptField {
f.ignoreFailure = &ignore
return f
}
// Source returns the serializable JSON for the ScriptField.
func (f *ScriptField) Source() (interface{}, error) {
if f.script == nil {
return nil, errors.New("ScriptField expects script")
}
source := make(map[string]interface{})
src, err := f.script.Source()
if err != nil {
return nil, err
}
source["script"] = src
if v := f.ignoreFailure; v != nil {
source["ignore_failure"] = *v
}
return source, nil
}
================================================
FILE: script_delete.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// DeleteScriptService removes a stored script in Elasticsearch.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/modules-scripting.html
// for details.
type DeleteScriptService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
id string
timeout string
masterTimeout string
}
// NewDeleteScriptService creates a new DeleteScriptService.
func NewDeleteScriptService(client *Client) *DeleteScriptService {
return &DeleteScriptService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *DeleteScriptService) Pretty(pretty bool) *DeleteScriptService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *DeleteScriptService) Human(human bool) *DeleteScriptService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *DeleteScriptService) ErrorTrace(errorTrace bool) *DeleteScriptService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *DeleteScriptService) FilterPath(filterPath ...string) *DeleteScriptService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *DeleteScriptService) Header(name string, value string) *DeleteScriptService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *DeleteScriptService) Headers(headers http.Header) *DeleteScriptService {
s.headers = headers
return s
}
// Id is the script ID.
func (s *DeleteScriptService) Id(id string) *DeleteScriptService {
s.id = id
return s
}
// Timeout is an explicit operation timeout.
func (s *DeleteScriptService) Timeout(timeout string) *DeleteScriptService {
s.timeout = timeout
return s
}
// MasterTimeout is the timeout for connecting to master.
func (s *DeleteScriptService) MasterTimeout(masterTimeout string) *DeleteScriptService {
s.masterTimeout = masterTimeout
return s
}
// buildURL builds the URL for the operation.
func (s *DeleteScriptService) buildURL() (string, string, url.Values, error) {
var (
err error
method = "DELETE"
path string
)
path, err = uritemplates.Expand("/_scripts/{id}", map[string]string{
"id": s.id,
})
if err != nil {
return "", "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.timeout != "" {
params.Set("timeout", s.timeout)
}
if s.masterTimeout != "" {
params.Set("master_timestamp", s.masterTimeout)
}
return method, path, params, nil
}
// Validate checks if the operation is valid.
func (s *DeleteScriptService) Validate() error {
var invalid []string
if s.id == "" {
invalid = append(invalid, "Id")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *DeleteScriptService) Do(ctx context.Context) (*DeleteScriptResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
method, path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: method,
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(DeleteScriptResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// DeleteScriptResponse is the result of deleting a stored script
// in Elasticsearch.
type DeleteScriptResponse struct {
AcknowledgedResponse
}
================================================
FILE: script_delete_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestDeleteScript(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
scriptID := "example-delete-script-id"
// Ensure the script does not exist
_, err := client.PerformRequest(
context.Background(),
PerformRequestOptions{
Method: "DELETE",
Path: "/_scripts/" + scriptID,
})
if err != nil && !IsNotFound(err) {
t.Fatal(err)
}
// PutScript API
script := `{
"script": {
"lang": "painless",
"source": "ctx._source.message = params.new_message"
}
}`
putRes, err := client.PutScript().
Id(scriptID).
BodyString(script).
Do(context.Background())
if err != nil {
t.Fatal(err)
}
if putRes == nil {
t.Errorf("expected result to be != nil; got: %v", putRes)
}
if !putRes.Acknowledged {
t.Errorf("expected ack for PutScript op; got %v", putRes.Acknowledged)
}
// Must exist now
_, err = client.PerformRequest(
context.Background(),
PerformRequestOptions{
Method: "GET",
Path: "/_scripts/" + scriptID,
})
if err != nil {
t.Fatal(err)
}
// DeleteScript API
res, err := client.DeleteScript().
Id(scriptID).
Do(context.Background())
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Errorf("expected result to be != nil; got: %v", res)
}
if !res.Acknowledged {
t.Errorf("expected ack for DeleteScript op; got %v", res.Acknowledged)
}
// Must not exist now
_, err = client.PerformRequest(
context.Background(),
PerformRequestOptions{
Method: "DELETE",
Path: "/_scripts/" + scriptID,
})
if err != nil && !IsNotFound(err) {
t.Fatal(err)
}
}
================================================
FILE: script_get.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// GetScriptService reads a stored script in Elasticsearch.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/modules-scripting.html
// for details.
type GetScriptService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
id string
}
// NewGetScriptService creates a new GetScriptService.
func NewGetScriptService(client *Client) *GetScriptService {
return &GetScriptService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *GetScriptService) Pretty(pretty bool) *GetScriptService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *GetScriptService) Human(human bool) *GetScriptService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *GetScriptService) ErrorTrace(errorTrace bool) *GetScriptService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *GetScriptService) FilterPath(filterPath ...string) *GetScriptService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *GetScriptService) Header(name string, value string) *GetScriptService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *GetScriptService) Headers(headers http.Header) *GetScriptService {
s.headers = headers
return s
}
// Id is the script ID.
func (s *GetScriptService) Id(id string) *GetScriptService {
s.id = id
return s
}
// buildURL builds the URL for the operation.
func (s *GetScriptService) buildURL() (string, string, url.Values, error) {
var (
err error
method = "GET"
path string
)
path, err = uritemplates.Expand("/_scripts/{id}", map[string]string{
"id": s.id,
})
if err != nil {
return "", "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
return method, path, params, nil
}
// Validate checks if the operation is valid.
func (s *GetScriptService) Validate() error {
var invalid []string
if s.id == "" {
invalid = append(invalid, "Id")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *GetScriptService) Do(ctx context.Context) (*GetScriptResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
method, path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: method,
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(GetScriptResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// GetScriptResponse is the result of getting a stored script
// in Elasticsearch.
type GetScriptResponse struct {
Id string `json:"_id"`
Found bool `json:"found"`
Script json.RawMessage `json:"script"`
}
================================================
FILE: script_get_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestGetScript(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
scriptID := "example-get-script-id"
// Ensure the script does not exist
_, err := client.PerformRequest(
context.Background(),
PerformRequestOptions{
Method: "DELETE",
Path: "/_scripts/" + scriptID,
})
if err != nil && !IsNotFound(err) {
t.Fatal(err)
}
// PutScript API
script := `{
"script": {
"lang": "painless",
"source": "ctx._source.message = params.new_message"
}
}`
putRes, err := client.PutScript().
Id(scriptID).
BodyString(script).
Do(context.Background())
if err != nil {
t.Fatal(err)
}
if putRes == nil {
t.Errorf("expected result to be != nil; got: %v", putRes)
}
if !putRes.Acknowledged {
t.Errorf("expected ack for PutScript op; got %v", putRes.Acknowledged)
}
// Must exist now
_, err = client.PerformRequest(
context.Background(),
PerformRequestOptions{
Method: "GET",
Path: "/_scripts/" + scriptID,
})
if err != nil {
t.Fatal(err)
}
// GetScript API
res, err := client.GetScript().
Id(scriptID).
Do(context.Background())
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Errorf("expected result to be != nil; got: %v", res)
}
if want, have := scriptID, res.Id; want != have {
t.Fatalf("expected _id = %q; got: %q", want, have)
}
if want, have := true, res.Found; want != have {
t.Fatalf("expected found = %v; got: %v", want, have)
}
if res.Script == nil {
t.Fatal("expected script; got: nil")
}
outScript := `{"lang":"painless","source":"ctx._source.message = params.new_message"}`
if want, have := outScript, string(res.Script); want != have {
t.Fatalf("expected script = %q; got: %q", want, have)
}
// Cleanup
client.PerformRequest(
context.Background(),
PerformRequestOptions{
Method: "DELETE",
Path: "/_scripts/" + scriptID,
})
}
================================================
FILE: script_put.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// PutScriptService adds or updates a stored script in Elasticsearch.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/modules-scripting.html
// for details.
type PutScriptService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
id string
context string
timeout string
masterTimeout string
bodyJson interface{}
bodyString string
}
// NewPutScriptService creates a new PutScriptService.
func NewPutScriptService(client *Client) *PutScriptService {
return &PutScriptService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *PutScriptService) Pretty(pretty bool) *PutScriptService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *PutScriptService) Human(human bool) *PutScriptService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *PutScriptService) ErrorTrace(errorTrace bool) *PutScriptService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *PutScriptService) FilterPath(filterPath ...string) *PutScriptService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *PutScriptService) Header(name string, value string) *PutScriptService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *PutScriptService) Headers(headers http.Header) *PutScriptService {
s.headers = headers
return s
}
// Id is the script ID.
func (s *PutScriptService) Id(id string) *PutScriptService {
s.id = id
return s
}
// Context specifies the script context (optional).
func (s *PutScriptService) Context(context string) *PutScriptService {
s.context = context
return s
}
// Timeout is an explicit operation timeout.
func (s *PutScriptService) Timeout(timeout string) *PutScriptService {
s.timeout = timeout
return s
}
// MasterTimeout is the timeout for connecting to master.
func (s *PutScriptService) MasterTimeout(masterTimeout string) *PutScriptService {
s.masterTimeout = masterTimeout
return s
}
// BodyJson is the document as a serializable JSON interface.
func (s *PutScriptService) BodyJson(body interface{}) *PutScriptService {
s.bodyJson = body
return s
}
// BodyString is the document encoded as a string.
func (s *PutScriptService) BodyString(body string) *PutScriptService {
s.bodyString = body
return s
}
// buildURL builds the URL for the operation.
func (s *PutScriptService) buildURL() (string, string, url.Values, error) {
var (
err error
method = "PUT"
path string
)
if s.context != "" {
path, err = uritemplates.Expand("/_scripts/{id}/{context}", map[string]string{
"id": s.id,
"context": s.context,
})
} else {
path, err = uritemplates.Expand("/_scripts/{id}", map[string]string{
"id": s.id,
})
}
if err != nil {
return "", "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.timeout != "" {
params.Set("timeout", s.timeout)
}
if s.masterTimeout != "" {
params.Set("master_timestamp", s.masterTimeout)
}
return method, path, params, nil
}
// Validate checks if the operation is valid.
func (s *PutScriptService) Validate() error {
var invalid []string
if s.id == "" {
invalid = append(invalid, "Id")
}
if s.bodyString == "" && s.bodyJson == nil {
invalid = append(invalid, "BodyJson")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *PutScriptService) Do(ctx context.Context) (*PutScriptResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
method, path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Setup HTTP request body
var body interface{}
if s.bodyJson != nil {
body = s.bodyJson
} else {
body = s.bodyString
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: method,
Path: path,
Params: params,
Body: body,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(PutScriptResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// PutScriptResponse is the result of saving a stored script
// in Elasticsearch.
type PutScriptResponse struct {
AcknowledgedResponse
}
================================================
FILE: script_put_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestPutScript(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
scriptID := "example-put-script-id"
// Ensure the script does not exist
_, err := client.PerformRequest(
context.Background(),
PerformRequestOptions{
Method: "DELETE",
Path: "/_scripts/" + scriptID,
})
if err != nil && !IsNotFound(err) {
t.Fatal(err)
}
// PutScript API
script := `{
"script": {
"lang": "painless",
"source": "ctx._source.message = params.new_message"
}
}`
res, err := client.PutScript().
Id(scriptID).
BodyString(script).
Do(context.Background())
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Errorf("expected result to be != nil; got: %v", res)
}
if !res.Acknowledged {
t.Errorf("expected ack for PutScript op; got %v", res.Acknowledged)
}
// Must exist now
_, err = client.PerformRequest(
context.Background(),
PerformRequestOptions{
Method: "GET",
Path: "/_scripts/" + scriptID,
})
if err != nil {
t.Fatal(err)
}
// Cleanup
client.PerformRequest(
context.Background(),
PerformRequestOptions{
Method: "DELETE",
Path: "/_scripts/" + scriptID,
})
}
================================================
FILE: script_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestScriptingDefault(t *testing.T) {
builder := NewScript("doc['field'].value * 2")
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"source":"doc['field'].value * 2"}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestScriptingInline(t *testing.T) {
builder := NewScriptInline("doc['field'].value * factor").Param("factor", 2.0)
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"params":{"factor":2},"source":"doc['field'].value * factor"}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestScriptingStored(t *testing.T) {
builder := NewScriptStored("script-with-id").Param("factor", 2.0)
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"id":"script-with-id","params":{"factor":2}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestScriptingSource(t *testing.T) {
tests := []struct {
Input string
Source string
}{
{
Input: ``,
Source: `{"source":""}`,
},
{
Input: `doc['field'].value * factor`,
Source: `{"source":"doc['field'].value * factor"}`,
},
{
Input: `"doc['field'].value * factor"`,
Source: `{"source":"doc['field'].value * factor"}`,
},
{
Input: `{"bool":{"filter":{"term":{"field1":"f"}}}}`,
Source: `{"source":{"bool":{"filter":{"term":{"field1":"f"}}}}}`,
},
}
for _, tt := range tests {
b := NewScriptInline(tt.Input)
src, err := b.Source()
if err != nil {
t.Fatalf("unable to generate source for %s: %v", tt.Input, err)
}
out, err := json.Marshal(src)
if err != nil {
t.Fatalf("unable to generate JSON for %s: %v", tt.Input, err)
}
if want, have := tt.Source, string(out); want != have {
t.Fatalf("Input=%q: want %s, have %s", tt.Input, want, have)
}
}
}
================================================
FILE: scroll.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"sync"
"github.com/olivere/elastic/v7/uritemplates"
)
const (
// DefaultScrollKeepAlive is the default time a scroll cursor will be kept alive.
DefaultScrollKeepAlive = "5m"
)
// ScrollService iterates over pages of search results from Elasticsearch.
type ScrollService struct {
client *Client
retrier Retrier
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
indices []string
types []string
keepAlive string
body interface{}
ss *SearchSource
size *int
routing string
preference string
ignoreUnavailable *bool
ignoreThrottled *bool
allowNoIndices *bool
expandWildcards string
maxResponseSize int64
restTotalHitsAsInt *bool
mu sync.RWMutex
scrollId string
}
// NewScrollService initializes and returns a new ScrollService.
func NewScrollService(client *Client) *ScrollService {
builder := &ScrollService{
client: client,
ss: NewSearchSource(),
keepAlive: DefaultScrollKeepAlive,
}
return builder
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *ScrollService) Pretty(pretty bool) *ScrollService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *ScrollService) Human(human bool) *ScrollService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *ScrollService) ErrorTrace(errorTrace bool) *ScrollService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *ScrollService) FilterPath(filterPath ...string) *ScrollService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *ScrollService) Header(name string, value string) *ScrollService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *ScrollService) Headers(headers http.Header) *ScrollService {
s.headers = headers
return s
}
// Retrier allows to set specific retry logic for this ScrollService.
// If not specified, it will use the client's default retrier.
func (s *ScrollService) Retrier(retrier Retrier) *ScrollService {
s.retrier = retrier
return s
}
// Index sets the name of one or more indices to iterate over.
func (s *ScrollService) Index(indices ...string) *ScrollService {
if s.indices == nil {
s.indices = make([]string, 0)
}
s.indices = append(s.indices, indices...)
return s
}
// Type sets the name of one or more types to iterate over.
//
// Deprecated: Types are in the process of being removed. Instead of using a type, prefer to
// filter on a field on the document.
func (s *ScrollService) Type(types ...string) *ScrollService {
if s.types == nil {
s.types = make([]string, 0)
}
s.types = append(s.types, types...)
return s
}
// Scroll is an alias for KeepAlive, the time to keep
// the cursor alive (e.g. "5m" for 5 minutes).
func (s *ScrollService) Scroll(keepAlive string) *ScrollService {
s.keepAlive = keepAlive
return s
}
// KeepAlive sets the maximum time after which the cursor will expire.
// It is "5m" by default.
func (s *ScrollService) KeepAlive(keepAlive string) *ScrollService {
s.keepAlive = keepAlive
return s
}
// Size specifies the number of documents Elasticsearch should return
// from each shard, per page.
func (s *ScrollService) Size(size int) *ScrollService {
s.size = &size
return s
}
// Highlight allows to highlight search results on one or more fields
func (s *ScrollService) Highlight(highlight *Highlight) *ScrollService {
s.ss = s.ss.Highlight(highlight)
return s
}
// Body sets the raw body to send to Elasticsearch. This can be e.g. a string,
// a map[string]interface{} or anything that can be serialized into JSON.
// Notice that setting the body disables the use of SearchSource and many
// other properties of the ScanService.
func (s *ScrollService) Body(body interface{}) *ScrollService {
s.body = body
return s
}
// SearchSource sets the search source builder to use with this iterator.
// Notice that only a certain number of properties can be used when scrolling,
// e.g. query and sorting.
func (s *ScrollService) SearchSource(searchSource *SearchSource) *ScrollService {
s.ss = searchSource
if s.ss == nil {
s.ss = NewSearchSource()
}
return s
}
// Query sets the query to perform, e.g. a MatchAllQuery.
func (s *ScrollService) Query(query Query) *ScrollService {
s.ss = s.ss.Query(query)
return s
}
// PostFilter is executed as the last filter. It only affects the
// search hits but not facets. See
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-request-post-filter.html
// for details.
func (s *ScrollService) PostFilter(postFilter Query) *ScrollService {
s.ss = s.ss.PostFilter(postFilter)
return s
}
// Slice allows slicing the scroll request into several batches.
// This is supported in Elasticsearch 5.0 or later.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-request-scroll.html#sliced-scroll
// for details.
func (s *ScrollService) Slice(sliceQuery Query) *ScrollService {
s.ss = s.ss.Slice(sliceQuery)
return s
}
// FetchSource indicates whether the response should contain the stored
// _source for every hit.
func (s *ScrollService) FetchSource(fetchSource bool) *ScrollService {
s.ss = s.ss.FetchSource(fetchSource)
return s
}
// FetchSourceContext indicates how the _source should be fetched.
func (s *ScrollService) FetchSourceContext(fetchSourceContext *FetchSourceContext) *ScrollService {
s.ss = s.ss.FetchSourceContext(fetchSourceContext)
return s
}
// Version can be set to true to return a version for each search hit.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-request-version.html.
func (s *ScrollService) Version(version bool) *ScrollService {
s.ss = s.ss.Version(version)
return s
}
// Sort adds a sort order. This can have negative effects on the performance
// of the scroll operation as Elasticsearch needs to sort first.
func (s *ScrollService) Sort(field string, ascending bool) *ScrollService {
s.ss = s.ss.Sort(field, ascending)
return s
}
// SortWithInfo specifies a sort order. Notice that sorting can have a
// negative impact on scroll performance.
func (s *ScrollService) SortWithInfo(info SortInfo) *ScrollService {
s.ss = s.ss.SortWithInfo(info)
return s
}
// SortBy specifies a sort order. Notice that sorting can have a
// negative impact on scroll performance.
func (s *ScrollService) SortBy(sorter ...Sorter) *ScrollService {
s.ss = s.ss.SortBy(sorter...)
return s
}
// TrackTotalHits controls if the total hit count for the query should be tracked.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.1/search-request-track-total-hits.html
// for details.
func (s *ScrollService) TrackTotalHits(trackTotalHits interface{}) *ScrollService {
s.ss = s.ss.TrackTotalHits(trackTotalHits)
return s
}
// RestTotalHitsAsInt indicates whether hits.total should be rendered as an
// integer or an object in the rest search response.
func (s *ScrollService) RestTotalHitsAsInt(enabled bool) *ScrollService {
s.restTotalHitsAsInt = &enabled
return s
}
// Routing is a list of specific routing values to control the shards
// the search will be executed on.
func (s *ScrollService) Routing(routings ...string) *ScrollService {
s.routing = strings.Join(routings, ",")
return s
}
// Preference sets the preference to execute the search. Defaults to
// randomize across shards ("random"). Can be set to "_local" to prefer
// local shards, "_primary" to execute on primary shards only,
// or a custom value which guarantees that the same order will be used
// across different requests.
func (s *ScrollService) Preference(preference string) *ScrollService {
s.preference = preference
return s
}
// IgnoreUnavailable indicates whether the specified concrete indices
// should be ignored when unavailable (missing or closed).
func (s *ScrollService) IgnoreUnavailable(ignoreUnavailable bool) *ScrollService {
s.ignoreUnavailable = &ignoreUnavailable
return s
}
// IgnoreThrottled indicates whether specified concrete, expanded or aliased
// indices should be ignored when throttled.
func (s *ScrollService) IgnoreThrottled(ignoreThrottled bool) *ScrollService {
s.ignoreThrottled = &ignoreThrottled
return s
}
// AllowNoIndices indicates whether to ignore if a wildcard indices
// expression resolves into no concrete indices. (This includes `_all` string
// or when no indices have been specified).
func (s *ScrollService) AllowNoIndices(allowNoIndices bool) *ScrollService {
s.allowNoIndices = &allowNoIndices
return s
}
// ExpandWildcards indicates whether to expand wildcard expression to
// concrete indices that are open, closed or both.
func (s *ScrollService) ExpandWildcards(expandWildcards string) *ScrollService {
s.expandWildcards = expandWildcards
return s
}
// MaxResponseSize sets an upper limit on the response body size that we accept,
// to guard against OOM situations.
func (s *ScrollService) MaxResponseSize(maxResponseSize int64) *ScrollService {
s.maxResponseSize = maxResponseSize
return s
}
// NoStoredFields indicates that no stored fields should be loaded, resulting in only
// id and type to be returned per field.
func (s *ScrollService) NoStoredFields() *ScrollService {
s.ss = s.ss.NoStoredFields()
return s
}
// StoredField adds a single field to load and return (note, must be stored) as
// part of the search request. If none are specified, the source of the
// document will be returned.
func (s *ScrollService) StoredField(fieldName string) *ScrollService {
s.ss = s.ss.StoredField(fieldName)
return s
}
// StoredFields sets the fields to load and return as part of the search request.
// If none are specified, the source of the document will be returned.
func (s *ScrollService) StoredFields(fields ...string) *ScrollService {
s.ss = s.ss.StoredFields(fields...)
return s
}
// ScrollId specifies the identifier of a scroll in action.
func (s *ScrollService) ScrollId(scrollId string) *ScrollService {
s.mu.Lock()
s.scrollId = scrollId
s.mu.Unlock()
return s
}
// Do returns the next search result. It will return io.EOF as error if there
// are no more search results.
func (s *ScrollService) Do(ctx context.Context) (*SearchResult, error) {
s.mu.RLock()
nextScrollId := s.scrollId
s.mu.RUnlock()
if len(nextScrollId) == 0 {
return s.first(ctx)
}
return s.next(ctx)
}
// Clear cancels the current scroll operation. If you don't do this manually,
// the scroll will be expired automatically by Elasticsearch. You can control
// how long a scroll cursor is kept alive with the KeepAlive func.
func (s *ScrollService) Clear(ctx context.Context) error {
s.mu.RLock()
scrollId := s.scrollId
s.mu.RUnlock()
if len(scrollId) == 0 {
return nil
}
path := "/_search/scroll"
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
body := struct {
ScrollId []string `json:"scroll_id,omitempty"`
}{
ScrollId: []string{scrollId},
}
_, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "DELETE",
Path: path,
Params: params,
Body: body,
Retrier: s.retrier,
})
if err != nil {
return err
}
return nil
}
// -- First --
// first takes the first page of search results.
func (s *ScrollService) first(ctx context.Context) (*SearchResult, error) {
// Get URL and parameters for request
path, params, err := s.buildFirstURL()
if err != nil {
return nil, err
}
// Get HTTP request body
body, err := s.bodyFirst()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "POST",
Path: path,
Params: params,
Body: body,
Retrier: s.retrier,
Headers: s.headers,
MaxResponseSize: s.maxResponseSize,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(SearchResult)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
s.mu.Lock()
s.scrollId = ret.ScrollId
s.mu.Unlock()
if ret.Hits == nil || len(ret.Hits.Hits) == 0 {
return ret, io.EOF
}
return ret, nil
}
// buildFirstURL builds the URL for retrieving the first page.
func (s *ScrollService) buildFirstURL() (string, url.Values, error) {
// Build URL
var err error
var path string
if len(s.indices) == 0 && len(s.types) == 0 {
path = "/_search"
} else if len(s.indices) > 0 && len(s.types) == 0 {
path, err = uritemplates.Expand("/{index}/_search", map[string]string{
"index": strings.Join(s.indices, ","),
})
} else if len(s.indices) == 0 && len(s.types) > 0 {
path, err = uritemplates.Expand("/_all/{typ}/_search", map[string]string{
"typ": strings.Join(s.types, ","),
})
} else {
path, err = uritemplates.Expand("/{index}/{typ}/_search", map[string]string{
"index": strings.Join(s.indices, ","),
"typ": strings.Join(s.types, ","),
})
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
// Always add "hits._scroll_id", otherwise we cannot scroll
var found bool
for _, path := range s.filterPath {
if path == "_scroll_id" {
found = true
break
}
}
if !found {
s.filterPath = append(s.filterPath, "_scroll_id")
}
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.size != nil && *s.size > 0 {
params.Set("size", fmt.Sprintf("%d", *s.size))
}
if len(s.keepAlive) > 0 {
params.Set("scroll", s.keepAlive)
}
if len(s.routing) > 0 {
params.Set("routing", s.routing)
}
if len(s.preference) > 0 {
params.Set("preference", s.preference)
}
if s.allowNoIndices != nil {
params.Set("allow_no_indices", fmt.Sprintf("%v", *s.allowNoIndices))
}
if len(s.expandWildcards) > 0 {
params.Set("expand_wildcards", s.expandWildcards)
}
if s.ignoreUnavailable != nil {
params.Set("ignore_unavailable", fmt.Sprintf("%v", *s.ignoreUnavailable))
}
if s.ignoreThrottled != nil {
params.Set("ignore_throttled", fmt.Sprintf("%v", *s.ignoreThrottled))
}
if v := s.restTotalHitsAsInt; v != nil {
params.Set("rest_total_hits_as_int", fmt.Sprint(*v))
}
return path, params, nil
}
// bodyFirst returns the request to fetch the first batch of results.
func (s *ScrollService) bodyFirst() (interface{}, error) {
var err error
var body interface{}
if s.body != nil {
body = s.body
} else {
// Use _doc sort by default if none is specified
if !s.ss.hasSort() {
// Use efficient sorting when no user-defined query/body is specified
s.ss = s.ss.SortBy(SortByDoc{})
}
// Body from search source
body, err = s.ss.Source()
if err != nil {
return nil, err
}
}
return body, nil
}
// -- Next --
func (s *ScrollService) next(ctx context.Context) (*SearchResult, error) {
// Get URL for request
path, params, err := s.buildNextURL()
if err != nil {
return nil, err
}
// Setup HTTP request body
body, err := s.bodyNext()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "POST",
Path: path,
Params: params,
Body: body,
Retrier: s.retrier,
Headers: s.headers,
MaxResponseSize: s.maxResponseSize,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(SearchResult)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
s.mu.Lock()
s.scrollId = ret.ScrollId
s.mu.Unlock()
if ret.Hits == nil || len(ret.Hits.Hits) == 0 {
return ret, io.EOF
}
return ret, nil
}
// buildNextURL builds the URL for the operation.
func (s *ScrollService) buildNextURL() (string, url.Values, error) {
path := "/_search/scroll"
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
// Always add "hits._scroll_id", otherwise we cannot scroll
var found bool
for _, path := range s.filterPath {
if path == "_scroll_id" {
found = true
break
}
}
if !found {
s.filterPath = append(s.filterPath, "_scroll_id")
}
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if v := s.restTotalHitsAsInt; v != nil {
params.Set("rest_total_hits_as_int", fmt.Sprint(*v))
}
return path, params, nil
}
// body returns the request to fetch the next batch of results.
func (s *ScrollService) bodyNext() (interface{}, error) {
s.mu.RLock()
body := struct {
Scroll string `json:"scroll"`
ScrollId string `json:"scroll_id,omitempty"`
}{
Scroll: s.keepAlive,
ScrollId: s.scrollId,
}
s.mu.RUnlock()
return body, nil
}
// DocvalueField adds a single field to load from the field data cache
// and return as part of the search.
func (s *ScrollService) DocvalueField(docvalueField string) *ScrollService {
s.ss = s.ss.DocvalueField(docvalueField)
return s
}
// DocvalueFieldWithFormat adds a single field to load from the field data cache
// and return as part of the search.
func (s *ScrollService) DocvalueFieldWithFormat(docvalueField DocvalueField) *ScrollService {
s.ss = s.ss.DocvalueFieldWithFormat(docvalueField)
return s
}
// DocvalueFields adds one or more fields to load from the field data cache
// and return as part of the search.
func (s *ScrollService) DocvalueFields(docvalueFields ...string) *ScrollService {
s.ss = s.ss.DocvalueFields(docvalueFields...)
return s
}
// DocvalueFieldsWithFormat adds one or more fields to load from the field data cache
// and return as part of the search.
func (s *ScrollService) DocvalueFieldsWithFormat(docvalueFields ...DocvalueField) *ScrollService {
s.ss = s.ss.DocvalueFieldsWithFormat(docvalueFields...)
return s
}
================================================
FILE: scroll_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"io"
_ "net/http"
"net/url"
"testing"
"github.com/google/go-cmp/cmp"
)
func TestScroll(t *testing.T) {
// client := setupTestClientAndCreateIndexAndLog(t)
client := setupTestClientAndCreateIndex(t)
tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
tweet2 := tweet{User: "olivere", Message: "Another unrelated topic."}
tweet3 := tweet{User: "sandrae", Message: "Cycling is fun."}
// Add all documents
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("3").BodyJson(&tweet3).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Should return all documents. Just don't call Do yet!
svc := client.Scroll(testIndexName).Size(1)
pages := 0
docs := 0
for {
res, err := svc.Do(context.TODO())
if err == io.EOF {
break
}
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected results != nil; got nil")
}
if res.Hits == nil {
t.Fatal("expected results.Hits != nil; got nil")
}
if want, have := int64(3), res.TotalHits(); want != have {
t.Fatalf("expected results.TotalHits() = %d; got %d", want, have)
}
if want, have := 1, len(res.Hits.Hits); want != have {
t.Fatalf("expected len(results.Hits.Hits) = %d; got %d", want, have)
}
pages++
for _, hit := range res.Hits.Hits {
if hit.Index != testIndexName {
t.Fatalf("expected SearchResult.Hits.Hit.Index = %q; got %q", testIndexName, hit.Index)
}
item := make(map[string]interface{})
err := json.Unmarshal(hit.Source, &item)
if err != nil {
t.Fatal(err)
}
docs++
}
if len(res.ScrollId) == 0 {
t.Fatalf("expected scrollId in results; got %q", res.ScrollId)
}
}
if want, have := 3, pages; want != have {
t.Fatalf("expected to retrieve %d pages; got %d", want, have)
}
if want, have := 3, docs; want != have {
t.Fatalf("expected to retrieve %d hits; got %d", want, have)
}
err = svc.Clear(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = svc.Do(context.TODO())
if err == nil {
t.Fatal("expected to fail")
}
}
func TestScrollWithQueryAndSort(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
// client := setupTestClientAndCreateIndexAndAddDocs(t, SetTraceLog(log.New(os.Stdout, "", log.LstdFlags)))
tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
tweet2 := tweet{User: "olivere", Message: "Another unrelated topic."}
tweet3 := tweet{User: "sandrae", Message: "Cycling is fun."}
// Add all documents
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("3").BodyJson(&tweet3).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Create a scroll service that returns tweets from user olivere
// and returns them sorted by "message", in reverse order.
//
// Just don't call Do yet!
svc := client.Scroll(testIndexName).
Query(NewTermQuery("user", "olivere")).
Sort("message", false).
Size(1).
TrackTotalHits(true)
docs := 0
pages := 0
for {
res, err := svc.Do(context.TODO())
if err == io.EOF {
break
}
if err != nil {
t.Fatal(err)
}
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected results != nil; got nil")
}
if res.Hits == nil {
t.Fatal("expected results.Hits != nil; got nil")
}
if want, have := int64(2), res.TotalHits(); want != have {
t.Fatalf("expected results.TotalHits() = %d; got %d", want, have)
}
if want, have := 1, len(res.Hits.Hits); want != have {
t.Fatalf("expected len(results.Hits.Hits) = %d; got %d", want, have)
}
pages++
for _, hit := range res.Hits.Hits {
if hit.Index != testIndexName {
t.Fatalf("expected SearchResult.Hits.Hit.Index = %q; got %q", testIndexName, hit.Index)
}
item := make(map[string]interface{})
err := json.Unmarshal(hit.Source, &item)
if err != nil {
t.Fatal(err)
}
docs++
}
}
if want, have := 2, pages; want != have {
t.Fatalf("expected to retrieve %d pages; got %d", want, have)
}
if want, have := 2, docs; want != have {
t.Fatalf("expected to retrieve %d hits; got %d", want, have)
}
}
func TestScrollWithBody(t *testing.T) {
// client := setupTestClientAndCreateIndexAndLog(t)
client := setupTestClientAndCreateIndex(t)
tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch.", Retweets: 4}
tweet2 := tweet{User: "olivere", Message: "Another unrelated topic.", Retweets: 10}
tweet3 := tweet{User: "sandrae", Message: "Cycling is fun.", Retweets: 3}
// Add all documents
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("3").BodyJson(&tweet3).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Test with simple strings and a map
var tests = []struct {
Body interface{}
ExpectedTotalHits int64
ExpectedDocs int
ExpectedPages int
}{
{
Body: `{"query":{"match_all":{}}}`,
ExpectedTotalHits: 3,
ExpectedDocs: 3,
ExpectedPages: 3,
},
{
Body: `{"query":{"term":{"user":"olivere"}},"sort":["_doc"]}`,
ExpectedTotalHits: 2,
ExpectedDocs: 2,
ExpectedPages: 2,
},
{
Body: `{"query":{"term":{"user":"olivere"}},"sort":[{"retweets":"desc"}]}`,
ExpectedTotalHits: 2,
ExpectedDocs: 2,
ExpectedPages: 2,
},
{
Body: map[string]interface{}{
"query": map[string]interface{}{
"term": map[string]interface{}{
"user": "olivere",
},
},
"sort": []interface{}{"_doc"},
},
ExpectedTotalHits: 2,
ExpectedDocs: 2,
ExpectedPages: 2,
},
}
for i, tt := range tests {
// Should return all documents. Just don't call Do yet!
svc := client.Scroll(testIndexName).Size(1).Body(tt.Body)
pages := 0
docs := 0
for {
res, err := svc.Do(context.TODO())
if err == io.EOF {
break
}
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatalf("#%d: expected results != nil; got nil", i)
}
if res.Hits == nil {
t.Fatalf("#%d: expected results.Hits != nil; got nil", i)
}
if want, have := tt.ExpectedTotalHits, res.TotalHits(); want != have {
t.Fatalf("#%d: expected results.TotalHits() = %d; got %d", i, want, have)
}
if want, have := 1, len(res.Hits.Hits); want != have {
t.Fatalf("#%d: expected len(results.Hits.Hits) = %d; got %d", i, want, have)
}
pages++
for _, hit := range res.Hits.Hits {
if hit.Index != testIndexName {
t.Fatalf("#%d: expected SearchResult.Hits.Hit.Index = %q; got %q", i, testIndexName, hit.Index)
}
item := make(map[string]interface{})
err := json.Unmarshal(hit.Source, &item)
if err != nil {
t.Fatalf("#%d: %v", i, err)
}
docs++
}
if len(res.ScrollId) == 0 {
t.Fatalf("#%d: expected scrollId in results; got %q", i, res.ScrollId)
}
}
if want, have := tt.ExpectedPages, pages; want != have {
t.Fatalf("#%d: expected to retrieve %d pages; got %d", i, want, have)
}
if want, have := tt.ExpectedDocs, docs; want != have {
t.Fatalf("#%d: expected to retrieve %d hits; got %d", i, want, have)
}
err = svc.Clear(context.TODO())
if err != nil {
t.Fatalf("#%d: failed to clear scroll context: %v", i, err)
}
_, err = svc.Do(context.TODO())
if err == nil {
t.Fatalf("#%d: expected to fail", i)
}
}
}
func TestScrollWithSlice(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t) //, SetTraceLog(log.New(os.Stdout, "", 0)))
// Should return all documents. Just don't call Do yet!
sliceQuery := NewSliceQuery().Id(0).Max(2)
svc := client.Scroll(testIndexName).Slice(sliceQuery).Size(1)
pages := 0
docs := 0
for {
res, err := svc.Do(context.TODO())
if err == io.EOF {
break
}
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected results != nil; got nil")
}
if res.Hits == nil {
t.Fatal("expected results.Hits != nil; got nil")
}
pages++
for _, hit := range res.Hits.Hits {
if hit.Index != testIndexName {
t.Fatalf("expected SearchResult.Hits.Hit.Index = %q; got %q", testIndexName, hit.Index)
}
item := make(map[string]interface{})
err := json.Unmarshal(hit.Source, &item)
if err != nil {
t.Fatal(err)
}
docs++
}
if len(res.ScrollId) == 0 {
t.Fatalf("expected scrollId in results; got %q", res.ScrollId)
}
}
if pages == 0 {
t.Fatal("expected to retrieve some pages")
}
if docs == 0 {
t.Fatal("expected to retrieve some hits")
}
if err := svc.Clear(context.TODO()); err != nil {
t.Fatal(err)
}
if _, err := svc.Do(context.TODO()); err == nil {
t.Fatal("expected to fail")
}
}
func TestScrollWithMaxResponseSize(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tweet1 := tweet{User: "sandrae", Message: "Cycling is fun.", Retweets: 3}
tweet2 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch.", Retweets: 4}
// Add all documents
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Test response size error on first scroll request (first response is 418 bytes)
svc := client.Scroll(testIndexName).Size(1).MaxResponseSize(400)
_, err = svc.Do(context.TODO())
if err != ErrResponseSize {
t.Fatalf("expected response size error")
}
// Test response size error on second scroll request (first response is 418 bytes, second is 439 bytes)
svc = client.Scroll(testIndexName).Size(1).MaxResponseSize(16384)
_, err = svc.Do(context.TODO())
if err != nil {
t.Fatal(err)
}
svc = client.Scroll(testIndexName).Size(1).MaxResponseSize(400)
_, err = svc.Do(context.TODO())
if err != ErrResponseSize {
t.Fatalf("expected response size error")
}
}
func TestScrollWithFilterPath(t *testing.T) {
// client := setupTestClientAndCreateIndexAndLog(t)
client := setupTestClientAndCreateIndex(t)
tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
tweet2 := tweet{User: "olivere", Message: "Another unrelated topic."}
tweet3 := tweet{User: "sandrae", Message: "Cycling is fun."}
// Add all documents
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("3").BodyJson(&tweet3).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Should return all documents. Just don't call Do yet!
// Notice that we don't have to add "_scroll_id" to the FilterPath here:
// It's been added automatically by the ScrollService.
svc := client.Scroll(testIndexName).Size(1).
FilterPath("hits.total", "hits.hits._index", "hits.hits._id", "hits.hits._source")
pages := 0
docs := 0
for {
res, err := svc.Do(context.TODO())
if err == io.EOF {
break
}
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected results != nil; got nil")
}
if res.Hits == nil {
t.Fatal("expected results.Hits != nil; got nil")
}
if want, have := int64(3), res.TotalHits(); want != have {
t.Fatalf("expected results.TotalHits() = %d; got %d", want, have)
}
if want, have := 1, len(res.Hits.Hits); want != have {
t.Fatalf("expected len(results.Hits.Hits) = %d; got %d", want, have)
}
pages++
for _, hit := range res.Hits.Hits {
if hit.Index != testIndexName {
t.Fatalf("expected SearchResult.Hits.Hit.Index = %q; got %q", testIndexName, hit.Index)
}
item := make(map[string]interface{})
err := json.Unmarshal(hit.Source, &item)
if err != nil {
t.Fatal(err)
}
docs++
}
if len(res.ScrollId) == 0 {
t.Fatalf("expected scrollId in results; got %q", res.ScrollId)
}
// Ensure we don't have "_scroll_id" in the query string more than once
var scrollIdCount int
for _, path := range svc.filterPath {
if path == "_scroll_id" {
scrollIdCount++
}
}
if want, have := 1, scrollIdCount; want != have {
t.Fatalf("expected _scroll_id to occur %d time(s), got %d", want, have)
}
}
if want, have := 3, pages; want != have {
t.Fatalf("expected to retrieve %d pages; got %d", want, have)
}
if want, have := 3, docs; want != have {
t.Fatalf("expected to retrieve %d hits; got %d", want, have)
}
err = svc.Clear(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = svc.Do(context.TODO())
if err == nil {
t.Fatal("expected to fail")
}
}
func TestScrollWithFilterPathKeepingContext(t *testing.T) {
// client := setupTestClientAndCreateIndexAndLog(t)
client := setupTestClientAndCreateIndex(t)
tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
tweet2 := tweet{User: "olivere", Message: "Another unrelated topic."}
tweet3 := tweet{User: "sandrae", Message: "Cycling is fun."}
// Add all documents
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("3").BodyJson(&tweet3).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Should return all documents. Just don't call Do yet!
// Notice that we don't have to add "_scroll_id" to the FilterPath here:
// It's been added automatically by the ScrollService.
svc := client.Scroll(testIndexName).Size(1).
FilterPath("hits.total", "hits.hits._index", "hits.hits._id")
pages := 0
docs := 0
for {
res, err := svc.Do(context.TODO())
if err == io.EOF {
break
}
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected results != nil; got nil")
}
if res.Hits == nil {
t.Fatal("expected results.Hits != nil; got nil")
}
if want, have := int64(3), res.TotalHits(); want != have {
t.Fatalf("expected results.TotalHits() = %d; got %d", want, have)
}
if want, have := 1, len(res.Hits.Hits); want != have {
t.Fatalf("expected len(results.Hits.Hits) = %d; got %d", want, have)
}
pages++
for _, hit := range res.Hits.Hits {
if hit.Index != testIndexName {
t.Fatalf("expected SearchResult.Hits.Hit.Index = %q; got %q", testIndexName, hit.Index)
}
if hit.Source != nil {
t.Fatal("expected SearchResult.Hits.Hit.Source = nil")
}
docs++
}
if len(res.ScrollId) == 0 {
t.Fatalf("expected scrollId in results; got %q", res.ScrollId)
}
}
if want, have := 3, pages; want != have {
t.Fatalf("expected to retrieve %d pages; got %d", want, have)
}
if want, have := 3, docs; want != have {
t.Fatalf("expected to retrieve %d hits; got %d", want, have)
}
err = svc.Clear(context.TODO())
if err != nil && !IsNotFound(err) {
t.Fatal(err)
}
_, err = svc.Do(context.TODO())
if err == nil {
t.Fatal("expected to fail")
}
}
func TestScrollTotalHits(t *testing.T) {
//client := setupTestClientAndCreateIndexAndLog(t)
client := setupTestClientAndCreateIndex(t)
tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
tweet2 := tweet{User: "olivere", Message: "Another unrelated topic."}
tweet3 := tweet{User: "sandrae", Message: "Cycling is fun."}
// Add all documents
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("3").BodyJson(&tweet3).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Should return all documents with ES 6.x compatible total hits. Just don't call Do yet!
svc := client.Scroll(testIndexName).Size(1).RestTotalHitsAsInt(true)
pages := 0
docs := 0
for {
res, err := svc.Do(context.TODO())
if err == io.EOF {
break
}
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected results != nil; got nil")
}
if res.Hits == nil {
t.Fatal("expected results.Hits != nil; got nil")
}
if want, have := int64(3), res.TotalHits(); want != have {
t.Fatalf("expected results.TotalHits() = %d; got %d", want, have)
}
if want, have := 1, len(res.Hits.Hits); want != have {
t.Fatalf("expected len(results.Hits.Hits) = %d; got %d", want, have)
}
pages++
for _, hit := range res.Hits.Hits {
if hit.Index != testIndexName {
t.Fatalf("expected SearchResult.Hits.Hit.Index = %q; got %q", testIndexName, hit.Index)
}
item := make(map[string]interface{})
err := json.Unmarshal(hit.Source, &item)
if err != nil {
t.Fatal(err)
}
docs++
}
if len(res.ScrollId) == 0 {
t.Fatalf("expected scrollId in results; got %q", res.ScrollId)
}
}
if want, have := 3, pages; want != have {
t.Fatalf("expected to retrieve %d pages; got %d", want, have)
}
if want, have := 3, docs; want != have {
t.Fatalf("expected to retrieve %d hits; got %d", want, have)
}
err = svc.Clear(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = svc.Do(context.TODO())
if err == nil {
t.Fatal("expected to fail")
}
}
func TestScrollBuilder(t *testing.T) {
// client := setupTestClientAndCreateIndexAndLog(t)
client := setupTestClientAndCreateIndex(t)
tests := []struct {
Service *ScrollService
ExpectedPath string
ExpectedParams url.Values
ExpectedBody string
}{
// #0: FetchSourceContext in ScrollService
{
Service: client.
Scroll(testIndexName).
SearchSource(
NewSearchSource().Query(NewMatchAllQuery()),
).
FetchSourceContext(NewFetchSourceContext(false).Include("foo")).
Size(600),
ExpectedPath: "/elastic-test/_search",
ExpectedParams: url.Values{
"scroll": []string{"5m"},
"size": []string{"600"},
},
ExpectedBody: `{"_source":false,"query":{"match_all":{}},"sort":["_doc"]}`,
},
// #1: FetchSourceContext in SearchSource
{
Service: client.Scroll(testIndexName).
SearchSource(
NewSearchSource().Query(
NewMatchAllQuery(),
).
FetchSourceContext(NewFetchSourceContext(false).Include("foo")),
).
Size(600),
ExpectedPath: "/elastic-test/_search",
ExpectedParams: url.Values{
"scroll": []string{"5m"},
"size": []string{"600"},
},
ExpectedBody: `{"_source":false,"query":{"match_all":{}},"sort":["_doc"]}`,
},
}
for i, tt := range tests {
// Get URL and parameters for request
path, params, err := tt.Service.buildFirstURL()
if err != nil {
t.Fatalf("#%d: %v", i, err)
}
if want, have := tt.ExpectedPath, path; want != have {
t.Fatalf("#%d: want Path=%q, have %q", i, want, have)
}
if want, have := tt.ExpectedParams, params; !cmp.Equal(want, have) {
t.Fatalf("#%d: invalid Params; expected:\ndiff=%v", i, cmp.Diff(want, have))
}
// Get HTTP request body
body, err := tt.Service.bodyFirst()
if err != nil {
t.Fatal(err)
}
js, err := json.Marshal(body)
if err != nil {
t.Fatal(err)
}
if want, have := tt.ExpectedBody, string(js); !cmp.Equal(want, have) {
t.Fatalf("#%d: want Body=%s, have %s\ndiff=%v", i, want, have, cmp.Diff(want, have))
}
}
}
================================================
FILE: search.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"reflect"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// Search for documents in Elasticsearch.
type SearchService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
searchSource *SearchSource // q
source interface{}
searchType string // search_type
index []string
typ []string
routing string // routing
preference string // preference
requestCache *bool // request_cache
ignoreUnavailable *bool // ignore_unavailable
ignoreThrottled *bool // ignore_throttled
allowNoIndices *bool // allow_no_indices
expandWildcards string // expand_wildcards
lenient *bool // lenient
maxResponseSize int64
allowPartialSearchResults *bool // allow_partial_search_results
typedKeys *bool // typed_keys
seqNoPrimaryTerm *bool // seq_no_primary_term
batchedReduceSize *int // batched_reduce_size
maxConcurrentShardRequests *int // max_concurrent_shard_requests
preFilterShardSize *int // pre_filter_shard_size
restTotalHitsAsInt *bool // rest_total_hits_as_int
ccsMinimizeRoundtrips *bool // ccs_minimize_roundtrips
}
// NewSearchService creates a new service for searching in Elasticsearch.
func NewSearchService(client *Client) *SearchService {
builder := &SearchService{
client: client,
searchSource: NewSearchSource(),
}
return builder
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *SearchService) Pretty(pretty bool) *SearchService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *SearchService) Human(human bool) *SearchService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *SearchService) ErrorTrace(errorTrace bool) *SearchService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *SearchService) FilterPath(filterPath ...string) *SearchService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *SearchService) Header(name string, value string) *SearchService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *SearchService) Headers(headers http.Header) *SearchService {
s.headers = headers
return s
}
// SearchSource sets the search source builder to use with this service.
func (s *SearchService) SearchSource(searchSource *SearchSource) *SearchService {
s.searchSource = searchSource
if s.searchSource == nil {
s.searchSource = NewSearchSource()
}
return s
}
// Source allows the user to set the request body manually without using
// any of the structs and interfaces in Elastic.
func (s *SearchService) Source(source interface{}) *SearchService {
s.source = source
return s
}
// Index sets the names of the indices to use for search.
func (s *SearchService) Index(index ...string) *SearchService {
s.index = append(s.index, index...)
return s
}
// Type adds search restrictions for a list of types.
//
// Deprecated: Types are in the process of being removed. Instead of using a type, prefer to
// filter on a field on the document.
func (s *SearchService) Type(typ ...string) *SearchService {
s.typ = append(s.typ, typ...)
return s
}
// Timeout sets the timeout to use, e.g. "1s" or "1000ms".
func (s *SearchService) Timeout(timeout string) *SearchService {
s.searchSource = s.searchSource.Timeout(timeout)
return s
}
// Profile sets the Profile API flag on the search source.
// When enabled, a search executed by this service will return query
// profiling data.
func (s *SearchService) Profile(profile bool) *SearchService {
s.searchSource = s.searchSource.Profile(profile)
return s
}
// Collapse adds field collapsing.
func (s *SearchService) Collapse(collapse *CollapseBuilder) *SearchService {
s.searchSource = s.searchSource.Collapse(collapse)
return s
}
// PointInTime specifies an optional PointInTime to be used in the context
// of this search.
func (s *SearchService) PointInTime(pointInTime *PointInTime) *SearchService {
s.searchSource = s.searchSource.PointInTime(pointInTime)
return s
}
// RuntimeMappings specifies optional runtime mappings.
func (s *SearchService) RuntimeMappings(runtimeMappings RuntimeMappings) *SearchService {
s.searchSource = s.searchSource.RuntimeMappings(runtimeMappings)
return s
}
// TimeoutInMillis sets the timeout in milliseconds.
func (s *SearchService) TimeoutInMillis(timeoutInMillis int) *SearchService {
s.searchSource = s.searchSource.TimeoutInMillis(timeoutInMillis)
return s
}
// TerminateAfter specifies the maximum number of documents to collect for
// each shard, upon reaching which the query execution will terminate early.
func (s *SearchService) TerminateAfter(terminateAfter int) *SearchService {
s.searchSource = s.searchSource.TerminateAfter(terminateAfter)
return s
}
// SearchType sets the search operation type. Valid values are:
// "dfs_query_then_fetch" and "query_then_fetch".
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-request-search-type.html
// for details.
func (s *SearchService) SearchType(searchType string) *SearchService {
s.searchType = searchType
return s
}
// Routing is a list of specific routing values to control the shards
// the search will be executed on.
func (s *SearchService) Routing(routings ...string) *SearchService {
s.routing = strings.Join(routings, ",")
return s
}
// Preference sets the preference to execute the search. Defaults to
// randomize across shards ("random"). Can be set to "_local" to prefer
// local shards, "_primary" to execute on primary shards only,
// or a custom value which guarantees that the same order will be used
// across different requests.
func (s *SearchService) Preference(preference string) *SearchService {
s.preference = preference
return s
}
// RequestCache indicates whether the cache should be used for this
// request or not, defaults to index level setting.
func (s *SearchService) RequestCache(requestCache bool) *SearchService {
s.requestCache = &requestCache
return s
}
// Query sets the query to perform, e.g. MatchAllQuery.
func (s *SearchService) Query(query Query) *SearchService {
s.searchSource = s.searchSource.Query(query)
return s
}
// PostFilter will be executed after the query has been executed and
// only affects the search hits, not the aggregations.
// This filter is always executed as the last filtering mechanism.
func (s *SearchService) PostFilter(postFilter Query) *SearchService {
s.searchSource = s.searchSource.PostFilter(postFilter)
return s
}
// FetchSource indicates whether the response should contain the stored
// _source for every hit.
func (s *SearchService) FetchSource(fetchSource bool) *SearchService {
s.searchSource = s.searchSource.FetchSource(fetchSource)
return s
}
// FetchSourceContext indicates how the _source should be fetched.
func (s *SearchService) FetchSourceContext(fetchSourceContext *FetchSourceContext) *SearchService {
s.searchSource = s.searchSource.FetchSourceContext(fetchSourceContext)
return s
}
// Highlight adds highlighting to the search.
func (s *SearchService) Highlight(highlight *Highlight) *SearchService {
s.searchSource = s.searchSource.Highlight(highlight)
return s
}
// GlobalSuggestText defines the global text to use with all suggesters.
// This avoids repetition.
func (s *SearchService) GlobalSuggestText(globalText string) *SearchService {
s.searchSource = s.searchSource.GlobalSuggestText(globalText)
return s
}
// Suggester adds a suggester to the search.
func (s *SearchService) Suggester(suggester Suggester) *SearchService {
s.searchSource = s.searchSource.Suggester(suggester)
return s
}
// Aggregation adds an aggreation to perform as part of the search.
func (s *SearchService) Aggregation(name string, aggregation Aggregation) *SearchService {
s.searchSource = s.searchSource.Aggregation(name, aggregation)
return s
}
// MinScore sets the minimum score below which docs will be filtered out.
func (s *SearchService) MinScore(minScore float64) *SearchService {
s.searchSource = s.searchSource.MinScore(minScore)
return s
}
// From index to start the search from. Defaults to 0.
func (s *SearchService) From(from int) *SearchService {
s.searchSource = s.searchSource.From(from)
return s
}
// Size is the number of search hits to return. Defaults to 10.
func (s *SearchService) Size(size int) *SearchService {
s.searchSource = s.searchSource.Size(size)
return s
}
// Explain indicates whether each search hit should be returned with
// an explanation of the hit (ranking).
func (s *SearchService) Explain(explain bool) *SearchService {
s.searchSource = s.searchSource.Explain(explain)
return s
}
// Version indicates whether each search hit should be returned with
// a version associated to it.
func (s *SearchService) Version(version bool) *SearchService {
s.searchSource = s.searchSource.Version(version)
return s
}
// Sort adds a sort order.
func (s *SearchService) Sort(field string, ascending bool) *SearchService {
s.searchSource = s.searchSource.Sort(field, ascending)
return s
}
// SortWithInfo adds a sort order.
func (s *SearchService) SortWithInfo(info SortInfo) *SearchService {
s.searchSource = s.searchSource.SortWithInfo(info)
return s
}
// SortBy adds a sort order.
func (s *SearchService) SortBy(sorter ...Sorter) *SearchService {
s.searchSource = s.searchSource.SortBy(sorter...)
return s
}
// DocvalueField adds a single field to load from the field data cache
// and return as part of the search.
func (s *SearchService) DocvalueField(docvalueField string) *SearchService {
s.searchSource = s.searchSource.DocvalueField(docvalueField)
return s
}
// DocvalueFieldWithFormat adds a single field to load from the field data cache
// and return as part of the search.
func (s *SearchService) DocvalueFieldWithFormat(docvalueField DocvalueField) *SearchService {
s.searchSource = s.searchSource.DocvalueFieldWithFormat(docvalueField)
return s
}
// DocvalueFields adds one or more fields to load from the field data cache
// and return as part of the search.
func (s *SearchService) DocvalueFields(docvalueFields ...string) *SearchService {
s.searchSource = s.searchSource.DocvalueFields(docvalueFields...)
return s
}
// DocvalueFieldsWithFormat adds one or more fields to load from the field data cache
// and return as part of the search.
func (s *SearchService) DocvalueFieldsWithFormat(docvalueFields ...DocvalueField) *SearchService {
s.searchSource = s.searchSource.DocvalueFieldsWithFormat(docvalueFields...)
return s
}
// NoStoredFields indicates that no stored fields should be loaded, resulting in only
// id and type to be returned per field.
func (s *SearchService) NoStoredFields() *SearchService {
s.searchSource = s.searchSource.NoStoredFields()
return s
}
// StoredField adds a single field to load and return (note, must be stored) as
// part of the search request. If none are specified, the source of the
// document will be returned.
func (s *SearchService) StoredField(fieldName string) *SearchService {
s.searchSource = s.searchSource.StoredField(fieldName)
return s
}
// StoredFields sets the fields to load and return as part of the search request.
// If none are specified, the source of the document will be returned.
func (s *SearchService) StoredFields(fields ...string) *SearchService {
s.searchSource = s.searchSource.StoredFields(fields...)
return s
}
// TrackScores is applied when sorting and controls if scores will be
// tracked as well. Defaults to false.
func (s *SearchService) TrackScores(trackScores bool) *SearchService {
s.searchSource = s.searchSource.TrackScores(trackScores)
return s
}
// TrackTotalHits controls if the total hit count for the query should be tracked.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.1/search-request-track-total-hits.html
// for details.
func (s *SearchService) TrackTotalHits(trackTotalHits interface{}) *SearchService {
s.searchSource = s.searchSource.TrackTotalHits(trackTotalHits)
return s
}
// SearchAfter allows a different form of pagination by using a live cursor,
// using the results of the previous page to help the retrieval of the next.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-request-search-after.html
func (s *SearchService) SearchAfter(sortValues ...interface{}) *SearchService {
s.searchSource = s.searchSource.SearchAfter(sortValues...)
return s
}
// DefaultRescoreWindowSize sets the rescore window size for rescores
// that don't specify their window.
func (s *SearchService) DefaultRescoreWindowSize(defaultRescoreWindowSize int) *SearchService {
s.searchSource = s.searchSource.DefaultRescoreWindowSize(defaultRescoreWindowSize)
return s
}
// Rescorer adds a rescorer to the search.
func (s *SearchService) Rescorer(rescore *Rescore) *SearchService {
s.searchSource = s.searchSource.Rescorer(rescore)
return s
}
// IgnoreUnavailable indicates whether the specified concrete indices
// should be ignored when unavailable (missing or closed).
func (s *SearchService) IgnoreUnavailable(ignoreUnavailable bool) *SearchService {
s.ignoreUnavailable = &ignoreUnavailable
return s
}
// IgnoreThrottled indicates whether specified concrete, expanded or aliased
// indices should be ignored when throttled.
func (s *SearchService) IgnoreThrottled(ignoreThrottled bool) *SearchService {
s.ignoreThrottled = &ignoreThrottled
return s
}
// AllowNoIndices indicates whether to ignore if a wildcard indices
// expression resolves into no concrete indices. (This includes `_all` string
// or when no indices have been specified).
func (s *SearchService) AllowNoIndices(allowNoIndices bool) *SearchService {
s.allowNoIndices = &allowNoIndices
return s
}
// ExpandWildcards indicates whether to expand wildcard expression to
// concrete indices that are open, closed or both.
func (s *SearchService) ExpandWildcards(expandWildcards string) *SearchService {
s.expandWildcards = expandWildcards
return s
}
// Lenient specifies whether format-based query failures (such as providing
// text to a numeric field) should be ignored.
func (s *SearchService) Lenient(lenient bool) *SearchService {
s.lenient = &lenient
return s
}
// MaxResponseSize sets an upper limit on the response body size that we accept,
// to guard against OOM situations.
func (s *SearchService) MaxResponseSize(maxResponseSize int64) *SearchService {
s.maxResponseSize = maxResponseSize
return s
}
// AllowPartialSearchResults indicates if an error should be returned if
// there is a partial search failure or timeout.
func (s *SearchService) AllowPartialSearchResults(enabled bool) *SearchService {
s.allowPartialSearchResults = &enabled
return s
}
// TypedKeys specifies whether aggregation and suggester names should be
// prefixed by their respective types in the response.
func (s *SearchService) TypedKeys(enabled bool) *SearchService {
s.typedKeys = &enabled
return s
}
// SeqNoPrimaryTerm is an alias for SeqNoAndPrimaryTerm.
//
// Deprecated: Use SeqNoAndPrimaryTerm.
func (s *SearchService) SeqNoPrimaryTerm(enabled bool) *SearchService {
return s.SeqNoAndPrimaryTerm(enabled)
}
// SeqNoAndPrimaryTerm specifies whether to return sequence number and
// primary term of the last modification of each hit.
func (s *SearchService) SeqNoAndPrimaryTerm(enabled bool) *SearchService {
s.seqNoPrimaryTerm = &enabled
return s
}
// BatchedReduceSize specifies the number of shard results that should be reduced
// at once on the coordinating node. This value should be used as a protection
// mechanism to reduce the memory overhead per search request if the potential
// number of shards in the request can be large.
func (s *SearchService) BatchedReduceSize(size int) *SearchService {
s.batchedReduceSize = &size
return s
}
// MaxConcurrentShardRequests specifies the number of concurrent shard requests
// this search executes concurrently. This value should be used to limit the
// impact of the search on the cluster in order to limit the number of
// concurrent shard requests.
func (s *SearchService) MaxConcurrentShardRequests(max int) *SearchService {
s.maxConcurrentShardRequests = &max
return s
}
// PreFilterShardSize specifies a threshold that enforces a pre-filter roundtrip
// to prefilter search shards based on query rewriting if the number of shards
// the search request expands to exceeds the threshold. This filter roundtrip
// can limit the number of shards significantly if for instance a shard can
// not match any documents based on it's rewrite method i.e. if date filters are
// mandatory to match but the shard bounds and the query are disjoint.
func (s *SearchService) PreFilterShardSize(threshold int) *SearchService {
s.preFilterShardSize = &threshold
return s
}
// RestTotalHitsAsInt indicates whether hits.total should be rendered as an
// integer or an object in the rest search response.
func (s *SearchService) RestTotalHitsAsInt(enabled bool) *SearchService {
s.restTotalHitsAsInt = &enabled
return s
}
// CCSMinimizeRoundtrips indicates whether network round-trips should be minimized
// as part of cross-cluster search requests execution.
func (s *SearchService) CCSMinimizeRoundtrips(enabled bool) *SearchService {
s.ccsMinimizeRoundtrips = &enabled
return s
}
// buildURL builds the URL for the operation.
func (s *SearchService) buildURL() (string, url.Values, error) {
var err error
var path string
if len(s.index) > 0 && len(s.typ) > 0 {
path, err = uritemplates.Expand("/{index}/{type}/_search", map[string]string{
"index": strings.Join(s.index, ","),
"type": strings.Join(s.typ, ","),
})
} else if len(s.index) > 0 {
path, err = uritemplates.Expand("/{index}/_search", map[string]string{
"index": strings.Join(s.index, ","),
})
} else if len(s.typ) > 0 {
path, err = uritemplates.Expand("/_all/{type}/_search", map[string]string{
"type": strings.Join(s.typ, ","),
})
} else {
path = "/_search"
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.searchType != "" {
params.Set("search_type", s.searchType)
}
if s.routing != "" {
params.Set("routing", s.routing)
}
if s.preference != "" {
params.Set("preference", s.preference)
}
if v := s.requestCache; v != nil {
params.Set("request_cache", fmt.Sprint(*v))
}
if v := s.allowNoIndices; v != nil {
params.Set("allow_no_indices", fmt.Sprint(*v))
}
if s.expandWildcards != "" {
params.Set("expand_wildcards", s.expandWildcards)
}
if v := s.lenient; v != nil {
params.Set("lenient", fmt.Sprint(*v))
}
if v := s.ignoreUnavailable; v != nil {
params.Set("ignore_unavailable", fmt.Sprint(*v))
}
if v := s.ignoreThrottled; v != nil {
params.Set("ignore_throttled", fmt.Sprint(*v))
}
if s.seqNoPrimaryTerm != nil {
params.Set("seq_no_primary_term", fmt.Sprint(*s.seqNoPrimaryTerm))
}
if v := s.allowPartialSearchResults; v != nil {
params.Set("allow_partial_search_results", fmt.Sprint(*v))
}
if v := s.typedKeys; v != nil {
params.Set("typed_keys", fmt.Sprint(*v))
}
if v := s.batchedReduceSize; v != nil {
params.Set("batched_reduce_size", fmt.Sprint(*v))
}
if v := s.maxConcurrentShardRequests; v != nil {
params.Set("max_concurrent_shard_requests", fmt.Sprint(*v))
}
if v := s.preFilterShardSize; v != nil {
params.Set("pre_filter_shard_size", fmt.Sprint(*v))
}
if v := s.restTotalHitsAsInt; v != nil {
params.Set("rest_total_hits_as_int", fmt.Sprint(*v))
}
if v := s.ccsMinimizeRoundtrips; v != nil {
params.Set("ccs_minimize_roundtrips", fmt.Sprint(*v))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *SearchService) Validate() error {
return nil
}
// Do executes the search and returns a SearchResult.
func (s *SearchService) Do(ctx context.Context) (*SearchResult, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Perform request
var body interface{}
if s.source != nil {
body = s.source
} else {
src, err := s.searchSource.Source()
if err != nil {
return nil, err
}
body = src
}
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "POST",
Path: path,
Params: params,
Body: body,
Headers: s.headers,
MaxResponseSize: s.maxResponseSize,
})
if err != nil {
return nil, err
}
// Return search results
ret := new(SearchResult)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
ret.Header = res.Header
return nil, err
}
ret.Header = res.Header
return ret, nil
}
// SearchResult is the result of a search in Elasticsearch.
type SearchResult struct {
Header http.Header `json:"-"`
TookInMillis int64 `json:"took,omitempty"` // search time in milliseconds
TerminatedEarly bool `json:"terminated_early,omitempty"` // request terminated early
NumReducePhases int `json:"num_reduce_phases,omitempty"`
Clusters *SearchResultCluster `json:"_clusters,omitempty"` // 6.1.0+
ScrollId string `json:"_scroll_id,omitempty"` // only used with Scroll and Scan operations
Hits *SearchHits `json:"hits,omitempty"` // the actual search hits
Suggest SearchSuggest `json:"suggest,omitempty"` // results from suggesters
Aggregations Aggregations `json:"aggregations,omitempty"` // results from aggregations
TimedOut bool `json:"timed_out,omitempty"` // true if the search timed out
Error *ErrorDetails `json:"error,omitempty"` // only used in MultiGet
Profile *SearchProfile `json:"profile,omitempty"` // profiling results, if optional Profile API was active for this search
Shards *ShardsInfo `json:"_shards,omitempty"` // shard information
Status int `json:"status,omitempty"` // used in MultiSearch
PitId string `json:"pit_id,omitempty"` // Point In Time ID
}
// SearchResultCluster holds information about a search response
// from a cluster.
type SearchResultCluster struct {
Successful int `json:"successful,omitempty"`
Total int `json:"total,omitempty"`
Skipped int `json:"skipped,omitempty"`
}
// TotalHits is a convenience function to return the number of hits for
// a search result. The return value might not be accurate, unless
// track_total_hits parameter has set to true.
func (r *SearchResult) TotalHits() int64 {
if r != nil && r.Hits != nil && r.Hits.TotalHits != nil {
return r.Hits.TotalHits.Value
}
return 0
}
// Each is a utility function to iterate over all hits. It saves you from
// checking for nil values. Notice that Each will ignore errors in
// serializing JSON and hits with empty/nil _source will get an empty
// value
func (r *SearchResult) Each(typ reflect.Type) []interface{} {
if r.Hits == nil || r.Hits.Hits == nil || len(r.Hits.Hits) == 0 {
return nil
}
slice := make([]interface{}, 0, len(r.Hits.Hits))
for _, hit := range r.Hits.Hits {
v := reflect.New(typ).Elem()
if hit.Source == nil {
slice = append(slice, v.Interface())
continue
}
if err := json.Unmarshal(hit.Source, v.Addr().Interface()); err == nil {
slice = append(slice, v.Interface())
}
}
return slice
}
// SearchHits specifies the list of search hits.
type SearchHits struct {
TotalHits *TotalHits `json:"total,omitempty"` // total number of hits found
MaxScore *float64 `json:"max_score,omitempty"` // maximum score of all hits
Hits []*SearchHit `json:"hits,omitempty"` // the actual hits returned
}
// NestedHit is a nested innerhit
type NestedHit struct {
Field string `json:"field"`
Offset int `json:"offset,omitempty"`
Child *NestedHit `json:"_nested,omitempty"`
}
// TotalHits specifies total number of hits and its relation
type TotalHits struct {
Value int64 `json:"value"` // value of the total hit count
Relation string `json:"relation"` // how the value should be interpreted: accurate ("eq") or a lower bound ("gte")
}
// UnmarshalJSON into TotalHits, accepting both the new response structure
// in ES 7.x as well as the older response structure in earlier versions.
// The latter can be enabled with RestTotalHitsAsInt(true).
func (h *TotalHits) UnmarshalJSON(data []byte) error {
if data == nil || string(data) == "null" {
return nil
}
var v struct {
Value int64 `json:"value"` // value of the total hit count
Relation string `json:"relation"` // how the value should be interpreted: accurate ("eq") or a lower bound ("gte")
}
if err := json.Unmarshal(data, &v); err != nil {
var count int64
if err2 := json.Unmarshal(data, &count); err2 != nil {
return err // return inner error
}
h.Value = count
h.Relation = "eq"
return nil
}
*h = v
return nil
}
// SearchHit is a single hit.
type SearchHit struct {
Score *float64 `json:"_score,omitempty"` // computed score
Index string `json:"_index,omitempty"` // index name
Type string `json:"_type,omitempty"` // type meta field
Id string `json:"_id,omitempty"` // external or internal
Uid string `json:"_uid,omitempty"` // uid meta field (see MapperService.java for all meta fields)
Routing string `json:"_routing,omitempty"` // routing meta field
Parent string `json:"_parent,omitempty"` // parent meta field
Version *int64 `json:"_version,omitempty"` // version number, when Version is set to true in SearchService
SeqNo *int64 `json:"_seq_no"`
PrimaryTerm *int64 `json:"_primary_term"`
Sort []interface{} `json:"sort,omitempty"` // sort information
Highlight SearchHitHighlight `json:"highlight,omitempty"` // highlighter information
Source json.RawMessage `json:"_source,omitempty"` // stored document source
Fields SearchHitFields `json:"fields,omitempty"` // returned (stored) fields
Explanation *SearchExplanation `json:"_explanation,omitempty"` // explains how the score was computed
MatchedQueries []string `json:"matched_queries,omitempty"` // matched queries
InnerHits map[string]*SearchHitInnerHits `json:"inner_hits,omitempty"` // inner hits with ES >= 1.5.0
Nested *NestedHit `json:"_nested,omitempty"` // for nested inner hits
Shard string `json:"_shard,omitempty"` // used e.g. in Search Explain
Node string `json:"_node,omitempty"` // used e.g. in Search Explain
// HighlightFields
// SortValues
// MatchedFilters
}
// SearchHitFields helps to simplify resolving slices of specific types.
type SearchHitFields map[string]interface{}
// Strings returns a slice of strings for the given field, if there is any
// such field in the hit. The method ignores elements that are not of type
// string.
func (f SearchHitFields) Strings(fieldName string) ([]string, bool) {
slice, ok := f[fieldName].([]interface{})
if !ok {
return nil, false
}
results := make([]string, 0, len(slice))
for _, item := range slice {
if v, ok := item.(string); ok {
results = append(results, v)
}
}
return results, true
}
// Float64s returns a slice of float64's for the given field, if there is any
// such field in the hit. The method ignores elements that are not of
// type float64.
func (f SearchHitFields) Float64s(fieldName string) ([]float64, bool) {
slice, ok := f[fieldName].([]interface{})
if !ok {
return nil, false
}
results := make([]float64, 0, len(slice))
for _, item := range slice {
if v, ok := item.(float64); ok {
results = append(results, v)
}
}
return results, true
}
// SearchHitInnerHits is used for inner hits.
type SearchHitInnerHits struct {
Hits *SearchHits `json:"hits,omitempty"`
}
// SearchExplanation explains how the score for a hit was computed.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-request-explain.html.
type SearchExplanation struct {
Value float64 `json:"value"` // e.g. 1.0
Description string `json:"description"` // e.g. "boost" or "ConstantScore(*:*), product of:"
Details []SearchExplanation `json:"details,omitempty"` // recursive details
}
// Suggest
// SearchSuggest is a map of suggestions.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-suggesters.html.
type SearchSuggest map[string][]SearchSuggestion
// SearchSuggestion is a single search suggestion.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-suggesters.html.
type SearchSuggestion struct {
Text string `json:"text"`
Offset int `json:"offset"`
Length int `json:"length"`
Options []SearchSuggestionOption `json:"options"`
}
// SearchSuggestionOption is an option of a SearchSuggestion.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-suggesters.html.
type SearchSuggestionOption struct {
Text string `json:"text"`
Index string `json:"_index"`
Type string `json:"_type"`
Id string `json:"_id"`
Score float64 `json:"score"` // term and phrase suggesters uses "score" as of 6.2.4
ScoreUnderscore float64 `json:"_score"` // completion and context suggesters uses "_score" as of 6.2.4
Highlighted string `json:"highlighted"`
CollateMatch bool `json:"collate_match"`
Freq int `json:"freq"` // from TermSuggestion.Option in Java API
Source json.RawMessage `json:"_source"`
Contexts map[string][]string `json:"contexts,omitempty"`
}
// SearchProfile is a list of shard profiling data collected during
// query execution in the "profile" section of a SearchResult
type SearchProfile struct {
Shards []SearchProfileShardResult `json:"shards"`
}
// SearchProfileShardResult returns the profiling data for a single shard
// accessed during the search query or aggregation.
type SearchProfileShardResult struct {
ID string `json:"id"`
Searches []QueryProfileShardResult `json:"searches"`
Aggregations []ProfileResult `json:"aggregations"`
Fetch *ProfileResult `json:"fetch"`
}
// QueryProfileShardResult is a container class to hold the profile results
// for a single shard in the request. It comtains a list of query profiles,
// a collector tree and a total rewrite tree.
type QueryProfileShardResult struct {
Query []ProfileResult `json:"query,omitempty"`
RewriteTime int64 `json:"rewrite_time,omitempty"`
Collector []interface{} `json:"collector,omitempty"`
}
// CollectorResult holds the profile timings of the collectors used in the
// search. Children's CollectorResults may be embedded inside of a parent
// CollectorResult.
type CollectorResult struct {
Name string `json:"name,omitempty"`
Reason string `json:"reason,omitempty"`
Time string `json:"time,omitempty"`
TimeNanos int64 `json:"time_in_nanos,omitempty"`
Children []CollectorResult `json:"children,omitempty"`
}
// ProfileResult is the internal representation of a profiled query,
// corresponding to a single node in the query tree.
type ProfileResult struct {
Type string `json:"type"`
Description string `json:"description,omitempty"`
NodeTime string `json:"time,omitempty"`
NodeTimeNanos int64 `json:"time_in_nanos,omitempty"`
Breakdown map[string]int64 `json:"breakdown,omitempty"`
Children []ProfileResult `json:"children,omitempty"`
Debug map[string]interface{} `json:"debug,omitempty"`
}
// Aggregations (see search_aggs.go)
// Highlighting
// SearchHitHighlight is the highlight information of a search hit.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-request-highlighting.html
// for a general discussion of highlighting.
type SearchHitHighlight map[string][]string
================================================
FILE: search_aggs.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"bytes"
"encoding/json"
)
// Aggregations can be seen as a unit-of-work that build
// analytic information over a set of documents. It is
// (in many senses) the follow-up of facets in Elasticsearch.
// For more details about aggregations, visit:
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations.html
type Aggregation interface {
// Source returns a JSON-serializable aggregation that is a fragment
// of the request sent to Elasticsearch.
Source() (interface{}, error)
}
// Aggregations is a list of aggregations that are part of a search result.
type Aggregations map[string]json.RawMessage
// Min returns min aggregation results.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-metrics-min-aggregation.html
func (a Aggregations) Min(name string) (*AggregationValueMetric, bool) {
if raw, found := a[name]; found {
agg := new(AggregationValueMetric)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// Max returns max aggregation results.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-metrics-max-aggregation.html
func (a Aggregations) Max(name string) (*AggregationValueMetric, bool) {
if raw, found := a[name]; found {
agg := new(AggregationValueMetric)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// Sum returns sum aggregation results.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-metrics-sum-aggregation.html
func (a Aggregations) Sum(name string) (*AggregationValueMetric, bool) {
if raw, found := a[name]; found {
agg := new(AggregationValueMetric)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// Avg returns average aggregation results.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-metrics-avg-aggregation.html
func (a Aggregations) Avg(name string) (*AggregationValueMetric, bool) {
if raw, found := a[name]; found {
agg := new(AggregationValueMetric)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// WeightedAvg computes the weighted average of numeric values that are extracted from the aggregated documents.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-metrics-weight-avg-aggregation.html
func (a Aggregations) WeightedAvg(name string) (*AggregationValueMetric, bool) {
if raw, found := a[name]; found {
agg := new(AggregationValueMetric)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// MedianAbsoluteDeviation returns median absolute deviation aggregation results.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.6/search-aggregations-metrics-median-absolute-deviation-aggregation.html
// for details.
func (a Aggregations) MedianAbsoluteDeviation(name string) (*AggregationValueMetric, bool) {
if raw, found := a[name]; found {
agg := new(AggregationValueMetric)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// ValueCount returns value-count aggregation results.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-metrics-valuecount-aggregation.html
func (a Aggregations) ValueCount(name string) (*AggregationValueMetric, bool) {
if raw, found := a[name]; found {
agg := new(AggregationValueMetric)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// Cardinality returns cardinality aggregation results.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-metrics-cardinality-aggregation.html
func (a Aggregations) Cardinality(name string) (*AggregationValueMetric, bool) {
if raw, found := a[name]; found {
agg := new(AggregationValueMetric)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// Stats returns stats aggregation results.
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-metrics-stats-aggregation.html
func (a Aggregations) Stats(name string) (*AggregationStatsMetric, bool) {
if raw, found := a[name]; found {
agg := new(AggregationStatsMetric)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// ExtendedStats returns extended stats aggregation results.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-metrics-extendedstats-aggregation.html
func (a Aggregations) ExtendedStats(name string) (*AggregationExtendedStatsMetric, bool) {
if raw, found := a[name]; found {
agg := new(AggregationExtendedStatsMetric)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// MatrixStats returns matrix stats aggregation results.
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-matrix-stats-aggregation.html
func (a Aggregations) MatrixStats(name string) (*AggregationMatrixStats, bool) {
if raw, found := a[name]; found {
agg := new(AggregationMatrixStats)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// Percentiles returns percentiles results.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-metrics-percentile-aggregation.html
func (a Aggregations) Percentiles(name string) (*AggregationPercentilesMetric, bool) {
if raw, found := a[name]; found {
agg := new(AggregationPercentilesMetric)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// PercentileRanks returns percentile ranks results.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-metrics-percentile-rank-aggregation.html
func (a Aggregations) PercentileRanks(name string) (*AggregationPercentilesMetric, bool) {
if raw, found := a[name]; found {
agg := new(AggregationPercentilesMetric)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// TopHits returns top-hits aggregation results.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-metrics-top-hits-aggregation.html
func (a Aggregations) TopHits(name string) (*AggregationTopHitsMetric, bool) {
if raw, found := a[name]; found {
agg := new(AggregationTopHitsMetric)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// Global returns global results.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-global-aggregation.html
func (a Aggregations) Global(name string) (*AggregationSingleBucket, bool) {
if raw, found := a[name]; found {
agg := new(AggregationSingleBucket)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// Filter returns filter results.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-filter-aggregation.html
func (a Aggregations) Filter(name string) (*AggregationSingleBucket, bool) {
if raw, found := a[name]; found {
agg := new(AggregationSingleBucket)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// Filters returns filters results.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-filters-aggregation.html
func (a Aggregations) Filters(name string) (*AggregationBucketFilters, bool) {
if raw, found := a[name]; found {
agg := new(AggregationBucketFilters)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// AdjacencyMatrix returning a form of adjacency matrix.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-adjacency-matrix-aggregation.html
func (a Aggregations) AdjacencyMatrix(name string) (*AggregationBucketAdjacencyMatrix, bool) {
if raw, found := a[name]; found {
agg := new(AggregationBucketAdjacencyMatrix)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// Missing returns missing results.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-missing-aggregation.html
func (a Aggregations) Missing(name string) (*AggregationSingleBucket, bool) {
if raw, found := a[name]; found {
agg := new(AggregationSingleBucket)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// Nested returns nested results.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-nested-aggregation.html
func (a Aggregations) Nested(name string) (*AggregationSingleBucket, bool) {
if raw, found := a[name]; found {
agg := new(AggregationSingleBucket)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// ReverseNested returns reverse-nested results.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-reverse-nested-aggregation.html
func (a Aggregations) ReverseNested(name string) (*AggregationSingleBucket, bool) {
if raw, found := a[name]; found {
agg := new(AggregationSingleBucket)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// Children returns children results.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-children-aggregation.html
func (a Aggregations) Children(name string) (*AggregationSingleBucket, bool) {
if raw, found := a[name]; found {
agg := new(AggregationSingleBucket)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// Terms returns terms aggregation results.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-terms-aggregation.html
func (a Aggregations) Terms(name string) (*AggregationBucketKeyItems, bool) {
if raw, found := a[name]; found {
agg := new(AggregationBucketKeyItems)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// MultiTerms returns multi terms aggregation results.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.13/search-aggregations-bucket-multi-terms-aggregation.html
func (a Aggregations) MultiTerms(name string) (*AggregationBucketMultiKeyItems, bool) {
if raw, found := a[name]; found {
agg := new(AggregationBucketMultiKeyItems)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// SignificantTerms returns significant terms aggregation results.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-significantterms-aggregation.html
func (a Aggregations) SignificantTerms(name string) (*AggregationBucketSignificantTerms, bool) {
if raw, found := a[name]; found {
agg := new(AggregationBucketSignificantTerms)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// RareTerms returns rate terms aggregation results.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-rare-terms-aggregation.html
func (a Aggregations) RareTerms(name string) (*AggregationBucketKeyItems, bool) {
if raw, found := a[name]; found {
agg := new(AggregationBucketKeyItems)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// Sampler returns sampler aggregation results.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-sampler-aggregation.html
func (a Aggregations) Sampler(name string) (*AggregationSingleBucket, bool) {
if raw, found := a[name]; found {
agg := new(AggregationSingleBucket)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// DiversifiedSampler returns diversified_sampler aggregation results.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-diversified-sampler-aggregation.html
func (a Aggregations) DiversifiedSampler(name string) (*AggregationSingleBucket, bool) {
if raw, found := a[name]; found {
agg := new(AggregationSingleBucket)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// Range returns range aggregation results.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-range-aggregation.html
func (a Aggregations) Range(name string) (*AggregationBucketRangeItems, bool) {
if raw, found := a[name]; found {
agg := new(AggregationBucketRangeItems)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// KeyedRange returns keyed range aggregation results.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-range-aggregation.html.
func (a Aggregations) KeyedRange(name string) (*AggregationBucketKeyedRangeItems, bool) {
if raw, found := a[name]; found {
agg := new(AggregationBucketKeyedRangeItems)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// DateRange returns date range aggregation results.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-daterange-aggregation.html
func (a Aggregations) DateRange(name string) (*AggregationBucketRangeItems, bool) {
if raw, found := a[name]; found {
agg := new(AggregationBucketRangeItems)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// IPRange returns IP range aggregation results.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-iprange-aggregation.html
func (a Aggregations) IPRange(name string) (*AggregationBucketRangeItems, bool) {
if raw, found := a[name]; found {
agg := new(AggregationBucketRangeItems)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// Histogram returns histogram aggregation results.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-histogram-aggregation.html
func (a Aggregations) Histogram(name string) (*AggregationBucketHistogramItems, bool) {
if raw, found := a[name]; found {
agg := new(AggregationBucketHistogramItems)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// AutoDateHistogram returns auto date histogram aggregation results.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-datehistogram-aggregation.html
func (a Aggregations) AutoDateHistogram(name string) (*AggregationBucketHistogramItems, bool) {
if raw, found := a[name]; found {
agg := new(AggregationBucketHistogramItems)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// DateHistogram returns date histogram aggregation results.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-datehistogram-aggregation.html
func (a Aggregations) DateHistogram(name string) (*AggregationBucketHistogramItems, bool) {
if raw, found := a[name]; found {
agg := new(AggregationBucketHistogramItems)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// KeyedDateHistogram returns date histogram aggregation results for keyed responses.
//
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-datehistogram-aggregation.html#_keyed_response_3
func (a Aggregations) KeyedDateHistogram(name string) (*AggregationBucketKeyedHistogramItems, bool) {
if raw, found := a[name]; found {
agg := new(AggregationBucketKeyedHistogramItems)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// GeoBounds returns geo-bounds aggregation results.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-metrics-geobounds-aggregation.html
func (a Aggregations) GeoBounds(name string) (*AggregationGeoBoundsMetric, bool) {
if raw, found := a[name]; found {
agg := new(AggregationGeoBoundsMetric)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// GeoHash returns geo-hash aggregation results.
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-geohashgrid-aggregation.html
func (a Aggregations) GeoHash(name string) (*AggregationBucketKeyItems, bool) {
if raw, found := a[name]; found {
agg := new(AggregationBucketKeyItems)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// GeoTile returns geo-tile aggregation results.
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-geotilegrid-aggregation.html
func (a Aggregations) GeoTile(name string) (*AggregationBucketKeyItems, bool) {
if raw, found := a[name]; found {
agg := new(AggregationBucketKeyItems)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// GeoCentroid returns geo-centroid aggregation results.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-metrics-geocentroid-aggregation.html
func (a Aggregations) GeoCentroid(name string) (*AggregationGeoCentroidMetric, bool) {
if raw, found := a[name]; found {
agg := new(AggregationGeoCentroidMetric)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// GeoDistance returns geo distance aggregation results.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-geodistance-aggregation.html
func (a Aggregations) GeoDistance(name string) (*AggregationBucketRangeItems, bool) {
if raw, found := a[name]; found {
agg := new(AggregationBucketRangeItems)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// AvgBucket returns average bucket pipeline aggregation results.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-pipeline-avg-bucket-aggregation.html
func (a Aggregations) AvgBucket(name string) (*AggregationPipelineSimpleValue, bool) {
if raw, found := a[name]; found {
agg := new(AggregationPipelineSimpleValue)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// SumBucket returns sum bucket pipeline aggregation results.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-pipeline-sum-bucket-aggregation.html
func (a Aggregations) SumBucket(name string) (*AggregationPipelineSimpleValue, bool) {
if raw, found := a[name]; found {
agg := new(AggregationPipelineSimpleValue)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// StatsBucket returns stats bucket pipeline aggregation results.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-pipeline-stats-bucket-aggregation.html
func (a Aggregations) StatsBucket(name string) (*AggregationPipelineStatsMetric, bool) {
if raw, found := a[name]; found {
agg := new(AggregationPipelineStatsMetric)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// PercentilesBucket returns stats bucket pipeline aggregation results.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-pipeline-percentiles-bucket-aggregation.html
func (a Aggregations) PercentilesBucket(name string) (*AggregationPipelinePercentilesMetric, bool) {
if raw, found := a[name]; found {
agg := new(AggregationPipelinePercentilesMetric)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// MaxBucket returns maximum bucket pipeline aggregation results.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-pipeline-max-bucket-aggregation.html
func (a Aggregations) MaxBucket(name string) (*AggregationPipelineBucketMetricValue, bool) {
if raw, found := a[name]; found {
agg := new(AggregationPipelineBucketMetricValue)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// MinBucket returns minimum bucket pipeline aggregation results.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-pipeline-min-bucket-aggregation.html
func (a Aggregations) MinBucket(name string) (*AggregationPipelineBucketMetricValue, bool) {
if raw, found := a[name]; found {
agg := new(AggregationPipelineBucketMetricValue)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// MovAvg returns moving average pipeline aggregation results.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-pipeline-movavg-aggregation.html
//
// Deprecated: The MovAvgAggregation has been deprecated in 6.4.0. Use the more generate MovFnAggregation instead.
func (a Aggregations) MovAvg(name string) (*AggregationPipelineSimpleValue, bool) {
if raw, found := a[name]; found {
agg := new(AggregationPipelineSimpleValue)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// MovFn returns moving function pipeline aggregation results.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-pipeline-movfn-aggregation.html
func (a Aggregations) MovFn(name string) (*AggregationPipelineSimpleValue, bool) {
if raw, found := a[name]; found {
agg := new(AggregationPipelineSimpleValue)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// Derivative returns derivative pipeline aggregation results.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-pipeline-derivative-aggregation.html
func (a Aggregations) Derivative(name string) (*AggregationPipelineDerivative, bool) {
if raw, found := a[name]; found {
agg := new(AggregationPipelineDerivative)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// CumulativeSum returns a cumulative sum pipeline aggregation results.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-pipeline-cumulative-sum-aggregation.html
func (a Aggregations) CumulativeSum(name string) (*AggregationPipelineSimpleValue, bool) {
if raw, found := a[name]; found {
agg := new(AggregationPipelineSimpleValue)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// BucketScript returns bucket script pipeline aggregation results.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-pipeline-bucket-script-aggregation.html
func (a Aggregations) BucketScript(name string) (*AggregationPipelineSimpleValue, bool) {
if raw, found := a[name]; found {
agg := new(AggregationPipelineSimpleValue)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// SerialDiff returns serial differencing pipeline aggregation results.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-pipeline-serialdiff-aggregation.html
func (a Aggregations) SerialDiff(name string) (*AggregationPipelineSimpleValue, bool) {
if raw, found := a[name]; found {
agg := new(AggregationPipelineSimpleValue)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// Composite returns composite bucket aggregation results.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-composite-aggregation.html
// for details.
func (a Aggregations) Composite(name string) (*AggregationBucketCompositeItems, bool) {
if raw, found := a[name]; found {
agg := new(AggregationBucketCompositeItems)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// ScriptedMetric returns scripted metric aggregation results.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.2/search-aggregations-metrics-scripted-metric-aggregation.html
// for details.
func (a Aggregations) ScriptedMetric(name string) (*AggregationScriptedMetric, bool) {
if raw, found := a[name]; found {
agg := new(AggregationScriptedMetric)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// TopMetrics returns top metrics aggregation results.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-metrics-top-metrics.html
//for details
func (a Aggregations) TopMetrics(name string) (*AggregationTopMetricsItems, bool) {
if raw, found := a[name]; found {
agg := new(AggregationTopMetricsItems)
if raw == nil {
return agg, true
}
if err := json.Unmarshal(raw, agg); err == nil {
return agg, true
}
}
return nil, false
}
// -- Single value metric --
// AggregationValueMetric is a single-value metric, returned e.g. by a
// Min or Max aggregation.
type AggregationValueMetric struct {
Aggregations
Value *float64 //`json:"value"`
Meta map[string]interface{} // `json:"meta,omitempty"`
}
// UnmarshalJSON decodes JSON data and initializes an AggregationValueMetric structure.
func (a *AggregationValueMetric) UnmarshalJSON(data []byte) error {
var aggs map[string]json.RawMessage
if err := json.Unmarshal(data, &aggs); err != nil {
return err
}
if v, ok := aggs["value"]; ok && v != nil {
json.Unmarshal(v, &a.Value)
}
if v, ok := aggs["meta"]; ok && v != nil {
json.Unmarshal(v, &a.Meta)
}
a.Aggregations = aggs
return nil
}
// -- Stats metric --
// AggregationStatsMetric is a multi-value metric, returned by a Stats aggregation.
type AggregationStatsMetric struct {
Aggregations
Count int64 // `json:"count"`
Min *float64 //`json:"min,omitempty"`
Max *float64 //`json:"max,omitempty"`
Avg *float64 //`json:"avg,omitempty"`
Sum *float64 //`json:"sum,omitempty"`
Meta map[string]interface{} // `json:"meta,omitempty"`
}
// UnmarshalJSON decodes JSON data and initializes an AggregationStatsMetric structure.
func (a *AggregationStatsMetric) UnmarshalJSON(data []byte) error {
var aggs map[string]json.RawMessage
if err := json.Unmarshal(data, &aggs); err != nil {
return err
}
if v, ok := aggs["count"]; ok && v != nil {
json.Unmarshal(v, &a.Count)
}
if v, ok := aggs["min"]; ok && v != nil {
json.Unmarshal(v, &a.Min)
}
if v, ok := aggs["max"]; ok && v != nil {
json.Unmarshal(v, &a.Max)
}
if v, ok := aggs["avg"]; ok && v != nil {
json.Unmarshal(v, &a.Avg)
}
if v, ok := aggs["sum"]; ok && v != nil {
json.Unmarshal(v, &a.Sum)
}
if v, ok := aggs["meta"]; ok && v != nil {
json.Unmarshal(v, &a.Meta)
}
a.Aggregations = aggs
return nil
}
// -- Extended stats metric --
// AggregationExtendedStatsMetric is a multi-value metric, returned by an ExtendedStats aggregation.
type AggregationExtendedStatsMetric struct {
Aggregations
Count int64 // `json:"count"`
Min *float64 //`json:"min,omitempty"`
Max *float64 //`json:"max,omitempty"`
Avg *float64 //`json:"avg,omitempty"`
Sum *float64 //`json:"sum,omitempty"`
SumOfSquares *float64 //`json:"sum_of_squares,omitempty"`
Variance *float64 //`json:"variance,omitempty"`
StdDeviation *float64 //`json:"std_deviation,omitempty"`
Meta map[string]interface{} // `json:"meta,omitempty"`
}
// UnmarshalJSON decodes JSON data and initializes an AggregationExtendedStatsMetric structure.
func (a *AggregationExtendedStatsMetric) UnmarshalJSON(data []byte) error {
var aggs map[string]json.RawMessage
if err := json.Unmarshal(data, &aggs); err != nil {
return err
}
if v, ok := aggs["count"]; ok && v != nil {
json.Unmarshal(v, &a.Count)
}
if v, ok := aggs["min"]; ok && v != nil {
json.Unmarshal(v, &a.Min)
}
if v, ok := aggs["max"]; ok && v != nil {
json.Unmarshal(v, &a.Max)
}
if v, ok := aggs["avg"]; ok && v != nil {
json.Unmarshal(v, &a.Avg)
}
if v, ok := aggs["sum"]; ok && v != nil {
json.Unmarshal(v, &a.Sum)
}
if v, ok := aggs["sum_of_squares"]; ok && v != nil {
json.Unmarshal(v, &a.SumOfSquares)
}
if v, ok := aggs["variance"]; ok && v != nil {
json.Unmarshal(v, &a.Variance)
}
if v, ok := aggs["std_deviation"]; ok && v != nil {
json.Unmarshal(v, &a.StdDeviation)
}
if v, ok := aggs["meta"]; ok && v != nil {
json.Unmarshal(v, &a.Meta)
}
a.Aggregations = aggs
return nil
}
// -- Matrix Stats --
// AggregationMatrixStats is returned by a MatrixStats aggregation.
type AggregationMatrixStats struct {
Aggregations
Fields []*AggregationMatrixStatsField // `json:"field,omitempty"`
Meta map[string]interface{} // `json:"meta,omitempty"`
}
// AggregationMatrixStatsField represents running stats of a single field
// returned from MatrixStats aggregation.
type AggregationMatrixStatsField struct {
Name string `json:"name"`
Count int64 `json:"count"`
Mean float64 `json:"mean,omitempty"`
Variance float64 `json:"variance,omitempty"`
Skewness float64 `json:"skewness,omitempty"`
Kurtosis float64 `json:"kurtosis,omitempty"`
Covariance map[string]float64 `json:"covariance,omitempty"`
Correlation map[string]float64 `json:"correlation,omitempty"`
}
// UnmarshalJSON decodes JSON data and initializes an AggregationMatrixStats structure.
func (a *AggregationMatrixStats) UnmarshalJSON(data []byte) error {
var aggs map[string]json.RawMessage
if err := json.Unmarshal(data, &aggs); err != nil {
return err
}
if v, ok := aggs["fields"]; ok && v != nil {
// RunningStats for every field
json.Unmarshal(v, &a.Fields)
}
if v, ok := aggs["meta"]; ok && v != nil {
json.Unmarshal(v, &a.Meta)
}
a.Aggregations = aggs
return nil
}
// -- Percentiles metric --
// AggregationPercentilesMetric is a multi-value metric, returned by a Percentiles aggregation.
type AggregationPercentilesMetric struct {
Aggregations
Values map[string]float64 // `json:"values"`
Meta map[string]interface{} // `json:"meta,omitempty"`
}
// UnmarshalJSON decodes JSON data and initializes an AggregationPercentilesMetric structure.
func (a *AggregationPercentilesMetric) UnmarshalJSON(data []byte) error {
var aggs map[string]json.RawMessage
if err := json.Unmarshal(data, &aggs); err != nil {
return err
}
if v, ok := aggs["values"]; ok && v != nil {
json.Unmarshal(v, &a.Values)
}
if v, ok := aggs["meta"]; ok && v != nil {
json.Unmarshal(v, &a.Meta)
}
a.Aggregations = aggs
return nil
}
// -- Top-hits metric --
// AggregationTopHitsMetric is a metric returned by a TopHits aggregation.
type AggregationTopHitsMetric struct {
Aggregations
Hits *SearchHits //`json:"hits"`
Meta map[string]interface{} // `json:"meta,omitempty"`
}
// UnmarshalJSON decodes JSON data and initializes an AggregationTopHitsMetric structure.
func (a *AggregationTopHitsMetric) UnmarshalJSON(data []byte) error {
var aggs map[string]json.RawMessage
if err := json.Unmarshal(data, &aggs); err != nil {
return err
}
a.Aggregations = aggs
a.Hits = new(SearchHits)
if v, ok := aggs["hits"]; ok && v != nil {
json.Unmarshal(v, &a.Hits)
}
if v, ok := aggs["meta"]; ok && v != nil {
json.Unmarshal(v, &a.Meta)
}
return nil
}
// -- Geo-bounds metric --
// AggregationGeoBoundsMetric is a metric as returned by a GeoBounds aggregation.
type AggregationGeoBoundsMetric struct {
Aggregations
Bounds struct {
TopLeft struct {
Latitude float64 `json:"lat"`
Longitude float64 `json:"lon"`
} `json:"top_left"`
BottomRight struct {
Latitude float64 `json:"lat"`
Longitude float64 `json:"lon"`
} `json:"bottom_right"`
} `json:"bounds"`
Meta map[string]interface{} // `json:"meta,omitempty"`
}
// UnmarshalJSON decodes JSON data and initializes an AggregationGeoBoundsMetric structure.
func (a *AggregationGeoBoundsMetric) UnmarshalJSON(data []byte) error {
var aggs map[string]json.RawMessage
if err := json.Unmarshal(data, &aggs); err != nil {
return err
}
if v, ok := aggs["bounds"]; ok && v != nil {
json.Unmarshal(v, &a.Bounds)
}
if v, ok := aggs["meta"]; ok && v != nil {
json.Unmarshal(v, &a.Meta)
}
a.Aggregations = aggs
return nil
}
// AggregationGeoCentroidMetric is a metric as returned by a GeoCentroid aggregation.
type AggregationGeoCentroidMetric struct {
Aggregations
Location struct {
Latitude float64 `json:"lat"`
Longitude float64 `json:"lon"`
} `json:"location"`
Count int // `json:"count,omitempty"`
Meta map[string]interface{} // `json:"meta,omitempty"`
}
// UnmarshalJSON decodes JSON data and initializes an AggregationGeoCentroidMetric structure.
func (a *AggregationGeoCentroidMetric) UnmarshalJSON(data []byte) error {
var aggs map[string]json.RawMessage
if err := json.Unmarshal(data, &aggs); err != nil {
return err
}
if v, ok := aggs["location"]; ok && v != nil {
json.Unmarshal(v, &a.Location)
}
if v, ok := aggs["meta"]; ok && v != nil {
json.Unmarshal(v, &a.Meta)
}
if v, ok := aggs["count"]; ok && v != nil {
json.Unmarshal(v, &a.Count)
}
a.Aggregations = aggs
return nil
}
// -- Single bucket --
// AggregationSingleBucket is a single bucket, returned e.g. via an aggregation of type Global.
type AggregationSingleBucket struct {
Aggregations
DocCount int64 // `json:"doc_count"`
Meta map[string]interface{} // `json:"meta,omitempty"`
}
// UnmarshalJSON decodes JSON data and initializes an AggregationSingleBucket structure.
func (a *AggregationSingleBucket) UnmarshalJSON(data []byte) error {
var aggs map[string]json.RawMessage
if err := json.Unmarshal(data, &aggs); err != nil {
return err
}
if v, ok := aggs["doc_count"]; ok && v != nil {
json.Unmarshal(v, &a.DocCount)
}
if v, ok := aggs["meta"]; ok && v != nil {
json.Unmarshal(v, &a.Meta)
}
a.Aggregations = aggs
return nil
}
// -- Bucket range items --
// AggregationBucketRangeItems is a bucket aggregation that is e.g. returned
// with a range aggregation.
type AggregationBucketRangeItems struct {
Aggregations
DocCountErrorUpperBound int64 //`json:"doc_count_error_upper_bound"`
SumOfOtherDocCount int64 //`json:"sum_other_doc_count"`
Buckets []*AggregationBucketRangeItem //`json:"buckets"`
Meta map[string]interface{} // `json:"meta,omitempty"`
}
// UnmarshalJSON decodes JSON data and initializes an AggregationBucketRangeItems structure.
func (a *AggregationBucketRangeItems) UnmarshalJSON(data []byte) error {
var aggs map[string]json.RawMessage
if err := json.Unmarshal(data, &aggs); err != nil {
return err
}
if v, ok := aggs["doc_count_error_upper_bound"]; ok && v != nil {
json.Unmarshal(v, &a.DocCountErrorUpperBound)
}
if v, ok := aggs["sum_other_doc_count"]; ok && v != nil {
json.Unmarshal(v, &a.SumOfOtherDocCount)
}
if v, ok := aggs["buckets"]; ok && v != nil {
json.Unmarshal(v, &a.Buckets)
}
if v, ok := aggs["meta"]; ok && v != nil {
json.Unmarshal(v, &a.Meta)
}
a.Aggregations = aggs
return nil
}
// AggregationBucketKeyedRangeItems is a bucket aggregation that is e.g. returned
// with a keyed range aggregation.
type AggregationBucketKeyedRangeItems struct {
Aggregations
DocCountErrorUpperBound int64 //`json:"doc_count_error_upper_bound"`
SumOfOtherDocCount int64 //`json:"sum_other_doc_count"`
Buckets map[string]*AggregationBucketRangeItem //`json:"buckets"`
Meta map[string]interface{} // `json:"meta,omitempty"`
}
// UnmarshalJSON decodes JSON data and initializes an AggregationBucketRangeItems structure.
func (a *AggregationBucketKeyedRangeItems) UnmarshalJSON(data []byte) error {
var aggs map[string]json.RawMessage
if err := json.Unmarshal(data, &aggs); err != nil {
return err
}
if v, ok := aggs["doc_count_error_upper_bound"]; ok && v != nil {
json.Unmarshal(v, &a.DocCountErrorUpperBound)
}
if v, ok := aggs["sum_other_doc_count"]; ok && v != nil {
json.Unmarshal(v, &a.SumOfOtherDocCount)
}
if v, ok := aggs["buckets"]; ok && v != nil {
json.Unmarshal(v, &a.Buckets)
}
if v, ok := aggs["meta"]; ok && v != nil {
json.Unmarshal(v, &a.Meta)
}
a.Aggregations = aggs
return nil
}
// AggregationBucketRangeItem is a single bucket of an AggregationBucketRangeItems structure.
type AggregationBucketRangeItem struct {
Aggregations
Key string //`json:"key"`
DocCount int64 //`json:"doc_count"`
From *float64 //`json:"from"`
FromAsString string //`json:"from_as_string"`
To *float64 //`json:"to"`
ToAsString string //`json:"to_as_string"`
}
// UnmarshalJSON decodes JSON data and initializes an AggregationBucketRangeItem structure.
func (a *AggregationBucketRangeItem) UnmarshalJSON(data []byte) error {
var aggs map[string]json.RawMessage
if err := json.Unmarshal(data, &aggs); err != nil {
return err
}
if v, ok := aggs["key"]; ok && v != nil {
json.Unmarshal(v, &a.Key)
}
if v, ok := aggs["doc_count"]; ok && v != nil {
json.Unmarshal(v, &a.DocCount)
}
if v, ok := aggs["from"]; ok && v != nil {
json.Unmarshal(v, &a.From)
}
if v, ok := aggs["from_as_string"]; ok && v != nil {
json.Unmarshal(v, &a.FromAsString)
}
if v, ok := aggs["to"]; ok && v != nil {
json.Unmarshal(v, &a.To)
}
if v, ok := aggs["to_as_string"]; ok && v != nil {
json.Unmarshal(v, &a.ToAsString)
}
a.Aggregations = aggs
return nil
}
// -- Bucket key items --
// AggregationBucketKeyItems is a bucket aggregation that is e.g. returned
// with a terms aggregation.
type AggregationBucketKeyItems struct {
Aggregations
DocCountErrorUpperBound int64 //`json:"doc_count_error_upper_bound"`
SumOfOtherDocCount int64 //`json:"sum_other_doc_count"`
Buckets []*AggregationBucketKeyItem //`json:"buckets"`
Meta map[string]interface{} // `json:"meta,omitempty"`
}
// UnmarshalJSON decodes JSON data and initializes an AggregationBucketKeyItems structure.
func (a *AggregationBucketKeyItems) UnmarshalJSON(data []byte) error {
var aggs map[string]json.RawMessage
if err := json.Unmarshal(data, &aggs); err != nil {
return err
}
if v, ok := aggs["doc_count_error_upper_bound"]; ok && v != nil {
json.Unmarshal(v, &a.DocCountErrorUpperBound)
}
if v, ok := aggs["sum_other_doc_count"]; ok && v != nil {
json.Unmarshal(v, &a.SumOfOtherDocCount)
}
if v, ok := aggs["buckets"]; ok && v != nil {
json.Unmarshal(v, &a.Buckets)
}
if v, ok := aggs["meta"]; ok && v != nil {
json.Unmarshal(v, &a.Meta)
}
a.Aggregations = aggs
return nil
}
// AggregationBucketKeyItem is a single bucket of an AggregationBucketKeyItems structure.
type AggregationBucketKeyItem struct {
Aggregations
Key interface{} //`json:"key"`
KeyAsString *string //`json:"key_as_string"`
KeyNumber json.Number
DocCount int64 //`json:"doc_count"`
}
// UnmarshalJSON decodes JSON data and initializes an AggregationBucketKeyItem structure.
func (a *AggregationBucketKeyItem) UnmarshalJSON(data []byte) error {
var aggs map[string]json.RawMessage
dec := json.NewDecoder(bytes.NewReader(data))
dec.UseNumber()
if err := dec.Decode(&aggs); err != nil {
return err
}
if v, ok := aggs["key"]; ok && v != nil {
json.Unmarshal(v, &a.Key)
json.Unmarshal(v, &a.KeyNumber)
}
if v, ok := aggs["key_as_string"]; ok && v != nil {
json.Unmarshal(v, &a.KeyAsString)
}
if v, ok := aggs["doc_count"]; ok && v != nil {
json.Unmarshal(v, &a.DocCount)
}
a.Aggregations = aggs
return nil
}
// AggregationBucketMultiKeyItems is a bucket aggregation that is returned
// with a multi terms aggregation.
type AggregationBucketMultiKeyItems struct {
Aggregations
DocCountErrorUpperBound int64 //`json:"doc_count_error_upper_bound"`
SumOfOtherDocCount int64 //`json:"sum_other_doc_count"`
Buckets []*AggregationBucketMultiKeyItem //`json:"buckets"`
Meta map[string]interface{} // `json:"meta,omitempty"`
}
// UnmarshalJSON decodes JSON data and initializes an AggregationBucketMultiKeyItems structure.
func (a *AggregationBucketMultiKeyItems) UnmarshalJSON(data []byte) error {
var aggs map[string]json.RawMessage
if err := json.Unmarshal(data, &aggs); err != nil {
return err
}
if v, ok := aggs["doc_count_error_upper_bound"]; ok && v != nil {
json.Unmarshal(v, &a.DocCountErrorUpperBound)
}
if v, ok := aggs["sum_other_doc_count"]; ok && v != nil {
json.Unmarshal(v, &a.SumOfOtherDocCount)
}
if v, ok := aggs["buckets"]; ok && v != nil {
json.Unmarshal(v, &a.Buckets)
}
if v, ok := aggs["meta"]; ok && v != nil {
json.Unmarshal(v, &a.Meta)
}
a.Aggregations = aggs
return nil
}
// AggregationBucketMultiKeyItem is a single bucket of an AggregationBucketMultiKeyItems structure.
type AggregationBucketMultiKeyItem struct {
Aggregations
Key []interface{} //`json:"key"`
KeyAsString *string //`json:"key_as_string"`
KeyNumber []json.Number
DocCount int64 //`json:"doc_count"`
}
// UnmarshalJSON decodes JSON data and initializes an AggregationBucketMultiKeyItem structure.
func (a *AggregationBucketMultiKeyItem) UnmarshalJSON(data []byte) error {
var aggs map[string]json.RawMessage
dec := json.NewDecoder(bytes.NewReader(data))
dec.UseNumber()
if err := dec.Decode(&aggs); err != nil {
return err
}
if v, ok := aggs["key"]; ok && v != nil {
json.Unmarshal(v, &a.Key)
json.Unmarshal(v, &a.KeyNumber)
}
if v, ok := aggs["key_as_string"]; ok && v != nil {
json.Unmarshal(v, &a.KeyAsString)
}
if v, ok := aggs["doc_count"]; ok && v != nil {
json.Unmarshal(v, &a.DocCount)
}
a.Aggregations = aggs
return nil
}
// -- Bucket types for significant terms --
// AggregationBucketSignificantTerms is a bucket aggregation returned
// with a significant terms aggregation.
type AggregationBucketSignificantTerms struct {
Aggregations
DocCount int64 //`json:"doc_count"`
Buckets []*AggregationBucketSignificantTerm //`json:"buckets"`
Meta map[string]interface{} // `json:"meta,omitempty"`
}
// UnmarshalJSON decodes JSON data and initializes an AggregationBucketSignificantTerms structure.
func (a *AggregationBucketSignificantTerms) UnmarshalJSON(data []byte) error {
var aggs map[string]json.RawMessage
if err := json.Unmarshal(data, &aggs); err != nil {
return err
}
if v, ok := aggs["doc_count"]; ok && v != nil {
json.Unmarshal(v, &a.DocCount)
}
if v, ok := aggs["buckets"]; ok && v != nil {
json.Unmarshal(v, &a.Buckets)
}
if v, ok := aggs["meta"]; ok && v != nil {
json.Unmarshal(v, &a.Meta)
}
a.Aggregations = aggs
return nil
}
// AggregationBucketSignificantTerm is a single bucket of an AggregationBucketSignificantTerms structure.
type AggregationBucketSignificantTerm struct {
Aggregations
Key string //`json:"key"`
DocCount int64 //`json:"doc_count"`
BgCount int64 //`json:"bg_count"`
Score float64 //`json:"score"`
}
// UnmarshalJSON decodes JSON data and initializes an AggregationBucketSignificantTerm structure.
func (a *AggregationBucketSignificantTerm) UnmarshalJSON(data []byte) error {
var aggs map[string]json.RawMessage
if err := json.Unmarshal(data, &aggs); err != nil {
return err
}
if v, ok := aggs["key"]; ok && v != nil {
json.Unmarshal(v, &a.Key)
}
if v, ok := aggs["doc_count"]; ok && v != nil {
json.Unmarshal(v, &a.DocCount)
}
if v, ok := aggs["bg_count"]; ok && v != nil {
json.Unmarshal(v, &a.BgCount)
}
if v, ok := aggs["score"]; ok && v != nil {
json.Unmarshal(v, &a.Score)
}
a.Aggregations = aggs
return nil
}
// -- Bucket filters --
// AggregationBucketFilters is a multi-bucket aggregation that is returned
// with a filters aggregation.
type AggregationBucketFilters struct {
Aggregations
Buckets []*AggregationBucketKeyItem //`json:"buckets"`
NamedBuckets map[string]*AggregationBucketKeyItem //`json:"buckets"`
Meta map[string]interface{} // `json:"meta,omitempty"`
}
// UnmarshalJSON decodes JSON data and initializes an AggregationBucketFilters structure.
func (a *AggregationBucketFilters) UnmarshalJSON(data []byte) error {
var aggs map[string]json.RawMessage
if err := json.Unmarshal(data, &aggs); err != nil {
return err
}
if v, ok := aggs["buckets"]; ok && v != nil {
json.Unmarshal(v, &a.Buckets)
json.Unmarshal(v, &a.NamedBuckets)
}
if v, ok := aggs["meta"]; ok && v != nil {
json.Unmarshal(v, &a.Meta)
}
a.Aggregations = aggs
return nil
}
// -- Bucket AdjacencyMatrix --
// AggregationBucketAdjacencyMatrix is a multi-bucket aggregation that is returned
// with a AdjacencyMatrix aggregation.
type AggregationBucketAdjacencyMatrix struct {
Aggregations
Buckets []*AggregationBucketKeyItem //`json:"buckets"`
Meta map[string]interface{} // `json:"meta,omitempty"`
}
// UnmarshalJSON decodes JSON data and initializes an AggregationBucketAdjacencyMatrix structure.
func (a *AggregationBucketAdjacencyMatrix) UnmarshalJSON(data []byte) error {
var aggs map[string]json.RawMessage
if err := json.Unmarshal(data, &aggs); err != nil {
return err
}
if v, ok := aggs["buckets"]; ok && v != nil {
json.Unmarshal(v, &a.Buckets)
}
if v, ok := aggs["meta"]; ok && v != nil {
json.Unmarshal(v, &a.Meta)
}
a.Aggregations = aggs
return nil
}
// -- Bucket histogram items --
// AggregationBucketHistogramItems is a bucket aggregation that is returned
// with a date histogram aggregation.
type AggregationBucketHistogramItems struct {
Aggregations
Buckets []*AggregationBucketHistogramItem // `json:"buckets"`
Meta map[string]interface{} // `json:"meta,omitempty"`
}
// UnmarshalJSON decodes JSON data and initializes an AggregationBucketHistogramItems structure.
func (a *AggregationBucketHistogramItems) UnmarshalJSON(data []byte) error {
var aggs map[string]json.RawMessage
if err := json.Unmarshal(data, &aggs); err != nil {
return err
}
if v, ok := aggs["buckets"]; ok && v != nil {
json.Unmarshal(v, &a.Buckets)
}
if v, ok := aggs["meta"]; ok && v != nil {
json.Unmarshal(v, &a.Meta)
}
a.Aggregations = aggs
return nil
}
// AggregationBucketKeyedHistogramItems is a bucket aggregation that is returned
// with a (keyed) date histogram aggregation.
type AggregationBucketKeyedHistogramItems struct {
Aggregations
Buckets map[string]*AggregationBucketHistogramItem //`json:"buckets"`
Meta map[string]interface{} // `json:"meta,omitempty"`
}
// UnmarshalJSON decodes JSON data and initializes an AggregationBucketKeyedHistogramItems structure.
func (a *AggregationBucketKeyedHistogramItems) UnmarshalJSON(data []byte) error {
var aggs map[string]json.RawMessage
if err := json.Unmarshal(data, &aggs); err != nil {
return err
}
if v, ok := aggs["buckets"]; ok && v != nil {
json.Unmarshal(v, &a.Buckets)
}
if v, ok := aggs["meta"]; ok && v != nil {
json.Unmarshal(v, &a.Meta)
}
a.Aggregations = aggs
return nil
}
// AggregationBucketHistogramItem is a single bucket of an AggregationBucketHistogramItems structure.
type AggregationBucketHistogramItem struct {
Aggregations
Key float64 //`json:"key"`
KeyAsString *string //`json:"key_as_string"`
DocCount int64 //`json:"doc_count"`
}
// UnmarshalJSON decodes JSON data and initializes an AggregationBucketHistogramItem structure.
func (a *AggregationBucketHistogramItem) UnmarshalJSON(data []byte) error {
var aggs map[string]json.RawMessage
if err := json.Unmarshal(data, &aggs); err != nil {
return err
}
if v, ok := aggs["key"]; ok && v != nil {
json.Unmarshal(v, &a.Key)
}
if v, ok := aggs["key_as_string"]; ok && v != nil {
json.Unmarshal(v, &a.KeyAsString)
}
if v, ok := aggs["doc_count"]; ok && v != nil {
json.Unmarshal(v, &a.DocCount)
}
a.Aggregations = aggs
return nil
}
// -- Pipeline simple value --
// AggregationPipelineSimpleValue is a simple value, returned e.g. by a
// MovAvg aggregation.
type AggregationPipelineSimpleValue struct {
Aggregations
Value *float64 // `json:"value"`
ValueAsString string // `json:"value_as_string"`
Meta map[string]interface{} // `json:"meta,omitempty"`
}
// UnmarshalJSON decodes JSON data and initializes an AggregationPipelineSimpleValue structure.
func (a *AggregationPipelineSimpleValue) UnmarshalJSON(data []byte) error {
var aggs map[string]json.RawMessage
if err := json.Unmarshal(data, &aggs); err != nil {
return err
}
if v, ok := aggs["value"]; ok && v != nil {
json.Unmarshal(v, &a.Value)
}
if v, ok := aggs["value_as_string"]; ok && v != nil {
json.Unmarshal(v, &a.ValueAsString)
}
if v, ok := aggs["meta"]; ok && v != nil {
json.Unmarshal(v, &a.Meta)
}
a.Aggregations = aggs
return nil
}
// -- Pipeline simple value --
// AggregationPipelineBucketMetricValue is a value returned e.g. by a
// MaxBucket aggregation.
type AggregationPipelineBucketMetricValue struct {
Aggregations
Keys []interface{} // `json:"keys"`
Value *float64 // `json:"value"`
ValueAsString string // `json:"value_as_string"`
Meta map[string]interface{} // `json:"meta,omitempty"`
}
// UnmarshalJSON decodes JSON data and initializes an AggregationPipelineBucketMetricValue structure.
func (a *AggregationPipelineBucketMetricValue) UnmarshalJSON(data []byte) error {
var aggs map[string]json.RawMessage
if err := json.Unmarshal(data, &aggs); err != nil {
return err
}
if v, ok := aggs["keys"]; ok && v != nil {
json.Unmarshal(v, &a.Keys)
}
if v, ok := aggs["value"]; ok && v != nil {
json.Unmarshal(v, &a.Value)
}
if v, ok := aggs["value_as_string"]; ok && v != nil {
json.Unmarshal(v, &a.ValueAsString)
}
if v, ok := aggs["meta"]; ok && v != nil {
json.Unmarshal(v, &a.Meta)
}
a.Aggregations = aggs
return nil
}
// -- Pipeline derivative --
// AggregationPipelineDerivative is the value returned by a
// Derivative aggregation.
type AggregationPipelineDerivative struct {
Aggregations
Value *float64 // `json:"value"`
ValueAsString string // `json:"value_as_string"`
NormalizedValue *float64 // `json:"normalized_value"`
NormalizedValueAsString string // `json:"normalized_value_as_string"`
Meta map[string]interface{} // `json:"meta,omitempty"`
}
// UnmarshalJSON decodes JSON data and initializes an AggregationPipelineDerivative structure.
func (a *AggregationPipelineDerivative) UnmarshalJSON(data []byte) error {
var aggs map[string]json.RawMessage
if err := json.Unmarshal(data, &aggs); err != nil {
return err
}
if v, ok := aggs["value"]; ok && v != nil {
json.Unmarshal(v, &a.Value)
}
if v, ok := aggs["value_as_string"]; ok && v != nil {
json.Unmarshal(v, &a.ValueAsString)
}
if v, ok := aggs["normalized_value"]; ok && v != nil {
json.Unmarshal(v, &a.NormalizedValue)
}
if v, ok := aggs["normalized_value_as_string"]; ok && v != nil {
json.Unmarshal(v, &a.NormalizedValueAsString)
}
if v, ok := aggs["meta"]; ok && v != nil {
json.Unmarshal(v, &a.Meta)
}
a.Aggregations = aggs
return nil
}
// -- Pipeline stats metric --
// AggregationPipelineStatsMetric is a simple value, returned e.g. by a
// MovAvg aggregation.
type AggregationPipelineStatsMetric struct {
Aggregations
Count int64 // `json:"count"`
CountAsString string // `json:"count_as_string"`
Min *float64 // `json:"min"`
MinAsString string // `json:"min_as_string"`
Max *float64 // `json:"max"`
MaxAsString string // `json:"max_as_string"`
Avg *float64 // `json:"avg"`
AvgAsString string // `json:"avg_as_string"`
Sum *float64 // `json:"sum"`
SumAsString string // `json:"sum_as_string"`
Meta map[string]interface{} // `json:"meta,omitempty"`
}
// UnmarshalJSON decodes JSON data and initializes an AggregationPipelineStatsMetric structure.
func (a *AggregationPipelineStatsMetric) UnmarshalJSON(data []byte) error {
var aggs map[string]json.RawMessage
if err := json.Unmarshal(data, &aggs); err != nil {
return err
}
if v, ok := aggs["count"]; ok && v != nil {
json.Unmarshal(v, &a.Count)
}
if v, ok := aggs["count_as_string"]; ok && v != nil {
json.Unmarshal(v, &a.CountAsString)
}
if v, ok := aggs["min"]; ok && v != nil {
json.Unmarshal(v, &a.Min)
}
if v, ok := aggs["min_as_string"]; ok && v != nil {
json.Unmarshal(v, &a.MinAsString)
}
if v, ok := aggs["max"]; ok && v != nil {
json.Unmarshal(v, &a.Max)
}
if v, ok := aggs["max_as_string"]; ok && v != nil {
json.Unmarshal(v, &a.MaxAsString)
}
if v, ok := aggs["avg"]; ok && v != nil {
json.Unmarshal(v, &a.Avg)
}
if v, ok := aggs["avg_as_string"]; ok && v != nil {
json.Unmarshal(v, &a.AvgAsString)
}
if v, ok := aggs["sum"]; ok && v != nil {
json.Unmarshal(v, &a.Sum)
}
if v, ok := aggs["sum_as_string"]; ok && v != nil {
json.Unmarshal(v, &a.SumAsString)
}
if v, ok := aggs["meta"]; ok && v != nil {
json.Unmarshal(v, &a.Meta)
}
a.Aggregations = aggs
return nil
}
// -- Pipeline percentiles
// AggregationPipelinePercentilesMetric is the value returned by a pipeline
// percentiles Metric aggregation
type AggregationPipelinePercentilesMetric struct {
Aggregations
Values map[string]float64 // `json:"values"`
Meta map[string]interface{} // `json:"meta,omitempty"`
}
// UnmarshalJSON decodes JSON data and initializes an AggregationPipelinePercentilesMetric structure.
func (a *AggregationPipelinePercentilesMetric) UnmarshalJSON(data []byte) error {
var aggs map[string]json.RawMessage
if err := json.Unmarshal(data, &aggs); err != nil {
return err
}
if v, ok := aggs["values"]; ok && v != nil {
json.Unmarshal(v, &a.Values)
}
if v, ok := aggs["meta"]; ok && v != nil {
json.Unmarshal(v, &a.Meta)
}
a.Aggregations = aggs
return nil
}
// -- Composite key items --
// AggregationBucketCompositeItems implements the response structure
// for a bucket aggregation of type composite.
type AggregationBucketCompositeItems struct {
Aggregations
Buckets []*AggregationBucketCompositeItem //`json:"buckets"`
Meta map[string]interface{} // `json:"meta,omitempty"`
AfterKey map[string]interface{} // `json:"after_key,omitempty"`
}
// UnmarshalJSON decodes JSON data and initializes an AggregationBucketCompositeItems structure.
func (a *AggregationBucketCompositeItems) UnmarshalJSON(data []byte) error {
var aggs map[string]json.RawMessage
if err := json.Unmarshal(data, &aggs); err != nil {
return err
}
if v, ok := aggs["buckets"]; ok && v != nil {
json.Unmarshal(v, &a.Buckets)
}
if v, ok := aggs["meta"]; ok && v != nil {
json.Unmarshal(v, &a.Meta)
}
if v, ok := aggs["after_key"]; ok && v != nil {
json.Unmarshal(v, &a.AfterKey)
}
a.Aggregations = aggs
return nil
}
// AggregationBucketCompositeItem is a single bucket of an AggregationBucketCompositeItems structure.
type AggregationBucketCompositeItem struct {
Aggregations
Key map[string]interface{} //`json:"key"`
DocCount int64 //`json:"doc_count"`
}
// UnmarshalJSON decodes JSON data and initializes an AggregationBucketCompositeItem structure.
func (a *AggregationBucketCompositeItem) UnmarshalJSON(data []byte) error {
var aggs map[string]json.RawMessage
dec := json.NewDecoder(bytes.NewReader(data))
dec.UseNumber()
if err := dec.Decode(&aggs); err != nil {
return err
}
if v, ok := aggs["key"]; ok && v != nil {
json.Unmarshal(v, &a.Key)
}
if v, ok := aggs["doc_count"]; ok && v != nil {
json.Unmarshal(v, &a.DocCount)
}
a.Aggregations = aggs
return nil
}
// AggregationScriptedMetric is the value return by a scripted metric aggregation.
// Value maybe one of map[string]interface{}/[]interface{}/string/bool/json.Number
type AggregationScriptedMetric struct {
Aggregations
Value interface{} //`json:"value"`
Meta map[string]interface{} //`json:"meta,omitempty"`
}
// UnmarshalJSON decodes JSON data and initializes an AggregationScriptedMetric structure.
func (a *AggregationScriptedMetric) UnmarshalJSON(data []byte) error {
var aggs map[string]json.RawMessage
if err := json.Unmarshal(data, &aggs); err != nil {
return err
}
if v, ok := aggs["value"]; ok && v != nil {
decoder := json.NewDecoder(bytes.NewReader(v))
decoder.UseNumber()
decoder.Decode(&a.Value)
}
if v, ok := aggs["meta"]; ok && v != nil {
json.Unmarshal(v, &a.Meta)
}
a.Aggregations = aggs
return nil
}
// AggregationTopMetricsItems is the value returned by the top metrics aggregation
type AggregationTopMetricsItems struct {
Aggregations
Top []AggregationTopMetricsItem `json:"top"`
}
// AggregationTopMetricsItem is a set of metrics returned for the top document or documents
type AggregationTopMetricsItem struct {
Sort []interface{} `json:"sort"` // sort information
Metrics map[string]interface{} `json:"metrics"` // returned metrics
}
================================================
FILE: search_aggs_bucket_adjacency_matrix.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// AdjacencyMatrixAggregation returning a form of adjacency matrix.
// The request provides a collection of named filter expressions,
// similar to the filters aggregation request. Each bucket in the
// response represents a non-empty cell in the matrix of intersecting filters.
//
// For details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-adjacency-matrix-aggregation.html
type AdjacencyMatrixAggregation struct {
filters map[string]Query
subAggregations map[string]Aggregation
meta map[string]interface{}
}
// NewAdjacencyMatrixAggregation initializes a new AdjacencyMatrixAggregation.
func NewAdjacencyMatrixAggregation() *AdjacencyMatrixAggregation {
return &AdjacencyMatrixAggregation{
filters: make(map[string]Query),
subAggregations: make(map[string]Aggregation),
}
}
// Filters adds the filter
func (a *AdjacencyMatrixAggregation) Filters(name string, filter Query) *AdjacencyMatrixAggregation {
a.filters[name] = filter
return a
}
// SubAggregation adds a sub-aggregation to this aggregation.
func (a *AdjacencyMatrixAggregation) SubAggregation(name string, subAggregation Aggregation) *AdjacencyMatrixAggregation {
a.subAggregations[name] = subAggregation
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *AdjacencyMatrixAggregation) Meta(metaData map[string]interface{}) *AdjacencyMatrixAggregation {
a.meta = metaData
return a
}
// Source returns the a JSON-serializable interface.
func (a *AdjacencyMatrixAggregation) Source() (interface{}, error) {
// Example:
// {
// "aggs" : {
// "interactions" : {
// "adjacency_matrix" : {
// "filters" : {
// "grpA" : { "terms" : { "accounts" : ["hillary", "sidney"] }},
// "grpB" : { "terms" : { "accounts" : ["donald", "mitt"] }},
// "grpC" : { "terms" : { "accounts" : ["vladimir", "nigel"] }}
// }
// }
// }
// }
// This method returns only the (outer) { "adjacency_matrix" : {} } part.
source := make(map[string]interface{})
adjacencyMatrix := make(map[string]interface{})
source["adjacency_matrix"] = adjacencyMatrix
dict := make(map[string]interface{})
for key, filter := range a.filters {
src, err := filter.Source()
if err != nil {
return nil, err
}
dict[key] = src
}
adjacencyMatrix["filters"] = dict
// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_bucket_adjacency_matrix_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestAdjacencyMatrixAggregationFilters(t *testing.T) {
f1 := NewTermQuery("accounts", "sydney")
f2 := NewTermQuery("accounts", "mitt")
f3 := NewTermQuery("accounts", "nigel")
agg := NewAdjacencyMatrixAggregation().Filters("grpA", f1).Filters("grpB", f2).Filters("grpC", f3)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"adjacency_matrix":{"filters":{"grpA":{"term":{"accounts":"sydney"}},"grpB":{"term":{"accounts":"mitt"}},"grpC":{"term":{"accounts":"nigel"}}}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestAdjacencyMatrixAggregationWithSubAggregation(t *testing.T) {
avgPriceAgg := NewAvgAggregation().Field("price")
f1 := NewTermQuery("accounts", "sydney")
f2 := NewTermQuery("accounts", "mitt")
f3 := NewTermQuery("accounts", "nigel")
agg := NewAdjacencyMatrixAggregation().SubAggregation("avg_price", avgPriceAgg).Filters("grpA", f1).Filters("grpB", f2).Filters("grpC", f3)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"adjacency_matrix":{"filters":{"grpA":{"term":{"accounts":"sydney"}},"grpB":{"term":{"accounts":"mitt"}},"grpC":{"term":{"accounts":"nigel"}}}},"aggregations":{"avg_price":{"avg":{"field":"price"}}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestAdjacencyMatrixAggregationWithMetaData(t *testing.T) {
f1 := NewTermQuery("accounts", "sydney")
f2 := NewTermQuery("accounts", "mitt")
f3 := NewTermQuery("accounts", "nigel")
agg := NewAdjacencyMatrixAggregation().Filters("grpA", f1).Filters("grpB", f2).Filters("grpC", f3).Meta(map[string]interface{}{"name": "Oliver"})
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"adjacency_matrix":{"filters":{"grpA":{"term":{"accounts":"sydney"}},"grpB":{"term":{"accounts":"mitt"}},"grpC":{"term":{"accounts":"nigel"}}}},"meta":{"name":"Oliver"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_bucket_auto_date_histogram.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// AutoDateHistogramAggregation is a multi-bucket aggregation similar to the
// histogram except it can only be applied on date values, and the buckets num can bin pointed.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.3/search-aggregations-bucket-autodatehistogram-aggregation.html
type AutoDateHistogramAggregation struct {
field string
script *Script
missing interface{}
subAggregations map[string]Aggregation
meta map[string]interface{}
buckets int
minDocCount *int64
timeZone string
format string
minimumInterval string
}
// NewAutoDateHistogramAggregation creates a new AutoDateHistogramAggregation.
func NewAutoDateHistogramAggregation() *AutoDateHistogramAggregation {
return &AutoDateHistogramAggregation{
subAggregations: make(map[string]Aggregation),
}
}
// Field on which the aggregation is processed.
func (a *AutoDateHistogramAggregation) Field(field string) *AutoDateHistogramAggregation {
a.field = field
return a
}
// Script on which th
func (a *AutoDateHistogramAggregation) Script(script *Script) *AutoDateHistogramAggregation {
a.script = script
return a
}
// Missing configures the value to use when documents miss a value.
func (a *AutoDateHistogramAggregation) Missing(missing interface{}) *AutoDateHistogramAggregation {
a.missing = missing
return a
}
// SubAggregation sub aggregation
func (a *AutoDateHistogramAggregation) SubAggregation(name string, subAggregation Aggregation) *AutoDateHistogramAggregation {
a.subAggregations[name] = subAggregation
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *AutoDateHistogramAggregation) Meta(metaData map[string]interface{}) *AutoDateHistogramAggregation {
a.meta = metaData
return a
}
// Buckets buckets num by which the aggregation gets processed.
func (a *AutoDateHistogramAggregation) Buckets(buckets int) *AutoDateHistogramAggregation {
a.buckets = buckets
return a
}
// MinDocCount sets the minimum document count per bucket.
// Buckets with less documents than this min value will not be returned.
func (a *AutoDateHistogramAggregation) MinDocCount(minDocCount int64) *AutoDateHistogramAggregation {
a.minDocCount = &minDocCount
return a
}
// TimeZone sets the timezone in which to translate dates before computing buckets.
func (a *AutoDateHistogramAggregation) TimeZone(timeZone string) *AutoDateHistogramAggregation {
a.timeZone = timeZone
return a
}
// Format sets the format to use for dates.
func (a *AutoDateHistogramAggregation) Format(format string) *AutoDateHistogramAggregation {
a.format = format
return a
}
// MinimumInterval accepted units for minimum_interval are: year/month/day/hour/minute/second
func (a *AutoDateHistogramAggregation) MinimumInterval(interval string) *AutoDateHistogramAggregation {
a.minimumInterval = interval
return a
}
// Source source for AutoDateHistogramAggregation
func (a *AutoDateHistogramAggregation) Source() (interface{}, error) {
// Example:
// {
// "aggs" : {
// "articles_over_time" : {
// "auto_date_histogram" : {
// "field" : "date",
// "buckets" : 10
// }
// }
// }
// }
//
// This method returns only the { "auto_date_histogram" : { ... } } part.
source := make(map[string]interface{})
opts := make(map[string]interface{})
source["auto_date_histogram"] = opts
// ValuesSourceAggregationBuilder
if a.field != "" {
opts["field"] = a.field
}
if a.script != nil {
src, err := a.script.Source()
if err != nil {
return nil, err
}
opts["script"] = src
}
if a.missing != nil {
opts["missing"] = a.missing
}
if a.buckets > 0 {
opts["buckets"] = a.buckets
}
if a.minDocCount != nil {
opts["min_doc_count"] = *a.minDocCount
}
if a.timeZone != "" {
opts["time_zone"] = a.timeZone
}
if a.format != "" {
opts["format"] = a.format
}
if a.minimumInterval != "" {
opts["minimum_interval"] = a.minimumInterval
}
// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_bucket_auto_date_histogram_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestAutoDateHistogramAggregation(t *testing.T) {
agg := NewAutoDateHistogramAggregation().
Field("date").
Buckets(10)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"auto_date_histogram":{"buckets":10,"field":"date"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestAutoDateHistogramAggregationWithFormat(t *testing.T) {
agg := NewAutoDateHistogramAggregation().Field("date").Format("yyyy-MM-dd").Buckets(5)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"auto_date_histogram":{"buckets":5,"field":"date","format":"yyyy-MM-dd"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestAutoDateHistogramAggregationWithMissing(t *testing.T) {
agg := NewAutoDateHistogramAggregation().Field("date").Missing("1900")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"auto_date_histogram":{"field":"date","missing":"1900"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_bucket_children.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// ChildrenAggregation is a special single bucket aggregation that enables
// aggregating from buckets on parent document types to buckets on child documents.
// It is available from 1.4.0.Beta1 upwards.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-children-aggregation.html
type ChildrenAggregation struct {
typ string
subAggregations map[string]Aggregation
meta map[string]interface{}
}
func NewChildrenAggregation() *ChildrenAggregation {
return &ChildrenAggregation{
subAggregations: make(map[string]Aggregation),
}
}
func (a *ChildrenAggregation) Type(typ string) *ChildrenAggregation {
a.typ = typ
return a
}
func (a *ChildrenAggregation) SubAggregation(name string, subAggregation Aggregation) *ChildrenAggregation {
a.subAggregations[name] = subAggregation
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *ChildrenAggregation) Meta(metaData map[string]interface{}) *ChildrenAggregation {
a.meta = metaData
return a
}
func (a *ChildrenAggregation) Source() (interface{}, error) {
// Example:
// {
// "aggs" : {
// "to-answers" : {
// "children": {
// "type" : "answer"
// }
// }
// }
// }
// This method returns only the { "type" : ... } part.
source := make(map[string]interface{})
opts := make(map[string]interface{})
source["children"] = opts
opts["type"] = a.typ
// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_bucket_children_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestChildrenAggregation(t *testing.T) {
agg := NewChildrenAggregation().Type("answer")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"children":{"type":"answer"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestChildrenAggregationWithSubAggregation(t *testing.T) {
subAgg := NewTermsAggregation().Field("owner.display_name").Size(10)
agg := NewChildrenAggregation().Type("answer")
agg = agg.SubAggregation("top-names", subAgg)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"aggregations":{"top-names":{"terms":{"field":"owner.display_name","size":10}}},"children":{"type":"answer"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_bucket_composite.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// CompositeAggregation is a multi-bucket values source based aggregation
// that can be used to calculate unique composite values from source documents.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-composite-aggregation.html
// for details.
type CompositeAggregation struct {
after map[string]interface{}
size *int
sources []CompositeAggregationValuesSource
subAggregations map[string]Aggregation
meta map[string]interface{}
}
// NewCompositeAggregation creates a new CompositeAggregation.
func NewCompositeAggregation() *CompositeAggregation {
return &CompositeAggregation{
sources: make([]CompositeAggregationValuesSource, 0),
subAggregations: make(map[string]Aggregation),
}
}
// Size represents the number of composite buckets to return.
// Defaults to 10 as of Elasticsearch 6.1.
func (a *CompositeAggregation) Size(size int) *CompositeAggregation {
a.size = &size
return a
}
// AggregateAfter sets the values that indicate which composite bucket this
// request should "aggregate after".
func (a *CompositeAggregation) AggregateAfter(after map[string]interface{}) *CompositeAggregation {
a.after = after
return a
}
// Sources specifies the list of CompositeAggregationValuesSource instances to
// use in the aggregation.
func (a *CompositeAggregation) Sources(sources ...CompositeAggregationValuesSource) *CompositeAggregation {
a.sources = append(a.sources, sources...)
return a
}
// SubAggregations of this aggregation.
func (a *CompositeAggregation) SubAggregation(name string, subAggregation Aggregation) *CompositeAggregation {
a.subAggregations[name] = subAggregation
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *CompositeAggregation) Meta(metaData map[string]interface{}) *CompositeAggregation {
a.meta = metaData
return a
}
// Source returns the serializable JSON for this aggregation.
func (a *CompositeAggregation) Source() (interface{}, error) {
// Example:
// {
// "aggs" : {
// "my_composite_agg" : {
// "composite" : {
// "sources": [
// {"my_term": { "terms": { "field": "product" }}},
// {"my_histo": { "histogram": { "field": "price", "interval": 5 }}},
// {"my_date": { "date_histogram": { "field": "timestamp", "interval": "1d" }}},
// ],
// "size" : 10,
// "after" : ["a", 2, "c"]
// }
// }
// }
// }
//
// This method returns only the { "histogram" : { ... } } part.
source := make(map[string]interface{})
opts := make(map[string]interface{})
source["composite"] = opts
sources := make([]interface{}, len(a.sources))
for i, s := range a.sources {
src, err := s.Source()
if err != nil {
return nil, err
}
sources[i] = src
}
opts["sources"] = sources
if a.size != nil {
opts["size"] = *a.size
}
if a.after != nil {
opts["after"] = a.after
}
// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
// -- Generic interface for CompositeAggregationValues --
// CompositeAggregationValuesSource specifies the interface that
// all implementations for CompositeAggregation's Sources method
// need to implement.
//
// The different implementations are described in
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-composite-aggregation.html#_values_source_2.
type CompositeAggregationValuesSource interface {
Source() (interface{}, error)
}
// -- CompositeAggregationTermsValuesSource --
// CompositeAggregationTermsValuesSource is a source for the CompositeAggregation that handles terms
// it works very similar to a terms aggregation with slightly different syntax
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-composite-aggregation.html#_terms
// for details.
type CompositeAggregationTermsValuesSource struct {
name string
field string
script *Script
valueType string
missing interface{}
missingBucket *bool
order string
}
// NewCompositeAggregationTermsValuesSource creates and initializes
// a new CompositeAggregationTermsValuesSource.
func NewCompositeAggregationTermsValuesSource(name string) *CompositeAggregationTermsValuesSource {
return &CompositeAggregationTermsValuesSource{
name: name,
}
}
// Field to use for this source.
func (a *CompositeAggregationTermsValuesSource) Field(field string) *CompositeAggregationTermsValuesSource {
a.field = field
return a
}
// Script to use for this source.
func (a *CompositeAggregationTermsValuesSource) Script(script *Script) *CompositeAggregationTermsValuesSource {
a.script = script
return a
}
// ValueType specifies the type of values produced by this source,
// e.g. "string" or "date".
func (a *CompositeAggregationTermsValuesSource) ValueType(valueType string) *CompositeAggregationTermsValuesSource {
a.valueType = valueType
return a
}
// Order specifies the order in the values produced by this source.
// It can be either "asc" or "desc".
func (a *CompositeAggregationTermsValuesSource) Order(order string) *CompositeAggregationTermsValuesSource {
a.order = order
return a
}
// Asc ensures the order of the values produced is ascending.
func (a *CompositeAggregationTermsValuesSource) Asc() *CompositeAggregationTermsValuesSource {
a.order = "asc"
return a
}
// Desc ensures the order of the values produced is descending.
func (a *CompositeAggregationTermsValuesSource) Desc() *CompositeAggregationTermsValuesSource {
a.order = "desc"
return a
}
// Missing specifies the value to use when the source finds a missing
// value in a document.
//
// Deprecated: Use MissingBucket instead.
func (a *CompositeAggregationTermsValuesSource) Missing(missing interface{}) *CompositeAggregationTermsValuesSource {
a.missing = missing
return a
}
// MissingBucket, if true, will create an explicit null bucket which represents
// documents with missing values.
func (a *CompositeAggregationTermsValuesSource) MissingBucket(missingBucket bool) *CompositeAggregationTermsValuesSource {
a.missingBucket = &missingBucket
return a
}
// Source returns the serializable JSON for this values source.
func (a *CompositeAggregationTermsValuesSource) Source() (interface{}, error) {
source := make(map[string]interface{})
name := make(map[string]interface{})
source[a.name] = name
values := make(map[string]interface{})
name["terms"] = values
// field
if a.field != "" {
values["field"] = a.field
}
// script
if a.script != nil {
src, err := a.script.Source()
if err != nil {
return nil, err
}
values["script"] = src
}
// missing
if a.missing != nil {
values["missing"] = a.missing
}
// missing_bucket
if a.missingBucket != nil {
values["missing_bucket"] = *a.missingBucket
}
// value_type
if a.valueType != "" {
values["value_type"] = a.valueType
}
// order
if a.order != "" {
values["order"] = a.order
}
return source, nil
}
// -- CompositeAggregationHistogramValuesSource --
// CompositeAggregationHistogramValuesSource is a source for the CompositeAggregation that handles histograms
// it works very similar to a terms histogram with slightly different syntax
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-composite-aggregation.html#_histogram
// for details.
type CompositeAggregationHistogramValuesSource struct {
name string
field string
script *Script
valueType string
missing interface{}
missingBucket *bool
order string
interval float64
}
// NewCompositeAggregationHistogramValuesSource creates and initializes
// a new CompositeAggregationHistogramValuesSource.
func NewCompositeAggregationHistogramValuesSource(name string, interval float64) *CompositeAggregationHistogramValuesSource {
return &CompositeAggregationHistogramValuesSource{
name: name,
interval: interval,
}
}
// Field to use for this source.
func (a *CompositeAggregationHistogramValuesSource) Field(field string) *CompositeAggregationHistogramValuesSource {
a.field = field
return a
}
// Script to use for this source.
func (a *CompositeAggregationHistogramValuesSource) Script(script *Script) *CompositeAggregationHistogramValuesSource {
a.script = script
return a
}
// ValueType specifies the type of values produced by this source,
// e.g. "string" or "date".
func (a *CompositeAggregationHistogramValuesSource) ValueType(valueType string) *CompositeAggregationHistogramValuesSource {
a.valueType = valueType
return a
}
// Missing specifies the value to use when the source finds a missing
// value in a document.
//
// Deprecated: Use MissingBucket instead.
func (a *CompositeAggregationHistogramValuesSource) Missing(missing interface{}) *CompositeAggregationHistogramValuesSource {
a.missing = missing
return a
}
// MissingBucket, if true, will create an explicit null bucket which represents
// documents with missing values.
func (a *CompositeAggregationHistogramValuesSource) MissingBucket(missingBucket bool) *CompositeAggregationHistogramValuesSource {
a.missingBucket = &missingBucket
return a
}
// Order specifies the order in the values produced by this source.
// It can be either "asc" or "desc".
func (a *CompositeAggregationHistogramValuesSource) Order(order string) *CompositeAggregationHistogramValuesSource {
a.order = order
return a
}
// Asc ensures the order of the values produced is ascending.
func (a *CompositeAggregationHistogramValuesSource) Asc() *CompositeAggregationHistogramValuesSource {
a.order = "asc"
return a
}
// Desc ensures the order of the values produced is descending.
func (a *CompositeAggregationHistogramValuesSource) Desc() *CompositeAggregationHistogramValuesSource {
a.order = "desc"
return a
}
// Interval specifies the interval to use.
func (a *CompositeAggregationHistogramValuesSource) Interval(interval float64) *CompositeAggregationHistogramValuesSource {
a.interval = interval
return a
}
// Source returns the serializable JSON for this values source.
func (a *CompositeAggregationHistogramValuesSource) Source() (interface{}, error) {
source := make(map[string]interface{})
name := make(map[string]interface{})
source[a.name] = name
values := make(map[string]interface{})
name["histogram"] = values
// field
if a.field != "" {
values["field"] = a.field
}
// script
if a.script != nil {
src, err := a.script.Source()
if err != nil {
return nil, err
}
values["script"] = src
}
// missing
if a.missing != nil {
values["missing"] = a.missing
}
// missing_bucket
if a.missingBucket != nil {
values["missing_bucket"] = *a.missingBucket
}
// value_type
if a.valueType != "" {
values["value_type"] = a.valueType
}
// order
if a.order != "" {
values["order"] = a.order
}
// Histogram-related properties
values["interval"] = a.interval
return source, nil
}
// -- CompositeAggregationDateHistogramValuesSource --
// CompositeAggregationDateHistogramValuesSource is a source for the CompositeAggregation that handles date histograms
// it works very similar to a date histogram aggregation with slightly different syntax
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.4/search-aggregations-bucket-composite-aggregation.html#_date_histogram
// for details.
type CompositeAggregationDateHistogramValuesSource struct {
name string
field string
script *Script
valueType string
missing interface{}
missingBucket *bool
order string
interval interface{}
fixedInterval interface{}
calendarInterval interface{}
format string
timeZone string
}
// NewCompositeAggregationDateHistogramValuesSource creates and initializes
// a new CompositeAggregationDateHistogramValuesSource.
func NewCompositeAggregationDateHistogramValuesSource(name string) *CompositeAggregationDateHistogramValuesSource {
return &CompositeAggregationDateHistogramValuesSource{
name: name,
}
}
// Field to use for this source.
func (a *CompositeAggregationDateHistogramValuesSource) Field(field string) *CompositeAggregationDateHistogramValuesSource {
a.field = field
return a
}
// Script to use for this source.
func (a *CompositeAggregationDateHistogramValuesSource) Script(script *Script) *CompositeAggregationDateHistogramValuesSource {
a.script = script
return a
}
// ValueType specifies the type of values produced by this source,
// e.g. "string" or "date".
func (a *CompositeAggregationDateHistogramValuesSource) ValueType(valueType string) *CompositeAggregationDateHistogramValuesSource {
a.valueType = valueType
return a
}
// Missing specifies the value to use when the source finds a missing
// value in a document.
//
// Deprecated: Use MissingBucket instead.
func (a *CompositeAggregationDateHistogramValuesSource) Missing(missing interface{}) *CompositeAggregationDateHistogramValuesSource {
a.missing = missing
return a
}
// MissingBucket, if true, will create an explicit null bucket which represents
// documents with missing values.
func (a *CompositeAggregationDateHistogramValuesSource) MissingBucket(missingBucket bool) *CompositeAggregationDateHistogramValuesSource {
a.missingBucket = &missingBucket
return a
}
// Order specifies the order in the values produced by this source.
// It can be either "asc" or "desc".
func (a *CompositeAggregationDateHistogramValuesSource) Order(order string) *CompositeAggregationDateHistogramValuesSource {
a.order = order
return a
}
// Asc ensures the order of the values produced is ascending.
func (a *CompositeAggregationDateHistogramValuesSource) Asc() *CompositeAggregationDateHistogramValuesSource {
a.order = "asc"
return a
}
// Desc ensures the order of the values produced is descending.
func (a *CompositeAggregationDateHistogramValuesSource) Desc() *CompositeAggregationDateHistogramValuesSource {
a.order = "desc"
return a
}
// Interval to use for the date histogram, e.g. "1d" or a numeric value like "60".
//
// Deprecated: Use FixedInterval or CalendarInterval instead.
func (a *CompositeAggregationDateHistogramValuesSource) Interval(interval interface{}) *CompositeAggregationDateHistogramValuesSource {
a.interval = interval
return a
}
// FixedInterval to use for the date histogram, e.g. "1d" or a numeric value like "60".
func (a *CompositeAggregationDateHistogramValuesSource) FixedInterval(fixedInterval interface{}) *CompositeAggregationDateHistogramValuesSource {
a.fixedInterval = fixedInterval
return a
}
// CalendarInterval to use for the date histogram, e.g. "1d" or a numeric value like "60".
func (a *CompositeAggregationDateHistogramValuesSource) CalendarInterval(calendarInterval interface{}) *CompositeAggregationDateHistogramValuesSource {
a.calendarInterval = calendarInterval
return a
}
// Format to use for the date histogram, e.g. "strict_date_optional_time"
func (a *CompositeAggregationDateHistogramValuesSource) Format(format string) *CompositeAggregationDateHistogramValuesSource {
a.format = format
return a
}
// TimeZone to use for the dates.
func (a *CompositeAggregationDateHistogramValuesSource) TimeZone(timeZone string) *CompositeAggregationDateHistogramValuesSource {
a.timeZone = timeZone
return a
}
// Source returns the serializable JSON for this values source.
func (a *CompositeAggregationDateHistogramValuesSource) Source() (interface{}, error) {
source := make(map[string]interface{})
name := make(map[string]interface{})
source[a.name] = name
values := make(map[string]interface{})
name["date_histogram"] = values
// field
if a.field != "" {
values["field"] = a.field
}
// script
if a.script != nil {
src, err := a.script.Source()
if err != nil {
return nil, err
}
values["script"] = src
}
// missing
if a.missing != nil {
values["missing"] = a.missing
}
// missing_bucket
if a.missingBucket != nil {
values["missing_bucket"] = *a.missingBucket
}
// value_type
if a.valueType != "" {
values["value_type"] = a.valueType
}
// order
if a.order != "" {
values["order"] = a.order
}
if a.format != "" {
values["format"] = a.format
}
// DateHistogram-related properties
if v := a.interval; v != nil {
values["interval"] = v
}
if v := a.fixedInterval; v != nil {
values["fixed_interval"] = v
}
if v := a.calendarInterval; v != nil {
values["calendar_interval"] = v
}
// timeZone
if a.timeZone != "" {
values["time_zone"] = a.timeZone
}
return source, nil
}
================================================
FILE: search_aggs_bucket_composite_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestCompositeAggregation(t *testing.T) {
agg := NewCompositeAggregation().
Sources(
NewCompositeAggregationTermsValuesSource("my_terms").Field("a_term").Missing("N/A").Order("asc"),
NewCompositeAggregationHistogramValuesSource("my_histogram", 5).Field("price").Asc(),
NewCompositeAggregationDateHistogramValuesSource("my_date_histogram").CalendarInterval("1d").Field("purchase_date").Desc(),
).
Size(10).
AggregateAfter(map[string]interface{}{
"my_terms": "1",
"my_histogram": 2,
"my_date_histogram": "3",
})
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"composite":{"after":{"my_date_histogram":"3","my_histogram":2,"my_terms":"1"},"size":10,"sources":[{"my_terms":{"terms":{"field":"a_term","missing":"N/A","order":"asc"}}},{"my_histogram":{"histogram":{"field":"price","interval":5,"order":"asc"}}},{"my_date_histogram":{"date_histogram":{"calendar_interval":"1d","field":"purchase_date","order":"desc"}}}]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestCompositeAggregationTermsValuesSource(t *testing.T) {
in := NewCompositeAggregationTermsValuesSource("products").
Script(NewScript("doc['product'].value").Lang("painless"))
src, err := in.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"products":{"terms":{"script":{"lang":"painless","source":"doc['product'].value"}}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestCompositeAggregationHistogramValuesSource(t *testing.T) {
in := NewCompositeAggregationHistogramValuesSource("histo", 5).
Field("price")
src, err := in.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"histo":{"histogram":{"field":"price","interval":5}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestCompositeAggregationDateHistogramValuesSourceWithCalendarInterval(t *testing.T) {
in := NewCompositeAggregationDateHistogramValuesSource("date").CalendarInterval("1d").
Field("timestamp").
Format("strict_date_optional_time")
src, err := in.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"date":{"date_histogram":{"calendar_interval":"1d","field":"timestamp","format":"strict_date_optional_time"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestCompositeAggregationDateHistogramValuesSourceWithFixedInterval(t *testing.T) {
in := NewCompositeAggregationDateHistogramValuesSource("date").FixedInterval("1d").
Field("timestamp").
Format("strict_date_optional_time")
src, err := in.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"date":{"date_histogram":{"field":"timestamp","fixed_interval":"1d","format":"strict_date_optional_time"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_bucket_count_thresholds.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// BucketCountThresholds is used in e.g. terms and significant text aggregations.
type BucketCountThresholds struct {
MinDocCount *int64
ShardMinDocCount *int64
RequiredSize *int
ShardSize *int
}
================================================
FILE: search_aggs_bucket_date_histogram.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// DateHistogramAggregation is a multi-bucket aggregation similar to the
// histogram except it can only be applied on date values.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-datehistogram-aggregation.html
type DateHistogramAggregation struct {
field string
script *Script
missing interface{}
subAggregations map[string]Aggregation
meta map[string]interface{}
interval string
fixedInterval string
calendarInterval string
order string
orderAsc bool
minDocCount *int64
extendedBoundsMin interface{}
extendedBoundsMax interface{}
timeZone string
format string
offset string
keyed *bool
}
// NewDateHistogramAggregation creates a new DateHistogramAggregation.
func NewDateHistogramAggregation() *DateHistogramAggregation {
return &DateHistogramAggregation{
subAggregations: make(map[string]Aggregation),
}
}
// Field on which the aggregation is processed.
func (a *DateHistogramAggregation) Field(field string) *DateHistogramAggregation {
a.field = field
return a
}
func (a *DateHistogramAggregation) Script(script *Script) *DateHistogramAggregation {
a.script = script
return a
}
// Missing configures the value to use when documents miss a value.
func (a *DateHistogramAggregation) Missing(missing interface{}) *DateHistogramAggregation {
a.missing = missing
return a
}
func (a *DateHistogramAggregation) SubAggregation(name string, subAggregation Aggregation) *DateHistogramAggregation {
a.subAggregations[name] = subAggregation
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *DateHistogramAggregation) Meta(metaData map[string]interface{}) *DateHistogramAggregation {
a.meta = metaData
return a
}
// Interval by which the aggregation gets processed. This field
// will be replaced by the two FixedInterval and CalendarInterval
// fields (see below).
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.4/search-aggregations-bucket-datehistogram-aggregation.html
//
// Deprecated: This field will be removed in the future.
func (a *DateHistogramAggregation) Interval(interval string) *DateHistogramAggregation {
a.interval = interval
return a
}
// FixedInterval by which the aggregation gets processed.
//
// Allowed values are: "year", "1y", "quarter", "1q", "month", "1M",
// "week", "1w", "day", "1d", "hour", "1h", "minute", "1m", "second",
// or "1s". It also supports time settings like "1.5h".
//
// These units are not calendar-aware and are simply multiples of
// fixed, SI units. This is mutually exclusive with CalendarInterval.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.4/search-aggregations-bucket-datehistogram-aggregation.html
func (a *DateHistogramAggregation) FixedInterval(fixedInterval string) *DateHistogramAggregation {
a.fixedInterval = fixedInterval
return a
}
// CalendarInterval by which the aggregation gets processed.
//
// Allowed values are: "year" ("1y", "y"), "quarter" ("1q", "q"),
// "month" ("1M", "M"), "week" ("1w", "w"), "day" ("d", "1d")
//
// These units are calendar-aware, meaning they respect leap
// additions, variable days per month etc. This is mutually
// exclusive with FixedInterval.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.4/search-aggregations-bucket-datehistogram-aggregation.html
func (a *DateHistogramAggregation) CalendarInterval(calendarInterval string) *DateHistogramAggregation {
a.calendarInterval = calendarInterval
return a
}
// Order specifies the sort order. Valid values for order are:
// "_key", "_count", a sub-aggregation name, or a sub-aggregation name
// with a metric.
func (a *DateHistogramAggregation) Order(order string, asc bool) *DateHistogramAggregation {
a.order = order
a.orderAsc = asc
return a
}
func (a *DateHistogramAggregation) OrderByCount(asc bool) *DateHistogramAggregation {
// "order" : { "_count" : "asc" }
a.order = "_count"
a.orderAsc = asc
return a
}
func (a *DateHistogramAggregation) OrderByCountAsc() *DateHistogramAggregation {
return a.OrderByCount(true)
}
func (a *DateHistogramAggregation) OrderByCountDesc() *DateHistogramAggregation {
return a.OrderByCount(false)
}
func (a *DateHistogramAggregation) OrderByKey(asc bool) *DateHistogramAggregation {
// "order" : { "_key" : "asc" }
a.order = "_key"
a.orderAsc = asc
return a
}
func (a *DateHistogramAggregation) OrderByKeyAsc() *DateHistogramAggregation {
return a.OrderByKey(true)
}
func (a *DateHistogramAggregation) OrderByKeyDesc() *DateHistogramAggregation {
return a.OrderByKey(false)
}
// OrderByAggregation creates a bucket ordering strategy which sorts buckets
// based on a single-valued calc get.
func (a *DateHistogramAggregation) OrderByAggregation(aggName string, asc bool) *DateHistogramAggregation {
// {
// "aggs" : {
// "genders" : {
// "terms" : {
// "field" : "gender",
// "order" : { "avg_height" : "desc" }
// },
// "aggs" : {
// "avg_height" : { "avg" : { "field" : "height" } }
// }
// }
// }
// }
a.order = aggName
a.orderAsc = asc
return a
}
// OrderByAggregationAndMetric creates a bucket ordering strategy which
// sorts buckets based on a multi-valued calc get.
func (a *DateHistogramAggregation) OrderByAggregationAndMetric(aggName, metric string, asc bool) *DateHistogramAggregation {
// {
// "aggs" : {
// "genders" : {
// "terms" : {
// "field" : "gender",
// "order" : { "height_stats.avg" : "desc" }
// },
// "aggs" : {
// "height_stats" : { "stats" : { "field" : "height" } }
// }
// }
// }
// }
a.order = aggName + "." + metric
a.orderAsc = asc
return a
}
// MinDocCount sets the minimum document count per bucket.
// Buckets with less documents than this min value will not be returned.
func (a *DateHistogramAggregation) MinDocCount(minDocCount int64) *DateHistogramAggregation {
a.minDocCount = &minDocCount
return a
}
// TimeZone sets the timezone in which to translate dates before computing buckets.
func (a *DateHistogramAggregation) TimeZone(timeZone string) *DateHistogramAggregation {
a.timeZone = timeZone
return a
}
// Format sets the format to use for dates.
func (a *DateHistogramAggregation) Format(format string) *DateHistogramAggregation {
a.format = format
return a
}
// Offset sets the offset of time intervals in the histogram, e.g. "+6h".
func (a *DateHistogramAggregation) Offset(offset string) *DateHistogramAggregation {
a.offset = offset
return a
}
// ExtendedBounds accepts int, int64, string, or time.Time values.
// In case the lower value in the histogram would be greater than min or the
// upper value would be less than max, empty buckets will be generated.
func (a *DateHistogramAggregation) ExtendedBounds(min, max interface{}) *DateHistogramAggregation {
a.extendedBoundsMin = min
a.extendedBoundsMax = max
return a
}
// ExtendedBoundsMin accepts int, int64, string, or time.Time values.
func (a *DateHistogramAggregation) ExtendedBoundsMin(min interface{}) *DateHistogramAggregation {
a.extendedBoundsMin = min
return a
}
// ExtendedBoundsMax accepts int, int64, string, or time.Time values.
func (a *DateHistogramAggregation) ExtendedBoundsMax(max interface{}) *DateHistogramAggregation {
a.extendedBoundsMax = max
return a
}
// Keyed specifies whether to return the results with a keyed response (or not).
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-datehistogram-aggregation.html#_keyed_response_3.
func (a *DateHistogramAggregation) Keyed(keyed bool) *DateHistogramAggregation {
a.keyed = &keyed
return a
}
func (a *DateHistogramAggregation) Source() (interface{}, error) {
// Example:
// {
// "aggs" : {
// "articles_over_time" : {
// "date_histogram" : {
// "field" : "date",
// "fixed_interval" : "month"
// }
// }
// }
// }
//
// This method returns only the { "date_histogram" : { ... } } part.
source := make(map[string]interface{})
opts := make(map[string]interface{})
source["date_histogram"] = opts
// ValuesSourceAggregationBuilder
if a.field != "" {
opts["field"] = a.field
}
if a.script != nil {
src, err := a.script.Source()
if err != nil {
return nil, err
}
opts["script"] = src
}
if a.missing != nil {
opts["missing"] = a.missing
}
if s := a.interval; s != "" {
opts["interval"] = s
}
if s := a.fixedInterval; s != "" {
opts["fixed_interval"] = s
}
if s := a.calendarInterval; s != "" {
opts["calendar_interval"] = s
}
if a.minDocCount != nil {
opts["min_doc_count"] = *a.minDocCount
}
if a.order != "" {
o := make(map[string]interface{})
if a.orderAsc {
o[a.order] = "asc"
} else {
o[a.order] = "desc"
}
opts["order"] = o
}
if a.timeZone != "" {
opts["time_zone"] = a.timeZone
}
if a.offset != "" {
opts["offset"] = a.offset
}
if a.format != "" {
opts["format"] = a.format
}
if a.extendedBoundsMin != nil || a.extendedBoundsMax != nil {
bounds := make(map[string]interface{})
if a.extendedBoundsMin != nil {
bounds["min"] = a.extendedBoundsMin
}
if a.extendedBoundsMax != nil {
bounds["max"] = a.extendedBoundsMax
}
opts["extended_bounds"] = bounds
}
if a.keyed != nil {
opts["keyed"] = *a.keyed
}
// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_bucket_date_histogram_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestDateHistogramAggregationLegacyInterval(t *testing.T) {
agg := NewDateHistogramAggregation().
Field("date").
Interval("week").
Format("yyyy-MM").
TimeZone("UTC").
Offset("+6h")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"date_histogram":{"field":"date","format":"yyyy-MM","interval":"week","offset":"+6h","time_zone":"UTC"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestDateHistogramAggregationFixed(t *testing.T) {
agg := NewDateHistogramAggregation().
Field("date").
FixedInterval("month").
Format("yyyy-MM").
TimeZone("UTC").
Offset("+6h")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"date_histogram":{"field":"date","fixed_interval":"month","format":"yyyy-MM","offset":"+6h","time_zone":"UTC"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestDateHistogramAggregationCalendar(t *testing.T) {
agg := NewDateHistogramAggregation().
Field("date").
CalendarInterval("1d").
Format("yyyy-MM").
TimeZone("UTC").
Offset("+6h")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"date_histogram":{"calendar_interval":"1d","field":"date","format":"yyyy-MM","offset":"+6h","time_zone":"UTC"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestDateHistogramAggregationWithKeyedResponse(t *testing.T) {
agg := NewDateHistogramAggregation().Field("date").CalendarInterval("year").Missing("1900").Keyed(true)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"date_histogram":{"calendar_interval":"year","field":"date","keyed":true,"missing":"1900"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestDateHistogramAggregationWithMissing(t *testing.T) {
agg := NewDateHistogramAggregation().Field("date").CalendarInterval("year").Missing("1900")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"date_histogram":{"calendar_interval":"year","field":"date","missing":"1900"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_bucket_date_range.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"time"
)
// DateRangeAggregation is 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 aggregration includes the from value and excludes the to
// value for each range.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-daterange-aggregation.html
type DateRangeAggregation struct {
field string
script *Script
subAggregations map[string]Aggregation
meta map[string]interface{}
keyed *bool
unmapped *bool
timeZone string
format string
entries []DateRangeAggregationEntry
}
type DateRangeAggregationEntry struct {
Key string
From interface{}
To interface{}
}
func NewDateRangeAggregation() *DateRangeAggregation {
return &DateRangeAggregation{
subAggregations: make(map[string]Aggregation),
entries: make([]DateRangeAggregationEntry, 0),
}
}
func (a *DateRangeAggregation) Field(field string) *DateRangeAggregation {
a.field = field
return a
}
func (a *DateRangeAggregation) Script(script *Script) *DateRangeAggregation {
a.script = script
return a
}
func (a *DateRangeAggregation) SubAggregation(name string, subAggregation Aggregation) *DateRangeAggregation {
a.subAggregations[name] = subAggregation
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *DateRangeAggregation) Meta(metaData map[string]interface{}) *DateRangeAggregation {
a.meta = metaData
return a
}
func (a *DateRangeAggregation) Keyed(keyed bool) *DateRangeAggregation {
a.keyed = &keyed
return a
}
func (a *DateRangeAggregation) Unmapped(unmapped bool) *DateRangeAggregation {
a.unmapped = &unmapped
return a
}
func (a *DateRangeAggregation) TimeZone(timeZone string) *DateRangeAggregation {
a.timeZone = timeZone
return a
}
func (a *DateRangeAggregation) Format(format string) *DateRangeAggregation {
a.format = format
return a
}
func (a *DateRangeAggregation) AddRange(from, to interface{}) *DateRangeAggregation {
a.entries = append(a.entries, DateRangeAggregationEntry{From: from, To: to})
return a
}
func (a *DateRangeAggregation) AddRangeWithKey(key string, from, to interface{}) *DateRangeAggregation {
a.entries = append(a.entries, DateRangeAggregationEntry{Key: key, From: from, To: to})
return a
}
func (a *DateRangeAggregation) AddUnboundedTo(from interface{}) *DateRangeAggregation {
a.entries = append(a.entries, DateRangeAggregationEntry{From: from, To: nil})
return a
}
func (a *DateRangeAggregation) AddUnboundedToWithKey(key string, from interface{}) *DateRangeAggregation {
a.entries = append(a.entries, DateRangeAggregationEntry{Key: key, From: from, To: nil})
return a
}
func (a *DateRangeAggregation) AddUnboundedFrom(to interface{}) *DateRangeAggregation {
a.entries = append(a.entries, DateRangeAggregationEntry{From: nil, To: to})
return a
}
func (a *DateRangeAggregation) AddUnboundedFromWithKey(key string, to interface{}) *DateRangeAggregation {
a.entries = append(a.entries, DateRangeAggregationEntry{Key: key, From: nil, To: to})
return a
}
func (a *DateRangeAggregation) Lt(to interface{}) *DateRangeAggregation {
a.entries = append(a.entries, DateRangeAggregationEntry{From: nil, To: to})
return a
}
func (a *DateRangeAggregation) LtWithKey(key string, to interface{}) *DateRangeAggregation {
a.entries = append(a.entries, DateRangeAggregationEntry{Key: key, From: nil, To: to})
return a
}
func (a *DateRangeAggregation) Between(from, to interface{}) *DateRangeAggregation {
a.entries = append(a.entries, DateRangeAggregationEntry{From: from, To: to})
return a
}
func (a *DateRangeAggregation) BetweenWithKey(key string, from, to interface{}) *DateRangeAggregation {
a.entries = append(a.entries, DateRangeAggregationEntry{Key: key, From: from, To: to})
return a
}
func (a *DateRangeAggregation) Gt(from interface{}) *DateRangeAggregation {
a.entries = append(a.entries, DateRangeAggregationEntry{From: from, To: nil})
return a
}
func (a *DateRangeAggregation) GtWithKey(key string, from interface{}) *DateRangeAggregation {
a.entries = append(a.entries, DateRangeAggregationEntry{Key: key, From: from, To: nil})
return a
}
func (a *DateRangeAggregation) Source() (interface{}, error) {
// Example:
// {
// "aggs" : {
// "range" : {
// "date_range": {
// "field": "date",
// "format": "MM-yyy",
// "ranges": [
// { "to": "now-10M/M" },
// { "from": "now-10M/M" }
// ]
// }
// }
// }
// }
// }
//
// This method returns only the { "date_range" : { ... } } part.
source := make(map[string]interface{})
opts := make(map[string]interface{})
source["date_range"] = opts
// ValuesSourceAggregationBuilder
if a.field != "" {
opts["field"] = a.field
}
if a.script != nil {
src, err := a.script.Source()
if err != nil {
return nil, err
}
opts["script"] = src
}
if a.keyed != nil {
opts["keyed"] = *a.keyed
}
if a.unmapped != nil {
opts["unmapped"] = *a.unmapped
}
if a.timeZone != "" {
opts["time_zone"] = a.timeZone
}
if a.format != "" {
opts["format"] = a.format
}
var ranges []interface{}
for _, ent := range a.entries {
r := make(map[string]interface{})
if ent.Key != "" {
r["key"] = ent.Key
}
if ent.From != nil {
switch from := ent.From.(type) {
case int, int16, int32, int64, float32, float64:
r["from"] = from
case *int, *int16, *int32, *int64, *float32, *float64:
r["from"] = from
case time.Time:
r["from"] = from.Format(time.RFC3339)
case *time.Time:
r["from"] = from.Format(time.RFC3339)
case string:
r["from"] = from
case *string:
r["from"] = from
}
}
if ent.To != nil {
switch to := ent.To.(type) {
case int, int16, int32, int64, float32, float64:
r["to"] = to
case *int, *int16, *int32, *int64, *float32, *float64:
r["to"] = to
case time.Time:
r["to"] = to.Format(time.RFC3339)
case *time.Time:
r["to"] = to.Format(time.RFC3339)
case string:
r["to"] = to
case *string:
r["to"] = to
}
}
ranges = append(ranges, r)
}
opts["ranges"] = ranges
// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_bucket_date_range_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestDateRangeAggregation(t *testing.T) {
agg := NewDateRangeAggregation().Field("created_at").TimeZone("UTC")
agg = agg.AddRange(nil, "2012-12-31")
agg = agg.AddRange("2013-01-01", "2013-12-31")
agg = agg.AddRange("2014-01-01", nil)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"date_range":{"field":"created_at","ranges":[{"to":"2012-12-31"},{"from":"2013-01-01","to":"2013-12-31"},{"from":"2014-01-01"}],"time_zone":"UTC"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestDateRangeAggregationWithPointers(t *testing.T) {
d1 := "2012-12-31"
d2 := "2013-01-01"
d3 := "2013-12-31"
d4 := "2014-01-01"
agg := NewDateRangeAggregation().Field("created_at")
agg = agg.AddRange(nil, &d1)
agg = agg.AddRange(d2, &d3)
agg = agg.AddRange(d4, nil)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"date_range":{"field":"created_at","ranges":[{"to":"2012-12-31"},{"from":"2013-01-01","to":"2013-12-31"},{"from":"2014-01-01"}]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestDateRangeAggregationWithUnbounded(t *testing.T) {
agg := NewDateRangeAggregation().Field("created_at").
AddUnboundedFrom("2012-12-31").
AddRange("2013-01-01", "2013-12-31").
AddUnboundedTo("2014-01-01")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"date_range":{"field":"created_at","ranges":[{"to":"2012-12-31"},{"from":"2013-01-01","to":"2013-12-31"},{"from":"2014-01-01"}]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestDateRangeAggregationWithLtAndCo(t *testing.T) {
agg := NewDateRangeAggregation().Field("created_at").
Lt("2012-12-31").
Between("2013-01-01", "2013-12-31").
Gt("2014-01-01")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"date_range":{"field":"created_at","ranges":[{"to":"2012-12-31"},{"from":"2013-01-01","to":"2013-12-31"},{"from":"2014-01-01"}]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestDateRangeAggregationWithKeyedFlag(t *testing.T) {
agg := NewDateRangeAggregation().Field("created_at").
Keyed(true).
Lt("2012-12-31").
Between("2013-01-01", "2013-12-31").
Gt("2014-01-01")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"date_range":{"field":"created_at","keyed":true,"ranges":[{"to":"2012-12-31"},{"from":"2013-01-01","to":"2013-12-31"},{"from":"2014-01-01"}]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestDateRangeAggregationWithKeys(t *testing.T) {
agg := NewDateRangeAggregation().Field("created_at").
Keyed(true).
LtWithKey("pre-2012", "2012-12-31").
BetweenWithKey("2013", "2013-01-01", "2013-12-31").
GtWithKey("post-2013", "2014-01-01")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"date_range":{"field":"created_at","keyed":true,"ranges":[{"key":"pre-2012","to":"2012-12-31"},{"from":"2013-01-01","key":"2013","to":"2013-12-31"},{"from":"2014-01-01","key":"post-2013"}]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestDateRangeAggregationWithSpecialNames(t *testing.T) {
agg := NewDateRangeAggregation().Field("created_at").
AddRange("now-10M/M", "now+10M/M")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"date_range":{"field":"created_at","ranges":[{"from":"now-10M/M","to":"now+10M/M"}]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_bucket_diversified_sampler.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// DiversifiedSamplerAggregation Like the ‘sampler` aggregation this is a filtering aggregation used to limit any
// sub aggregations’ processing to a sample of the top-scoring documents. The diversified_sampler aggregation adds
// the ability to limit the number of matches that share a common value such as an "author".
//
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-diversified-sampler-aggregation.html
type DiversifiedSamplerAggregation struct {
subAggregations map[string]Aggregation
meta map[string]interface{}
field string
script *Script
shardSize int
maxDocsPerValue int
executionHint string
}
func NewDiversifiedSamplerAggregation() *DiversifiedSamplerAggregation {
return &DiversifiedSamplerAggregation{
shardSize: -1,
maxDocsPerValue: -1,
subAggregations: make(map[string]Aggregation),
}
}
func (a *DiversifiedSamplerAggregation) SubAggregation(name string, subAggregation Aggregation) *DiversifiedSamplerAggregation {
a.subAggregations[name] = subAggregation
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *DiversifiedSamplerAggregation) Meta(metaData map[string]interface{}) *DiversifiedSamplerAggregation {
a.meta = metaData
return a
}
// Field on which the aggregation is processed.
func (a *DiversifiedSamplerAggregation) Field(field string) *DiversifiedSamplerAggregation {
a.field = field
return a
}
func (a *DiversifiedSamplerAggregation) Script(script *Script) *DiversifiedSamplerAggregation {
a.script = script
return a
}
// ShardSize sets the maximum number of docs returned from each shard.
func (a *DiversifiedSamplerAggregation) ShardSize(shardSize int) *DiversifiedSamplerAggregation {
a.shardSize = shardSize
return a
}
func (a *DiversifiedSamplerAggregation) MaxDocsPerValue(maxDocsPerValue int) *DiversifiedSamplerAggregation {
a.maxDocsPerValue = maxDocsPerValue
return a
}
func (a *DiversifiedSamplerAggregation) ExecutionHint(hint string) *DiversifiedSamplerAggregation {
a.executionHint = hint
return a
}
func (a *DiversifiedSamplerAggregation) Source() (interface{}, error) {
// Example:
// {
// "aggs": {
// "my_unbiased_sample": {
// "diversified_sampler": {
// "shard_size": 200,
// "field" : "author"
// }
// }
// }
// }
//
// This method returns only the { "diversified_sampler" : { ... } } part.
source := make(map[string]interface{})
opts := make(map[string]interface{})
source["diversified_sampler"] = opts
if a.field != "" {
opts["field"] = a.field
}
if a.script != nil {
src, err := a.script.Source()
if err != nil {
return nil, err
}
opts["script"] = src
}
if a.shardSize >= 0 {
opts["shard_size"] = a.shardSize
}
if a.maxDocsPerValue >= 0 {
opts["max_docs_per_value"] = a.maxDocsPerValue
}
if a.executionHint != "" {
opts["execution_hint"] = a.executionHint
}
// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_bucket_diversified_sampler_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestDiversifiedSamplerAggregation(t *testing.T) {
keywordsAgg := NewSignificantTermsAggregation().Field("text")
agg := NewDiversifiedSamplerAggregation().
ShardSize(200).
Field("author").
SubAggregation("keywords", keywordsAgg)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"aggregations":{"keywords":{"significant_terms":{"field":"text"}}},"diversified_sampler":{"field":"author","shard_size":200}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_bucket_filter.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// FilterAggregation 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.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-filter-aggregation.html
type FilterAggregation struct {
filter Query
subAggregations map[string]Aggregation
meta map[string]interface{}
}
func NewFilterAggregation() *FilterAggregation {
return &FilterAggregation{
subAggregations: make(map[string]Aggregation),
}
}
func (a *FilterAggregation) SubAggregation(name string, subAggregation Aggregation) *FilterAggregation {
a.subAggregations[name] = subAggregation
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *FilterAggregation) Meta(metaData map[string]interface{}) *FilterAggregation {
a.meta = metaData
return a
}
func (a *FilterAggregation) Filter(filter Query) *FilterAggregation {
a.filter = filter
return a
}
func (a *FilterAggregation) Source() (interface{}, error) {
// Example:
// {
// "aggs" : {
// "in_stock_products" : {
// "filter" : { "range" : { "stock" : { "gt" : 0 } } }
// }
// }
// }
// This method returns only the { "filter" : {} } part.
src, err := a.filter.Source()
if err != nil {
return nil, err
}
source := make(map[string]interface{})
source["filter"] = src
// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_bucket_filter_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestFilterAggregation(t *testing.T) {
filter := NewRangeQuery("stock").Gt(0)
agg := NewFilterAggregation().Filter(filter)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"filter":{"range":{"stock":{"from":0,"include_lower":false,"include_upper":true,"to":null}}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestFilterAggregationWithSubAggregation(t *testing.T) {
avgPriceAgg := NewAvgAggregation().Field("price")
filter := NewRangeQuery("stock").Gt(0)
agg := NewFilterAggregation().Filter(filter).
SubAggregation("avg_price", avgPriceAgg)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"aggregations":{"avg_price":{"avg":{"field":"price"}}},"filter":{"range":{"stock":{"from":0,"include_lower":false,"include_upper":true,"to":null}}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestFilterAggregationWithMeta(t *testing.T) {
filter := NewRangeQuery("stock").Gt(0)
agg := NewFilterAggregation().Filter(filter).Meta(map[string]interface{}{"name": "Oliver"})
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"filter":{"range":{"stock":{"from":0,"include_lower":false,"include_upper":true,"to":null}}},"meta":{"name":"Oliver"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_bucket_filters.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import "errors"
// FiltersAggregation defines a multi bucket aggregations where each bucket
// is associated with a filter. Each bucket will collect all documents that
// match its associated filter.
//
// Notice that the caller has to decide whether to add filters by name
// (using FilterWithName) or unnamed filters (using Filter or Filters). One cannot
// use both named and unnamed filters.
//
// For details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-filters-aggregation.html
type FiltersAggregation struct {
unnamedFilters []Query
namedFilters map[string]Query
otherBucket *bool
otherBucketKey string
subAggregations map[string]Aggregation
meta map[string]interface{}
}
// NewFiltersAggregation initializes a new FiltersAggregation.
func NewFiltersAggregation() *FiltersAggregation {
return &FiltersAggregation{
unnamedFilters: make([]Query, 0),
namedFilters: make(map[string]Query),
subAggregations: make(map[string]Aggregation),
}
}
// Filter adds an unnamed filter. Notice that you can
// either use named or unnamed filters, but not both.
func (a *FiltersAggregation) Filter(filter Query) *FiltersAggregation {
a.unnamedFilters = append(a.unnamedFilters, filter)
return a
}
// Filters adds one or more unnamed filters. Notice that you can
// either use named or unnamed filters, but not both.
func (a *FiltersAggregation) Filters(filters ...Query) *FiltersAggregation {
if len(filters) > 0 {
a.unnamedFilters = append(a.unnamedFilters, filters...)
}
return a
}
// FilterWithName adds a filter with a specific name. Notice that you can
// either use named or unnamed filters, but not both.
func (a *FiltersAggregation) FilterWithName(name string, filter Query) *FiltersAggregation {
a.namedFilters[name] = filter
return a
}
// OtherBucket indicates whether to include a bucket for documents not
// matching any filter.
func (a *FiltersAggregation) OtherBucket(otherBucket bool) *FiltersAggregation {
a.otherBucket = &otherBucket
return a
}
// OtherBucketKey sets the key to use for the bucket for documents not
// matching any filter.
func (a *FiltersAggregation) OtherBucketKey(key string) *FiltersAggregation {
a.otherBucketKey = key
return a
}
// SubAggregation adds a sub-aggregation to this aggregation.
func (a *FiltersAggregation) SubAggregation(name string, subAggregation Aggregation) *FiltersAggregation {
a.subAggregations[name] = subAggregation
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *FiltersAggregation) Meta(metaData map[string]interface{}) *FiltersAggregation {
a.meta = metaData
return a
}
// Source returns the a JSON-serializable interface.
// If the aggregation is invalid, an error is returned. This may e.g. happen
// if you mixed named and unnamed filters.
func (a *FiltersAggregation) Source() (interface{}, error) {
// Example:
// {
// "aggs" : {
// "messages" : {
// "filters" : {
// "filters" : {
// "errors" : { "term" : { "body" : "error" }},
// "warnings" : { "term" : { "body" : "warning" }}
// }
// }
// }
// }
// }
// This method returns only the (outer) { "filters" : {} } part.
source := make(map[string]interface{})
filters := make(map[string]interface{})
source["filters"] = filters
if len(a.unnamedFilters) > 0 && len(a.namedFilters) > 0 {
return nil, errors.New("elastic: use either named or unnamed filters with FiltersAggregation but not both")
}
if len(a.unnamedFilters) > 0 {
arr := make([]interface{}, len(a.unnamedFilters))
for i, filter := range a.unnamedFilters {
src, err := filter.Source()
if err != nil {
return nil, err
}
arr[i] = src
}
filters["filters"] = arr
} else {
dict := make(map[string]interface{})
for key, filter := range a.namedFilters {
src, err := filter.Source()
if err != nil {
return nil, err
}
dict[key] = src
}
filters["filters"] = dict
}
if v := a.otherBucket; v != nil {
filters["other_bucket"] = *v
}
if v := a.otherBucketKey; v != "" {
filters["other_bucket_key"] = v
}
// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_bucket_filters_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestFiltersAggregationFilters(t *testing.T) {
f1 := NewRangeQuery("stock").Gt(0)
f2 := NewTermQuery("symbol", "GOOG")
agg := NewFiltersAggregation().Filters(f1, f2)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"filters":{"filters":[{"range":{"stock":{"from":0,"include_lower":false,"include_upper":true,"to":null}}},{"term":{"symbol":"GOOG"}}]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestFiltersAggregationFilterWithName(t *testing.T) {
f1 := NewRangeQuery("stock").Gt(0)
f2 := NewTermQuery("symbol", "GOOG")
agg := NewFiltersAggregation().
FilterWithName("f1", f1).
FilterWithName("f2", f2)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"filters":{"filters":{"f1":{"range":{"stock":{"from":0,"include_lower":false,"include_upper":true,"to":null}}},"f2":{"term":{"symbol":"GOOG"}}}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestFiltersAggregationWithKeyedAndNonKeyedFilters(t *testing.T) {
agg := NewFiltersAggregation().
Filter(NewTermQuery("symbol", "MSFT")). // unnamed
FilterWithName("one", NewTermQuery("symbol", "GOOG")) // named filter
_, err := agg.Source()
if err == nil {
t.Fatal("expected error, got nil")
}
}
func TestFiltersAggregationWithOtherBuckets(t *testing.T) {
agg := NewFiltersAggregation().
FilterWithName("errors", NewMatchQuery("body", "error")).
FilterWithName("warnings", NewMatchQuery("body", "warnings")).
OtherBucket(true).
OtherBucketKey("other")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"filters":{"filters":{"errors":{"match":{"body":{"query":"error"}}},"warnings":{"match":{"body":{"query":"warnings"}}}},"other_bucket":true,"other_bucket_key":"other"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestFiltersAggregationWithSubAggregation(t *testing.T) {
avgPriceAgg := NewAvgAggregation().Field("price")
f1 := NewRangeQuery("stock").Gt(0)
f2 := NewTermQuery("symbol", "GOOG")
agg := NewFiltersAggregation().Filters(f1, f2).SubAggregation("avg_price", avgPriceAgg)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"aggregations":{"avg_price":{"avg":{"field":"price"}}},"filters":{"filters":[{"range":{"stock":{"from":0,"include_lower":false,"include_upper":true,"to":null}}},{"term":{"symbol":"GOOG"}}]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestFiltersAggregationWithMetaData(t *testing.T) {
f1 := NewRangeQuery("stock").Gt(0)
f2 := NewTermQuery("symbol", "GOOG")
agg := NewFiltersAggregation().Filters(f1, f2).Meta(map[string]interface{}{"name": "Oliver"})
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"filters":{"filters":[{"range":{"stock":{"from":0,"include_lower":false,"include_upper":true,"to":null}}},{"term":{"symbol":"GOOG"}}]},"meta":{"name":"Oliver"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_bucket_geo_distance.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// GeoDistanceAggregation is a multi-bucket aggregation that works on geo_point fields
// and conceptually works very similar to the range aggregation.
// The user can define a point of origin and a set of distance range buckets.
// The aggregation evaluate the distance of each document value from
// the origin point and determines the buckets it belongs to based on
// the ranges (a document belongs to a bucket if the distance between the
// document and the origin falls within the distance range of the bucket).
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-geodistance-aggregation.html
type GeoDistanceAggregation struct {
field string
unit string
distanceType string
point string
ranges []geoDistAggRange
subAggregations map[string]Aggregation
meta map[string]interface{}
}
type geoDistAggRange struct {
Key string
From interface{}
To interface{}
}
func NewGeoDistanceAggregation() *GeoDistanceAggregation {
return &GeoDistanceAggregation{
subAggregations: make(map[string]Aggregation),
ranges: make([]geoDistAggRange, 0),
}
}
func (a *GeoDistanceAggregation) Field(field string) *GeoDistanceAggregation {
a.field = field
return a
}
func (a *GeoDistanceAggregation) Unit(unit string) *GeoDistanceAggregation {
a.unit = unit
return a
}
func (a *GeoDistanceAggregation) DistanceType(distanceType string) *GeoDistanceAggregation {
a.distanceType = distanceType
return a
}
func (a *GeoDistanceAggregation) Point(latLon string) *GeoDistanceAggregation {
a.point = latLon
return a
}
func (a *GeoDistanceAggregation) SubAggregation(name string, subAggregation Aggregation) *GeoDistanceAggregation {
a.subAggregations[name] = subAggregation
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *GeoDistanceAggregation) Meta(metaData map[string]interface{}) *GeoDistanceAggregation {
a.meta = metaData
return a
}
func (a *GeoDistanceAggregation) AddRange(from, to interface{}) *GeoDistanceAggregation {
a.ranges = append(a.ranges, geoDistAggRange{From: from, To: to})
return a
}
func (a *GeoDistanceAggregation) AddRangeWithKey(key string, from, to interface{}) *GeoDistanceAggregation {
a.ranges = append(a.ranges, geoDistAggRange{Key: key, From: from, To: to})
return a
}
func (a *GeoDistanceAggregation) AddUnboundedTo(from float64) *GeoDistanceAggregation {
a.ranges = append(a.ranges, geoDistAggRange{From: from, To: nil})
return a
}
func (a *GeoDistanceAggregation) AddUnboundedToWithKey(key string, from float64) *GeoDistanceAggregation {
a.ranges = append(a.ranges, geoDistAggRange{Key: key, From: from, To: nil})
return a
}
func (a *GeoDistanceAggregation) AddUnboundedFrom(to float64) *GeoDistanceAggregation {
a.ranges = append(a.ranges, geoDistAggRange{From: nil, To: to})
return a
}
func (a *GeoDistanceAggregation) AddUnboundedFromWithKey(key string, to float64) *GeoDistanceAggregation {
a.ranges = append(a.ranges, geoDistAggRange{Key: key, From: nil, To: to})
return a
}
func (a *GeoDistanceAggregation) Between(from, to interface{}) *GeoDistanceAggregation {
a.ranges = append(a.ranges, geoDistAggRange{From: from, To: to})
return a
}
func (a *GeoDistanceAggregation) BetweenWithKey(key string, from, to interface{}) *GeoDistanceAggregation {
a.ranges = append(a.ranges, geoDistAggRange{Key: key, From: from, To: to})
return a
}
func (a *GeoDistanceAggregation) Source() (interface{}, error) {
// Example:
// {
// "aggs" : {
// "rings_around_amsterdam" : {
// "geo_distance" : {
// "field" : "location",
// "origin" : "52.3760, 4.894",
// "ranges" : [
// { "to" : 100 },
// { "from" : 100, "to" : 300 },
// { "from" : 300 }
// ]
// }
// }
// }
// }
//
// This method returns only the { "range" : { ... } } part.
source := make(map[string]interface{})
opts := make(map[string]interface{})
source["geo_distance"] = opts
if a.field != "" {
opts["field"] = a.field
}
if a.unit != "" {
opts["unit"] = a.unit
}
if a.distanceType != "" {
opts["distance_type"] = a.distanceType
}
if a.point != "" {
opts["origin"] = a.point
}
var ranges []interface{}
for _, ent := range a.ranges {
r := make(map[string]interface{})
if ent.Key != "" {
r["key"] = ent.Key
}
if ent.From != nil {
switch from := ent.From.(type) {
case int, int16, int32, int64, float32, float64:
r["from"] = from
case *int, *int16, *int32, *int64, *float32, *float64:
r["from"] = from
case string:
r["from"] = from
case *string:
r["from"] = from
}
}
if ent.To != nil {
switch to := ent.To.(type) {
case int, int16, int32, int64, float32, float64:
r["to"] = to
case *int, *int16, *int32, *int64, *float32, *float64:
r["to"] = to
case string:
r["to"] = to
case *string:
r["to"] = to
}
}
ranges = append(ranges, r)
}
opts["ranges"] = ranges
// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_bucket_geo_distance_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestGeoDistanceAggregation(t *testing.T) {
agg := NewGeoDistanceAggregation().Field("location").Point("52.3760, 4.894")
agg = agg.AddRange(nil, 100)
agg = agg.AddRange(100, 300)
agg = agg.AddRange(300, nil)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"geo_distance":{"field":"location","origin":"52.3760, 4.894","ranges":[{"to":100},{"from":100,"to":300},{"from":300}]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestGeoDistanceAggregationWithPointers(t *testing.T) {
hundred := 100
threeHundred := 300
agg := NewGeoDistanceAggregation().Field("location").Point("52.3760, 4.894")
agg = agg.AddRange(nil, &hundred)
agg = agg.AddRange(hundred, &threeHundred)
agg = agg.AddRange(threeHundred, nil)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"geo_distance":{"field":"location","origin":"52.3760, 4.894","ranges":[{"to":100},{"from":100,"to":300},{"from":300}]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestGeoDistanceAggregationWithUnbounded(t *testing.T) {
agg := NewGeoDistanceAggregation().Field("location").Point("52.3760, 4.894")
agg = agg.AddUnboundedFrom(100)
agg = agg.AddRange(100, 300)
agg = agg.AddUnboundedTo(300)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"geo_distance":{"field":"location","origin":"52.3760, 4.894","ranges":[{"to":100},{"from":100,"to":300},{"from":300}]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestGeoDistanceAggregationWithMetaData(t *testing.T) {
agg := NewGeoDistanceAggregation().Field("location").Point("52.3760, 4.894")
agg = agg.AddRange(nil, 100)
agg = agg.AddRange(100, 300)
agg = agg.AddRange(300, nil)
agg = agg.Meta(map[string]interface{}{"name": "Oliver"})
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"geo_distance":{"field":"location","origin":"52.3760, 4.894","ranges":[{"to":100},{"from":100,"to":300},{"from":300}]},"meta":{"name":"Oliver"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_bucket_geohash_grid.go
================================================
package elastic
type GeoHashGridAggregation struct {
field string
precision interface{}
size int
shardSize int
subAggregations map[string]Aggregation
meta map[string]interface{}
}
func NewGeoHashGridAggregation() *GeoHashGridAggregation {
return &GeoHashGridAggregation{
subAggregations: make(map[string]Aggregation),
size: -1,
shardSize: -1,
}
}
func (a *GeoHashGridAggregation) Field(field string) *GeoHashGridAggregation {
a.field = field
return a
}
// Precision accepts the level as int value between 1 and 12 or Distance Units like "2km", "5mi" as described at
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/common-options.html#distance-units and
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-geohashgrid-aggregation.html
func (a *GeoHashGridAggregation) Precision(precision interface{}) *GeoHashGridAggregation {
a.precision = precision
return a
}
func (a *GeoHashGridAggregation) Size(size int) *GeoHashGridAggregation {
a.size = size
return a
}
func (a *GeoHashGridAggregation) ShardSize(shardSize int) *GeoHashGridAggregation {
a.shardSize = shardSize
return a
}
func (a *GeoHashGridAggregation) SubAggregation(name string, subAggregation Aggregation) *GeoHashGridAggregation {
a.subAggregations[name] = subAggregation
return a
}
func (a *GeoHashGridAggregation) Meta(metaData map[string]interface{}) *GeoHashGridAggregation {
a.meta = metaData
return a
}
func (a *GeoHashGridAggregation) Source() (interface{}, error) {
// Example:
// {
// "aggs": {
// "new_york": {
// "geohash_grid": {
// "field": "location",
// "precision": 5
// }
// }
// }
// }
source := make(map[string]interface{})
opts := make(map[string]interface{})
source["geohash_grid"] = opts
if a.field != "" {
opts["field"] = a.field
}
if a.precision != nil {
opts["precision"] = a.precision
}
if a.size != -1 {
opts["size"] = a.size
}
if a.shardSize != -1 {
opts["shard_size"] = a.shardSize
}
// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_bucket_geohash_grid_test.go
================================================
package elastic
import (
"encoding/json"
"testing"
)
func TestGeoHashGridAggregation(t *testing.T) {
agg := NewGeoHashGridAggregation().Field("location").Precision(5)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("Marshalling to JSON failed: %v", err)
}
got := string(data)
expected := `{"geohash_grid":{"field":"location","precision":5}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestGeoHashGridAggregation_PrecisionAsString(t *testing.T) {
agg := NewGeoHashGridAggregation().Field("location").Precision("2km")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("Marshalling to JSON failed: %v", err)
}
got := string(data)
expected := `{"geohash_grid":{"field":"location","precision":"2km"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestGeoHashGridAggregationWithMetaData(t *testing.T) {
agg := NewGeoHashGridAggregation().Field("location").Precision(5)
agg = agg.Meta(map[string]interface{}{"name": "Oliver"})
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("Marshalling to JSON failed: %v", err)
}
got := string(data)
expected := `{"geohash_grid":{"field":"location","precision":5},"meta":{"name":"Oliver"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestGeoHashGridAggregationWithSize(t *testing.T) {
agg := NewGeoHashGridAggregation().Field("location").Precision(5).Size(5)
agg = agg.Meta(map[string]interface{}{"name": "Oliver"})
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("Marshalling to JSON failed: %v", err)
}
got := string(data)
expected := `{"geohash_grid":{"field":"location","precision":5,"size":5},"meta":{"name":"Oliver"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestGeoHashGridAggregationWithShardSize(t *testing.T) {
agg := NewGeoHashGridAggregation().Field("location").Precision(5).ShardSize(5)
agg = agg.Meta(map[string]interface{}{"name": "Oliver"})
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("Marshalling to JSON failed: %v", err)
}
got := string(data)
expected := `{"geohash_grid":{"field":"location","precision":5,"shard_size":5},"meta":{"name":"Oliver"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_bucket_geotile_grid.go
================================================
package elastic
import "errors"
type GeoTileGridAggregation struct {
field string
precision int
size int
shardSize int
bounds *BoundingBox
subAggregations map[string]Aggregation
meta map[string]interface{}
}
// NewGeoTileGridAggregation Create new bucket aggregation of Geotile grid type
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-geotilegrid-aggregation.html
func NewGeoTileGridAggregation() *GeoTileGridAggregation {
return &GeoTileGridAggregation{
precision: -1,
size: -1,
shardSize: -1,
subAggregations: make(map[string]Aggregation),
}
}
// Field The name of the field indexed with GeoPoints. Mandatory.
func (a *GeoTileGridAggregation) Field(field string) *GeoTileGridAggregation {
a.field = field
return a
}
// Precision The integer zoom of the key used to define cells/buckets in the results. Defaults to 7. Values outside of [0,29] will be rejected. Optional.
func (a *GeoTileGridAggregation) Precision(precision int) *GeoTileGridAggregation {
a.precision = precision
return a
}
// Size The maximum number of buckets to return in the result structure. Optional.
func (a *GeoTileGridAggregation) Size(size int) *GeoTileGridAggregation {
a.size = size
return a
}
// ShardSize The maximum number of buckets to return from each shard. Optional.
func (a *GeoTileGridAggregation) ShardSize(shardSize int) *GeoTileGridAggregation {
a.shardSize = shardSize
return a
}
// Bounds The bounding box to filter the points in the bucket. Optional.
func (a *GeoTileGridAggregation) Bounds(boundingBox BoundingBox) *GeoTileGridAggregation {
a.bounds = &boundingBox
return a
}
// SubAggregation Adds a sub-aggregation to this aggregation.
func (a *GeoTileGridAggregation) SubAggregation(name string, subAggregation Aggregation) *GeoTileGridAggregation {
a.subAggregations[name] = subAggregation
return a
}
// Meta Sets the meta data to be included in the aggregation response.
func (a *GeoTileGridAggregation) Meta(metaData map[string]interface{}) *GeoTileGridAggregation {
a.meta = metaData
return a
}
// Source returns the a JSON-serializable interface.
func (a *GeoTileGridAggregation) Source() (interface{}, error) {
source := make(map[string]interface{})
opts := make(map[string]interface{})
source["geotile_grid"] = opts
if a.field == "" {
return nil, errors.New("elastic: 'field' is a mandatory parameter")
}
opts["field"] = a.field
if a.precision != -1 {
opts["precision"] = a.precision
}
if a.size != -1 {
opts["size"] = a.size
}
if a.shardSize != -1 {
opts["shard_size"] = a.shardSize
}
if a.bounds != nil {
opts["bounds"] = *a.bounds
}
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
// BoundingBox bounding box
type BoundingBox struct {
TopLeft GeoPoint `json:"top_left"`
BottomRight GeoPoint `json:"bottom_right"`
}
================================================
FILE: search_aggs_bucket_geotile_grid_test.go
================================================
package elastic
import (
"encoding/json"
"testing"
)
func TestGeoTileGridAggregation(t *testing.T) {
bounds := BoundingBox{
TopLeft: GeoPoint{
Lat: 55.145984,
Lon: 82.75195,
},
BottomRight: GeoPoint{
Lat: 54.830199,
Lon: 83.143839,
},
}
agg := NewGeoTileGridAggregation().
Field("location").
Precision(12).
Size(3).
ShardSize(5).
Bounds(bounds).
Meta(map[string]interface{}{"city": "Novosibirsk"})
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("Marshalling to JSON failed: %v", err)
}
got := string(data)
expected := `{"geotile_grid":{"bounds":{"top_left":{"lat":55.145984,"lon":82.75195},"bottom_right":{"lat":54.830199,"lon":83.143839}},"field":"location","precision":12,"shard_size":5,"size":3},"meta":{"city":"Novosibirsk"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_bucket_global.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// GlobalAggregation defines a single bucket of all the documents within
// the search execution context. This context is defined by the indices
// and the document types you’re searching on, but is not influenced
// by the search query itself.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-global-aggregation.html
type GlobalAggregation struct {
subAggregations map[string]Aggregation
meta map[string]interface{}
}
func NewGlobalAggregation() *GlobalAggregation {
return &GlobalAggregation{
subAggregations: make(map[string]Aggregation),
}
}
func (a *GlobalAggregation) SubAggregation(name string, subAggregation Aggregation) *GlobalAggregation {
a.subAggregations[name] = subAggregation
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *GlobalAggregation) Meta(metaData map[string]interface{}) *GlobalAggregation {
a.meta = metaData
return a
}
func (a *GlobalAggregation) Source() (interface{}, error) {
// Example:
// {
// "aggs" : {
// "all_products" : {
// "global" : {},
// "aggs" : {
// "avg_price" : { "avg" : { "field" : "price" } }
// }
// }
// }
// }
// This method returns only the { "global" : {} } part.
source := make(map[string]interface{})
opts := make(map[string]interface{})
source["global"] = opts
// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_bucket_global_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestGlobalAggregation(t *testing.T) {
agg := NewGlobalAggregation()
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"global":{}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestGlobalAggregationWithMetaData(t *testing.T) {
agg := NewGlobalAggregation().Meta(map[string]interface{}{"name": "Oliver"})
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"global":{},"meta":{"name":"Oliver"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_bucket_histogram.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// HistogramAggregation is a multi-bucket values source based aggregation
// that can be applied on numeric values extracted from the documents.
// It dynamically builds fixed size (a.k.a. interval) buckets over the
// values.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-histogram-aggregation.html
type HistogramAggregation struct {
field string
script *Script
missing interface{}
subAggregations map[string]Aggregation
meta map[string]interface{}
interval float64
order string
orderAsc bool
minDocCount *int64
minBounds *float64
maxBounds *float64
offset *float64
}
func NewHistogramAggregation() *HistogramAggregation {
return &HistogramAggregation{
subAggregations: make(map[string]Aggregation),
}
}
func (a *HistogramAggregation) Field(field string) *HistogramAggregation {
a.field = field
return a
}
func (a *HistogramAggregation) Script(script *Script) *HistogramAggregation {
a.script = script
return a
}
// Missing configures the value to use when documents miss a value.
func (a *HistogramAggregation) Missing(missing interface{}) *HistogramAggregation {
a.missing = missing
return a
}
func (a *HistogramAggregation) SubAggregation(name string, subAggregation Aggregation) *HistogramAggregation {
a.subAggregations[name] = subAggregation
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *HistogramAggregation) Meta(metaData map[string]interface{}) *HistogramAggregation {
a.meta = metaData
return a
}
// Interval for this builder, must be greater than 0.
func (a *HistogramAggregation) Interval(interval float64) *HistogramAggregation {
a.interval = interval
return a
}
// Order specifies the sort order. Valid values for order are:
// "_key", "_count", a sub-aggregation name, or a sub-aggregation name
// with a metric.
func (a *HistogramAggregation) Order(order string, asc bool) *HistogramAggregation {
a.order = order
a.orderAsc = asc
return a
}
func (a *HistogramAggregation) OrderByCount(asc bool) *HistogramAggregation {
// "order" : { "_count" : "asc" }
a.order = "_count"
a.orderAsc = asc
return a
}
func (a *HistogramAggregation) OrderByCountAsc() *HistogramAggregation {
return a.OrderByCount(true)
}
func (a *HistogramAggregation) OrderByCountDesc() *HistogramAggregation {
return a.OrderByCount(false)
}
func (a *HistogramAggregation) OrderByKey(asc bool) *HistogramAggregation {
// "order" : { "_key" : "asc" }
a.order = "_key"
a.orderAsc = asc
return a
}
func (a *HistogramAggregation) OrderByKeyAsc() *HistogramAggregation {
return a.OrderByKey(true)
}
func (a *HistogramAggregation) OrderByKeyDesc() *HistogramAggregation {
return a.OrderByKey(false)
}
// OrderByAggregation creates a bucket ordering strategy which sorts buckets
// based on a single-valued calc get.
func (a *HistogramAggregation) OrderByAggregation(aggName string, asc bool) *HistogramAggregation {
// {
// "aggs" : {
// "genders" : {
// "terms" : {
// "field" : "gender",
// "order" : { "avg_height" : "desc" }
// },
// "aggs" : {
// "avg_height" : { "avg" : { "field" : "height" } }
// }
// }
// }
// }
a.order = aggName
a.orderAsc = asc
return a
}
// OrderByAggregationAndMetric creates a bucket ordering strategy which
// sorts buckets based on a multi-valued calc get.
func (a *HistogramAggregation) OrderByAggregationAndMetric(aggName, metric string, asc bool) *HistogramAggregation {
// {
// "aggs" : {
// "genders" : {
// "terms" : {
// "field" : "gender",
// "order" : { "height_stats.avg" : "desc" }
// },
// "aggs" : {
// "height_stats" : { "stats" : { "field" : "height" } }
// }
// }
// }
// }
a.order = aggName + "." + metric
a.orderAsc = asc
return a
}
func (a *HistogramAggregation) MinDocCount(minDocCount int64) *HistogramAggregation {
a.minDocCount = &minDocCount
return a
}
func (a *HistogramAggregation) ExtendedBounds(min, max float64) *HistogramAggregation {
a.minBounds = &min
a.maxBounds = &max
return a
}
func (a *HistogramAggregation) ExtendedBoundsMin(min float64) *HistogramAggregation {
a.minBounds = &min
return a
}
func (a *HistogramAggregation) MinBounds(min float64) *HistogramAggregation {
a.minBounds = &min
return a
}
func (a *HistogramAggregation) ExtendedBoundsMax(max float64) *HistogramAggregation {
a.maxBounds = &max
return a
}
func (a *HistogramAggregation) MaxBounds(max float64) *HistogramAggregation {
a.maxBounds = &max
return a
}
// Offset into the histogram
func (a *HistogramAggregation) Offset(offset float64) *HistogramAggregation {
a.offset = &offset
return a
}
func (a *HistogramAggregation) Source() (interface{}, error) {
// Example:
// {
// "aggs" : {
// "prices" : {
// "histogram" : {
// "field" : "price",
// "interval" : 50
// }
// }
// }
// }
//
// This method returns only the { "histogram" : { ... } } part.
source := make(map[string]interface{})
opts := make(map[string]interface{})
source["histogram"] = opts
// ValuesSourceAggregationBuilder
if a.field != "" {
opts["field"] = a.field
}
if a.script != nil {
src, err := a.script.Source()
if err != nil {
return nil, err
}
opts["script"] = src
}
if a.missing != nil {
opts["missing"] = a.missing
}
opts["interval"] = a.interval
if a.order != "" {
o := make(map[string]interface{})
if a.orderAsc {
o[a.order] = "asc"
} else {
o[a.order] = "desc"
}
opts["order"] = o
}
if a.offset != nil {
opts["offset"] = *a.offset
}
if a.minDocCount != nil {
opts["min_doc_count"] = *a.minDocCount
}
if a.minBounds != nil || a.maxBounds != nil {
bounds := make(map[string]interface{})
if a.minBounds != nil {
bounds["min"] = a.minBounds
}
if a.maxBounds != nil {
bounds["max"] = a.maxBounds
}
opts["extended_bounds"] = bounds
}
// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_bucket_histogram_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestHistogramAggregation(t *testing.T) {
agg := NewHistogramAggregation().Field("price").Interval(50)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"histogram":{"field":"price","interval":50}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestHistogramAggregationWithMetaData(t *testing.T) {
agg := NewHistogramAggregation().Field("price").Offset(10).Interval(50).Meta(map[string]interface{}{"name": "Oliver"})
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"histogram":{"field":"price","interval":50,"offset":10},"meta":{"name":"Oliver"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestHistogramAggregationWithMissing(t *testing.T) {
agg := NewHistogramAggregation().Field("price").Interval(50).Missing("n/a")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"histogram":{"field":"price","interval":50,"missing":"n/a"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_bucket_ip_range.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// IPRangeAggregation is a range aggregation that is dedicated for
// IP addresses.
//
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-iprange-aggregation.html
type IPRangeAggregation struct {
field string
subAggregations map[string]Aggregation
meta map[string]interface{}
keyed *bool
entries []IPRangeAggregationEntry
}
type IPRangeAggregationEntry struct {
Key string
Mask string
From string
To string
}
func NewIPRangeAggregation() *IPRangeAggregation {
return &IPRangeAggregation{
subAggregations: make(map[string]Aggregation),
entries: make([]IPRangeAggregationEntry, 0),
}
}
func (a *IPRangeAggregation) Field(field string) *IPRangeAggregation {
a.field = field
return a
}
func (a *IPRangeAggregation) SubAggregation(name string, subAggregation Aggregation) *IPRangeAggregation {
a.subAggregations[name] = subAggregation
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *IPRangeAggregation) Meta(metaData map[string]interface{}) *IPRangeAggregation {
a.meta = metaData
return a
}
func (a *IPRangeAggregation) Keyed(keyed bool) *IPRangeAggregation {
a.keyed = &keyed
return a
}
func (a *IPRangeAggregation) AddMaskRange(mask string) *IPRangeAggregation {
a.entries = append(a.entries, IPRangeAggregationEntry{Mask: mask})
return a
}
func (a *IPRangeAggregation) AddMaskRangeWithKey(key, mask string) *IPRangeAggregation {
a.entries = append(a.entries, IPRangeAggregationEntry{Key: key, Mask: mask})
return a
}
func (a *IPRangeAggregation) AddRange(from, to string) *IPRangeAggregation {
a.entries = append(a.entries, IPRangeAggregationEntry{From: from, To: to})
return a
}
func (a *IPRangeAggregation) AddRangeWithKey(key, from, to string) *IPRangeAggregation {
a.entries = append(a.entries, IPRangeAggregationEntry{Key: key, From: from, To: to})
return a
}
func (a *IPRangeAggregation) AddUnboundedTo(from string) *IPRangeAggregation {
a.entries = append(a.entries, IPRangeAggregationEntry{From: from, To: ""})
return a
}
func (a *IPRangeAggregation) AddUnboundedToWithKey(key, from string) *IPRangeAggregation {
a.entries = append(a.entries, IPRangeAggregationEntry{Key: key, From: from, To: ""})
return a
}
func (a *IPRangeAggregation) AddUnboundedFrom(to string) *IPRangeAggregation {
a.entries = append(a.entries, IPRangeAggregationEntry{From: "", To: to})
return a
}
func (a *IPRangeAggregation) AddUnboundedFromWithKey(key, to string) *IPRangeAggregation {
a.entries = append(a.entries, IPRangeAggregationEntry{Key: key, From: "", To: to})
return a
}
func (a *IPRangeAggregation) Lt(to string) *IPRangeAggregation {
a.entries = append(a.entries, IPRangeAggregationEntry{From: "", To: to})
return a
}
func (a *IPRangeAggregation) LtWithKey(key, to string) *IPRangeAggregation {
a.entries = append(a.entries, IPRangeAggregationEntry{Key: key, From: "", To: to})
return a
}
func (a *IPRangeAggregation) Between(from, to string) *IPRangeAggregation {
a.entries = append(a.entries, IPRangeAggregationEntry{From: from, To: to})
return a
}
func (a *IPRangeAggregation) BetweenWithKey(key, from, to string) *IPRangeAggregation {
a.entries = append(a.entries, IPRangeAggregationEntry{Key: key, From: from, To: to})
return a
}
func (a *IPRangeAggregation) Gt(from string) *IPRangeAggregation {
a.entries = append(a.entries, IPRangeAggregationEntry{From: from, To: ""})
return a
}
func (a *IPRangeAggregation) GtWithKey(key, from string) *IPRangeAggregation {
a.entries = append(a.entries, IPRangeAggregationEntry{Key: key, From: from, To: ""})
return a
}
func (a *IPRangeAggregation) Source() (interface{}, error) {
// Example:
// {
// "aggs" : {
// "range" : {
// "ip_range": {
// "field": "ip",
// "ranges": [
// { "to": "10.0.0.5" },
// { "from": "10.0.0.5" }
// ]
// }
// }
// }
// }
// }
//
// This method returns only the { "ip_range" : { ... } } part.
source := make(map[string]interface{})
opts := make(map[string]interface{})
source["ip_range"] = opts
// ValuesSourceAggregationBuilder
if a.field != "" {
opts["field"] = a.field
}
if a.keyed != nil {
opts["keyed"] = *a.keyed
}
var ranges []interface{}
for _, ent := range a.entries {
r := make(map[string]interface{})
if ent.Key != "" {
r["key"] = ent.Key
}
if ent.Mask != "" {
r["mask"] = ent.Mask
} else {
if ent.From != "" {
r["from"] = ent.From
}
if ent.To != "" {
r["to"] = ent.To
}
}
ranges = append(ranges, r)
}
opts["ranges"] = ranges
// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_bucket_ip_range_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestIPRangeAggregation(t *testing.T) {
agg := NewIPRangeAggregation().Field("remote_ip")
agg = agg.AddRange("", "10.0.0.0")
agg = agg.AddRange("10.1.0.0", "10.1.255.255")
agg = agg.AddRange("10.2.0.0", "")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"ip_range":{"field":"remote_ip","ranges":[{"to":"10.0.0.0"},{"from":"10.1.0.0","to":"10.1.255.255"},{"from":"10.2.0.0"}]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestIPRangeAggregationMask(t *testing.T) {
agg := NewIPRangeAggregation().Field("remote_ip")
agg = agg.AddMaskRange("10.0.0.0/25")
agg = agg.AddMaskRange("10.0.0.127/25")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"ip_range":{"field":"remote_ip","ranges":[{"mask":"10.0.0.0/25"},{"mask":"10.0.0.127/25"}]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestIPRangeAggregationWithKeyedFlag(t *testing.T) {
agg := NewIPRangeAggregation().Field("remote_ip")
agg = agg.Keyed(true)
agg = agg.AddRange("", "10.0.0.0")
agg = agg.AddRange("10.1.0.0", "10.1.255.255")
agg = agg.AddRange("10.2.0.0", "")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"ip_range":{"field":"remote_ip","keyed":true,"ranges":[{"to":"10.0.0.0"},{"from":"10.1.0.0","to":"10.1.255.255"},{"from":"10.2.0.0"}]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestIPRangeAggregationWithKeys(t *testing.T) {
agg := NewIPRangeAggregation().Field("remote_ip")
agg = agg.Keyed(true)
agg = agg.LtWithKey("infinity", "10.0.0.5")
agg = agg.GtWithKey("and-beyond", "10.0.0.5")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"ip_range":{"field":"remote_ip","keyed":true,"ranges":[{"key":"infinity","to":"10.0.0.5"},{"from":"10.0.0.5","key":"and-beyond"}]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_bucket_missing.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// MissingAggregation is a field data based single bucket aggregation,
// that creates a bucket of all documents in the current document set context
// that are missing a field value (effectively, missing a field or having
// the configured NULL value set). This aggregator will often be used in
// conjunction with other field data bucket aggregators (such as ranges)
// to return information for all the documents that could not be placed
// in any of the other buckets due to missing field data values.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-missing-aggregation.html
type MissingAggregation struct {
field string
subAggregations map[string]Aggregation
meta map[string]interface{}
}
func NewMissingAggregation() *MissingAggregation {
return &MissingAggregation{
subAggregations: make(map[string]Aggregation),
}
}
func (a *MissingAggregation) Field(field string) *MissingAggregation {
a.field = field
return a
}
func (a *MissingAggregation) SubAggregation(name string, subAggregation Aggregation) *MissingAggregation {
a.subAggregations[name] = subAggregation
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *MissingAggregation) Meta(metaData map[string]interface{}) *MissingAggregation {
a.meta = metaData
return a
}
func (a *MissingAggregation) Source() (interface{}, error) {
// Example:
// {
// "aggs" : {
// "products_without_a_price" : {
// "missing" : { "field" : "price" }
// }
// }
// }
// This method returns only the { "missing" : { ... } } part.
source := make(map[string]interface{})
opts := make(map[string]interface{})
source["missing"] = opts
if a.field != "" {
opts["field"] = a.field
}
// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_bucket_missing_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestMissingAggregation(t *testing.T) {
agg := NewMissingAggregation().Field("price")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"missing":{"field":"price"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestMissingAggregationWithMetaData(t *testing.T) {
agg := NewMissingAggregation().Field("price").Meta(map[string]interface{}{"name": "Oliver"})
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"meta":{"name":"Oliver"},"missing":{"field":"price"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_bucket_multi_terms.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// MultiTermsAggregation is a multi-bucket value source based aggregation
// where buckets are dynamically built - one per unique set of values.
// The multi terms aggregation is very similar to the terms aggregation,
// however in most cases it will be slower than the terms aggregation and will
// consume more memory. Therefore, if the same set of fields is constantly
// used, it would be more efficient to index a combined key for this fields
// as a separate field and use the terms aggregation on this field.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.13/search-aggregations-bucket-multi-terms-aggregation.html
type MultiTermsAggregation struct {
multiTerms []MultiTerm
subAggregations map[string]Aggregation
meta map[string]interface{}
size *int
shardSize *int
minDocCount *int
shardMinDocCount *int
collectionMode string
showTermDocCountError *bool
order []MultiTermsOrder
}
// NewMultiTermsAggregation initializes a new MultiTermsAggregation.
func NewMultiTermsAggregation() *MultiTermsAggregation {
return &MultiTermsAggregation{
subAggregations: make(map[string]Aggregation),
}
}
// Terms adds a slice of field names to return in the aggregation.
//
// Notice that it appends to existing terms, so you can use Terms more than
// once, and mix with MultiTerms method.
func (a *MultiTermsAggregation) Terms(fields ...string) *MultiTermsAggregation {
for _, field := range fields {
a.multiTerms = append(a.multiTerms, MultiTerm{Field: field})
}
return a
}
// MultiTerms adds a slice of MultiTerm instances to return in the aggregation.
//
// Notice that it appends to existing terms, so you can use MultiTerms more
// than once, and mix with Terms method.
func (a *MultiTermsAggregation) MultiTerms(multiTerms ...MultiTerm) *MultiTermsAggregation {
a.multiTerms = append(a.multiTerms, multiTerms...)
return a
}
func (a *MultiTermsAggregation) SubAggregation(name string, subAggregation Aggregation) *MultiTermsAggregation {
a.subAggregations[name] = subAggregation
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *MultiTermsAggregation) Meta(metaData map[string]interface{}) *MultiTermsAggregation {
a.meta = metaData
return a
}
func (a *MultiTermsAggregation) Size(size int) *MultiTermsAggregation {
a.size = &size
return a
}
func (a *MultiTermsAggregation) ShardSize(shardSize int) *MultiTermsAggregation {
a.shardSize = &shardSize
return a
}
func (a *MultiTermsAggregation) MinDocCount(minDocCount int) *MultiTermsAggregation {
a.minDocCount = &minDocCount
return a
}
func (a *MultiTermsAggregation) ShardMinDocCount(shardMinDocCount int) *MultiTermsAggregation {
a.shardMinDocCount = &shardMinDocCount
return a
}
func (a *MultiTermsAggregation) Order(order string, asc bool) *MultiTermsAggregation {
a.order = append(a.order, MultiTermsOrder{Field: order, Ascending: asc})
return a
}
func (a *MultiTermsAggregation) OrderByCount(asc bool) *MultiTermsAggregation {
// "order" : { "_count" : "asc" }
a.order = append(a.order, MultiTermsOrder{Field: "_count", Ascending: asc})
return a
}
func (a *MultiTermsAggregation) OrderByCountAsc() *MultiTermsAggregation {
return a.OrderByCount(true)
}
func (a *MultiTermsAggregation) OrderByCountDesc() *MultiTermsAggregation {
return a.OrderByCount(false)
}
func (a *MultiTermsAggregation) OrderByKey(asc bool) *MultiTermsAggregation {
// "order" : { "_term" : "asc" }
a.order = append(a.order, MultiTermsOrder{Field: "_key", Ascending: asc})
return a
}
func (a *MultiTermsAggregation) OrderByKeyAsc() *MultiTermsAggregation {
return a.OrderByKey(true)
}
func (a *MultiTermsAggregation) OrderByKeyDesc() *MultiTermsAggregation {
return a.OrderByKey(false)
}
// OrderByAggregation creates a bucket ordering strategy which sorts buckets
// based on a single-valued calc get.
func (a *MultiTermsAggregation) OrderByAggregation(aggName string, asc bool) *MultiTermsAggregation {
// {
// "aggs": {
// "genres_and_products": {
// "multi_terms": {
// "terms": [
// {
// "field": "genre"
// },
// {
// "field": "product"
// }
// ],
// "order": {
// "total_quantity": "desc"
// }
// },
// "aggs": {
// "total_quantity": {
// "sum": {
// "field": "quantity"
// }
// }
// }
// }
// }
// }
a.order = append(a.order, MultiTermsOrder{Field: aggName, Ascending: asc})
return a
}
// OrderByAggregationAndMetric creates a bucket ordering strategy which
// sorts buckets based on a multi-valued calc get.
func (a *MultiTermsAggregation) OrderByAggregationAndMetric(aggName, metric string, asc bool) *MultiTermsAggregation {
// {
// "aggs": {
// "genres_and_products": {
// "multi_terms": {
// "terms": [
// {
// "field": "genre"
// },
// {
// "field": "product"
// }
// ],
// "order": {
// "total_quantity": "desc"
// }
// },
// "aggs": {
// "total_quantity": {
// "sum": {
// "field": "quantity"
// }
// }
// }
// }
// }
// }
a.order = append(a.order, MultiTermsOrder{Field: aggName + "." + metric, Ascending: asc})
return a
}
// Collection mode can be depth_first or breadth_first as of 1.4.0.
func (a *MultiTermsAggregation) CollectionMode(collectionMode string) *MultiTermsAggregation {
a.collectionMode = collectionMode
return a
}
func (a *MultiTermsAggregation) ShowTermDocCountError(showTermDocCountError bool) *MultiTermsAggregation {
a.showTermDocCountError = &showTermDocCountError
return a
}
func (a *MultiTermsAggregation) Source() (interface{}, error) {
// Example:
// {
// "aggs": {
// "genres_and_products": {
// "multi_terms": {
// "terms": [
// {
// "field": "genre"
// },
// {
// "field": "product"
// }
// ]
// }
// }
// }
// }
// This method returns only the "multi_terms": { "terms": [ { "field": "genre" }, { "field": "product" } ] } part.
source := make(map[string]interface{})
opts := make(map[string]interface{})
source["multi_terms"] = opts
// ValuesSourceAggregationBuilder
terms := make([]interface{}, len(a.multiTerms))
for i := range a.multiTerms {
s, err := a.multiTerms[i].Source()
if err != nil {
return nil, err
}
terms[i] = s
}
opts["terms"] = terms
// TermsBuilder
if a.size != nil && *a.size >= 0 {
opts["size"] = *a.size
}
if a.shardSize != nil && *a.shardSize >= 0 {
opts["shard_size"] = *a.shardSize
}
if a.minDocCount != nil && *a.minDocCount >= 0 {
opts["min_doc_count"] = *a.minDocCount
}
if a.shardMinDocCount != nil && *a.shardMinDocCount >= 0 {
opts["shard_min_doc_count"] = *a.shardMinDocCount
}
if a.showTermDocCountError != nil {
opts["show_term_doc_count_error"] = *a.showTermDocCountError
}
if a.collectionMode != "" {
opts["collect_mode"] = a.collectionMode
}
if len(a.order) > 0 {
var orderSlice []interface{}
for _, order := range a.order {
src, err := order.Source()
if err != nil {
return nil, err
}
orderSlice = append(orderSlice, src)
}
opts["order"] = orderSlice
}
// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
// MultiTermsOrder specifies a single order field for a multi terms aggregation.
type MultiTermsOrder struct {
Field string
Ascending bool
}
// Source returns serializable JSON of the MultiTermsOrder.
func (order *MultiTermsOrder) Source() (interface{}, error) {
source := make(map[string]string)
if order.Ascending {
source[order.Field] = "asc"
} else {
source[order.Field] = "desc"
}
return source, nil
}
// MultiTerm specifies a single term field for a multi terms aggregation.
type MultiTerm struct {
Field string
Missing interface{}
}
// Source returns serializable JSON of the MultiTerm.
func (term *MultiTerm) Source() (interface{}, error) {
source := make(map[string]interface{})
source["field"] = term.Field
if term.Missing != nil {
source["missing"] = term.Missing
}
return source, nil
}
================================================
FILE: search_aggs_bucket_multi_terms_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestMultiTermsAggregation(t *testing.T) {
agg := NewMultiTermsAggregation().Terms("genre", "product")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"multi_terms":{"terms":[{"field":"genre"},{"field":"product"}]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestMultiTermsAggregationWithMultiTerms(t *testing.T) {
agg := NewMultiTermsAggregation().MultiTerms(
MultiTerm{Field: "genre", Missing: "n/a"},
MultiTerm{Field: "product", Missing: "n/a"},
)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"multi_terms":{"terms":[{"field":"genre","missing":"n/a"},{"field":"product","missing":"n/a"}]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestMultiTermsAggregationWithSubAggregation(t *testing.T) {
subAgg := NewAvgAggregation().Field("height")
agg := NewMultiTermsAggregation().Terms("genre", "product").Size(10).
OrderByAggregation("avg_height", false)
agg = agg.SubAggregation("avg_height", subAgg)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"aggregations":{"avg_height":{"avg":{"field":"height"}}},"multi_terms":{"order":[{"avg_height":"desc"}],"size":10,"terms":[{"field":"genre"},{"field":"product"}]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestMultiTermsAggregationWithMultipleSubAggregation(t *testing.T) {
subAgg1 := NewAvgAggregation().Field("height")
subAgg2 := NewAvgAggregation().Field("width")
agg := NewMultiTermsAggregation().Terms("genre", "product").Size(10).
OrderByAggregation("avg_height", false)
agg = agg.SubAggregation("avg_height", subAgg1)
agg = agg.SubAggregation("avg_width", subAgg2)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"aggregations":{"avg_height":{"avg":{"field":"height"}},"avg_width":{"avg":{"field":"width"}}},"multi_terms":{"order":[{"avg_height":"desc"}],"size":10,"terms":[{"field":"genre"},{"field":"product"}]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestMultiTermsAggregationWithMetaData(t *testing.T) {
agg := NewMultiTermsAggregation().Terms("genre", "product").Size(10).OrderByKeyDesc()
agg = agg.Meta(map[string]interface{}{"name": "Oliver"})
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"meta":{"name":"Oliver"},"multi_terms":{"order":[{"_key":"desc"}],"size":10,"terms":[{"field":"genre"},{"field":"product"}]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestMultiTermsAggregationWithMissing(t *testing.T) {
agg := NewMultiTermsAggregation().MultiTerms(
MultiTerm{Field: "genre"},
MultiTerm{Field: "product", Missing: "n/a"},
).Size(10)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"multi_terms":{"size":10,"terms":[{"field":"genre"},{"field":"product","missing":"n/a"}]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_bucket_nested.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// NestedAggregation is a special single bucket aggregation that enables
// aggregating nested documents.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-nested-aggregation.html
type NestedAggregation struct {
path string
subAggregations map[string]Aggregation
meta map[string]interface{}
}
func NewNestedAggregation() *NestedAggregation {
return &NestedAggregation{
subAggregations: make(map[string]Aggregation),
}
}
func (a *NestedAggregation) SubAggregation(name string, subAggregation Aggregation) *NestedAggregation {
a.subAggregations[name] = subAggregation
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *NestedAggregation) Meta(metaData map[string]interface{}) *NestedAggregation {
a.meta = metaData
return a
}
func (a *NestedAggregation) Path(path string) *NestedAggregation {
a.path = path
return a
}
func (a *NestedAggregation) Source() (interface{}, error) {
// Example:
// {
// "query" : {
// "match" : { "name" : "led tv" }
// }
// "aggs" : {
// "resellers" : {
// "nested" : {
// "path" : "resellers"
// },
// "aggs" : {
// "min_price" : { "min" : { "field" : "resellers.price" } }
// }
// }
// }
// }
// This method returns only the { "nested" : {} } part.
source := make(map[string]interface{})
opts := make(map[string]interface{})
source["nested"] = opts
opts["path"] = a.path
// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_bucket_nested_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestNestedAggregation(t *testing.T) {
agg := NewNestedAggregation().Path("resellers")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"nested":{"path":"resellers"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestNestedAggregationWithSubAggregation(t *testing.T) {
minPriceAgg := NewMinAggregation().Field("resellers.price")
agg := NewNestedAggregation().Path("resellers").SubAggregation("min_price", minPriceAgg)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"aggregations":{"min_price":{"min":{"field":"resellers.price"}}},"nested":{"path":"resellers"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestNestedAggregationWithMetaData(t *testing.T) {
agg := NewNestedAggregation().Path("resellers").Meta(map[string]interface{}{"name": "Oliver"})
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"meta":{"name":"Oliver"},"nested":{"path":"resellers"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_bucket_range.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"time"
)
// RangeAggregation is a multi-bucket value source based aggregation that
// enables the user to define a set of ranges - each representing a bucket.
// During the aggregation process, the values extracted from each document
// will be checked against each bucket range and "bucket" the
// relevant/matching document. Note that this aggregration includes the
// from value and excludes the to value for each range.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-range-aggregation.html
type RangeAggregation struct {
field string
script *Script
missing interface{}
subAggregations map[string]Aggregation
meta map[string]interface{}
keyed *bool
unmapped *bool
entries []rangeAggregationEntry
}
type rangeAggregationEntry struct {
Key string
From interface{}
To interface{}
}
func NewRangeAggregation() *RangeAggregation {
return &RangeAggregation{
subAggregations: make(map[string]Aggregation),
entries: make([]rangeAggregationEntry, 0),
}
}
func (a *RangeAggregation) Field(field string) *RangeAggregation {
a.field = field
return a
}
func (a *RangeAggregation) Script(script *Script) *RangeAggregation {
a.script = script
return a
}
// Missing configures the value to use when documents miss a value.
func (a *RangeAggregation) Missing(missing interface{}) *RangeAggregation {
a.missing = missing
return a
}
func (a *RangeAggregation) SubAggregation(name string, subAggregation Aggregation) *RangeAggregation {
a.subAggregations[name] = subAggregation
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *RangeAggregation) Meta(metaData map[string]interface{}) *RangeAggregation {
a.meta = metaData
return a
}
func (a *RangeAggregation) Keyed(keyed bool) *RangeAggregation {
a.keyed = &keyed
return a
}
func (a *RangeAggregation) Unmapped(unmapped bool) *RangeAggregation {
a.unmapped = &unmapped
return a
}
func (a *RangeAggregation) AddRange(from, to interface{}) *RangeAggregation {
a.entries = append(a.entries, rangeAggregationEntry{From: from, To: to})
return a
}
func (a *RangeAggregation) AddRangeWithKey(key string, from, to interface{}) *RangeAggregation {
a.entries = append(a.entries, rangeAggregationEntry{Key: key, From: from, To: to})
return a
}
func (a *RangeAggregation) AddUnboundedTo(from interface{}) *RangeAggregation {
a.entries = append(a.entries, rangeAggregationEntry{From: from, To: nil})
return a
}
func (a *RangeAggregation) AddUnboundedToWithKey(key string, from interface{}) *RangeAggregation {
a.entries = append(a.entries, rangeAggregationEntry{Key: key, From: from, To: nil})
return a
}
func (a *RangeAggregation) AddUnboundedFrom(to interface{}) *RangeAggregation {
a.entries = append(a.entries, rangeAggregationEntry{From: nil, To: to})
return a
}
func (a *RangeAggregation) AddUnboundedFromWithKey(key string, to interface{}) *RangeAggregation {
a.entries = append(a.entries, rangeAggregationEntry{Key: key, From: nil, To: to})
return a
}
func (a *RangeAggregation) Lt(to interface{}) *RangeAggregation {
a.entries = append(a.entries, rangeAggregationEntry{From: nil, To: to})
return a
}
func (a *RangeAggregation) LtWithKey(key string, to interface{}) *RangeAggregation {
a.entries = append(a.entries, rangeAggregationEntry{Key: key, From: nil, To: to})
return a
}
func (a *RangeAggregation) Between(from, to interface{}) *RangeAggregation {
a.entries = append(a.entries, rangeAggregationEntry{From: from, To: to})
return a
}
func (a *RangeAggregation) BetweenWithKey(key string, from, to interface{}) *RangeAggregation {
a.entries = append(a.entries, rangeAggregationEntry{Key: key, From: from, To: to})
return a
}
func (a *RangeAggregation) Gt(from interface{}) *RangeAggregation {
a.entries = append(a.entries, rangeAggregationEntry{From: from, To: nil})
return a
}
func (a *RangeAggregation) GtWithKey(key string, from interface{}) *RangeAggregation {
a.entries = append(a.entries, rangeAggregationEntry{Key: key, From: from, To: nil})
return a
}
func (a *RangeAggregation) Source() (interface{}, error) {
// Example:
// {
// "aggs" : {
// "price_ranges" : {
// "range" : {
// "field" : "price",
// "ranges" : [
// { "to" : 50 },
// { "from" : 50, "to" : 100 },
// { "from" : 100 }
// ]
// }
// }
// }
// }
//
// This method returns only the { "range" : { ... } } part.
source := make(map[string]interface{})
opts := make(map[string]interface{})
source["range"] = opts
// ValuesSourceAggregationBuilder
if a.field != "" {
opts["field"] = a.field
}
if a.script != nil {
src, err := a.script.Source()
if err != nil {
return nil, err
}
opts["script"] = src
}
if a.missing != nil {
opts["missing"] = a.missing
}
if a.keyed != nil {
opts["keyed"] = *a.keyed
}
if a.unmapped != nil {
opts["unmapped"] = *a.unmapped
}
var ranges []interface{}
for _, ent := range a.entries {
r := make(map[string]interface{})
if ent.Key != "" {
r["key"] = ent.Key
}
if ent.From != nil {
switch from := ent.From.(type) {
case int, int16, int32, int64, float32, float64:
r["from"] = from
case *int, *int16, *int32, *int64, *float32, *float64:
r["from"] = from
case time.Time:
r["from"] = from.Format(time.RFC3339)
case *time.Time:
r["from"] = from.Format(time.RFC3339)
case string:
r["from"] = from
case *string:
r["from"] = from
}
}
if ent.To != nil {
switch to := ent.To.(type) {
case int, int16, int32, int64, float32, float64:
r["to"] = to
case *int, *int16, *int32, *int64, *float32, *float64:
r["to"] = to
case time.Time:
r["to"] = to.Format(time.RFC3339)
case *time.Time:
r["to"] = to.Format(time.RFC3339)
case string:
r["to"] = to
case *string:
r["to"] = to
}
}
ranges = append(ranges, r)
}
opts["ranges"] = ranges
// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_bucket_range_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestRangeAggregation(t *testing.T) {
agg := NewRangeAggregation().Field("price")
agg = agg.AddRange(nil, 50)
agg = agg.AddRange(50, 100)
agg = agg.AddRange(100, nil)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"range":{"field":"price","ranges":[{"to":50},{"from":50,"to":100},{"from":100}]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestRangeAggregationWithPointers(t *testing.T) {
fifty := 50
hundred := 100
agg := NewRangeAggregation().Field("price")
agg = agg.AddRange(nil, &fifty)
agg = agg.AddRange(fifty, &hundred)
agg = agg.AddRange(hundred, nil)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"range":{"field":"price","ranges":[{"to":50},{"from":50,"to":100},{"from":100}]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestRangeAggregationWithUnbounded(t *testing.T) {
agg := NewRangeAggregation().Field("field_name").
AddUnboundedFrom(50).
AddRange(20, 70).
AddRange(70, 120).
AddUnboundedTo(150)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"range":{"field":"field_name","ranges":[{"to":50},{"from":20,"to":70},{"from":70,"to":120},{"from":150}]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestRangeAggregationWithLtAndCo(t *testing.T) {
agg := NewRangeAggregation().Field("field_name").
Lt(50).
Between(20, 70).
Between(70, 120).
Gt(150)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"range":{"field":"field_name","ranges":[{"to":50},{"from":20,"to":70},{"from":70,"to":120},{"from":150}]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestRangeAggregationWithKeyedFlag(t *testing.T) {
agg := NewRangeAggregation().Field("field_name").
Keyed(true).
Lt(50).
Between(20, 70).
Between(70, 120).
Gt(150)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"range":{"field":"field_name","keyed":true,"ranges":[{"to":50},{"from":20,"to":70},{"from":70,"to":120},{"from":150}]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestRangeAggregationWithKeys(t *testing.T) {
agg := NewRangeAggregation().Field("field_name").
Keyed(true).
LtWithKey("cheap", 50).
BetweenWithKey("affordable", 20, 70).
BetweenWithKey("average", 70, 120).
GtWithKey("expensive", 150)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"range":{"field":"field_name","keyed":true,"ranges":[{"key":"cheap","to":50},{"from":20,"key":"affordable","to":70},{"from":70,"key":"average","to":120},{"from":150,"key":"expensive"}]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestRangeAggregationWithMetaData(t *testing.T) {
agg := NewRangeAggregation().Field("price").Meta(map[string]interface{}{"name": "Oliver"})
agg = agg.AddRange(nil, 50)
agg = agg.AddRange(50, 100)
agg = agg.AddRange(100, nil)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"meta":{"name":"Oliver"},"range":{"field":"price","ranges":[{"to":50},{"from":50,"to":100},{"from":100}]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestRangeAggregationWithMissing(t *testing.T) {
agg := NewRangeAggregation().Field("price").Missing(0)
agg = agg.AddRange(nil, 50)
agg = agg.AddRange(50, 100)
agg = agg.AddRange(100, nil)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"range":{"field":"price","missing":0,"ranges":[{"to":50},{"from":50,"to":100},{"from":100}]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_bucket_rare_terms.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// RareTermsAggregation is a multi-bucket value source based aggregation
// which finds "rare" terms — terms that are at the long-tail of the distribution
// and are not frequent. Conceptually, this is like a terms aggregation that
// is sorted by _count ascending.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-rare-terms-aggregation.html
// for details.
type RareTermsAggregation struct {
field string
subAggregations map[string]Aggregation
meta map[string]interface{}
includeExclude *TermsAggregationIncludeExclude
maxDocCount *int
precision *float64
missing interface{}
}
func NewRareTermsAggregation() *RareTermsAggregation {
return &RareTermsAggregation{
subAggregations: make(map[string]Aggregation),
}
}
func (a *RareTermsAggregation) Field(field string) *RareTermsAggregation {
a.field = field
return a
}
func (a *RareTermsAggregation) SubAggregation(name string, subAggregation Aggregation) *RareTermsAggregation {
a.subAggregations[name] = subAggregation
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *RareTermsAggregation) Meta(metaData map[string]interface{}) *RareTermsAggregation {
a.meta = metaData
return a
}
func (a *RareTermsAggregation) MaxDocCount(maxDocCount int) *RareTermsAggregation {
a.maxDocCount = &maxDocCount
return a
}
func (a *RareTermsAggregation) Precision(precision float64) *RareTermsAggregation {
a.precision = &precision
return a
}
func (a *RareTermsAggregation) Missing(missing interface{}) *RareTermsAggregation {
a.missing = missing
return a
}
func (a *RareTermsAggregation) Include(regexp string) *RareTermsAggregation {
if a.includeExclude == nil {
a.includeExclude = &TermsAggregationIncludeExclude{}
}
a.includeExclude.Include = regexp
return a
}
func (a *RareTermsAggregation) IncludeValues(values ...interface{}) *RareTermsAggregation {
if a.includeExclude == nil {
a.includeExclude = &TermsAggregationIncludeExclude{}
}
a.includeExclude.IncludeValues = append(a.includeExclude.IncludeValues, values...)
return a
}
func (a *RareTermsAggregation) Exclude(regexp string) *RareTermsAggregation {
if a.includeExclude == nil {
a.includeExclude = &TermsAggregationIncludeExclude{}
}
a.includeExclude.Exclude = regexp
return a
}
func (a *RareTermsAggregation) ExcludeValues(values ...interface{}) *RareTermsAggregation {
if a.includeExclude == nil {
a.includeExclude = &TermsAggregationIncludeExclude{}
}
a.includeExclude.ExcludeValues = append(a.includeExclude.ExcludeValues, values...)
return a
}
func (a *RareTermsAggregation) IncludeExclude(includeExclude *TermsAggregationIncludeExclude) *RareTermsAggregation {
a.includeExclude = includeExclude
return a
}
func (a *RareTermsAggregation) Source() (interface{}, error) {
// Example:
// {
// "aggregations" : {
// "genres" : {
// "rare_terms" : { "field" : "genre" }
// }
// }
// }
//
// This method returns only the
// "rare_terms" : { "field" : "genre" }
// part.
source := make(map[string]interface{})
opts := make(map[string]interface{})
source["rare_terms"] = opts
if a.field != "" {
opts["field"] = a.field
}
if a.maxDocCount != nil {
opts["max_doc_count"] = *a.maxDocCount
}
if a.precision != nil {
opts["precision"] = *a.precision
}
if a.missing != nil {
opts["missing"] = a.missing
}
// Include/Exclude
if ie := a.includeExclude; ie != nil {
if err := ie.MergeInto(opts); err != nil {
return nil, err
}
}
// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_bucket_rare_terms_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestRareTermsAggregation(t *testing.T) {
agg := NewRareTermsAggregation().Field("genre")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"rare_terms":{"field":"genre"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestRareTermsAggregationWithArgs(t *testing.T) {
agg := NewRareTermsAggregation().
Field("genre").
MaxDocCount(2).
Precision(0.1).
Missing("n/a")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"rare_terms":{"field":"genre","max_doc_count":2,"missing":"n/a","precision":0.1}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestRareTermsAggregationWithIncludeExclude(t *testing.T) {
agg := NewRareTermsAggregation().Field("genre").Include("swi*").Exclude("electro*")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"rare_terms":{"exclude":"electro*","field":"genre","include":"swi*"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestRareTermsAggregationWithIncludeExcludeValues(t *testing.T) {
agg := NewRareTermsAggregation().Field("genre").IncludeValues("swing", "rock").ExcludeValues("jazz")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"rare_terms":{"exclude":["jazz"],"field":"genre","include":["swing","rock"]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestRareTermsAggregationSubAggregation(t *testing.T) {
genres := NewRareTermsAggregation().Field("genre")
agg := NewTermsAggregation().Field("force")
agg = agg.SubAggregation("genres", genres)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"aggregations":{"genres":{"rare_terms":{"field":"genre"}}},"terms":{"field":"force"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestRareTermsAggregationWithMetaData(t *testing.T) {
agg := NewRareTermsAggregation().Field("genre")
agg = agg.Meta(map[string]interface{}{"name": "Oliver"})
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"meta":{"name":"Oliver"},"rare_terms":{"field":"genre"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_bucket_reverse_nested.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// ReverseNestedAggregation defines a special single bucket aggregation
// that enables aggregating on parent docs from nested documents.
// Effectively this aggregation can break out of the nested block
// structure and link to other nested structures or the root document,
// which allows nesting other aggregations that aren’t part of
// the nested object in a nested aggregation.
//
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-reverse-nested-aggregation.html
type ReverseNestedAggregation struct {
path string
subAggregations map[string]Aggregation
meta map[string]interface{}
}
// NewReverseNestedAggregation initializes a new ReverseNestedAggregation
// bucket aggregation.
func NewReverseNestedAggregation() *ReverseNestedAggregation {
return &ReverseNestedAggregation{
subAggregations: make(map[string]Aggregation),
}
}
// Path set the path to use for this nested aggregation. The path must match
// the path to a nested object in the mappings. If it is not specified
// then this aggregation will go back to the root document.
func (a *ReverseNestedAggregation) Path(path string) *ReverseNestedAggregation {
a.path = path
return a
}
func (a *ReverseNestedAggregation) SubAggregation(name string, subAggregation Aggregation) *ReverseNestedAggregation {
a.subAggregations[name] = subAggregation
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *ReverseNestedAggregation) Meta(metaData map[string]interface{}) *ReverseNestedAggregation {
a.meta = metaData
return a
}
func (a *ReverseNestedAggregation) Source() (interface{}, error) {
// Example:
// {
// "aggs" : {
// "reverse_nested" : {
// "path": "..."
// }
// }
// }
// This method returns only the { "reverse_nested" : {} } part.
source := make(map[string]interface{})
opts := make(map[string]interface{})
source["reverse_nested"] = opts
if a.path != "" {
opts["path"] = a.path
}
// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_bucket_reverse_nested_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestReverseNestedAggregation(t *testing.T) {
agg := NewReverseNestedAggregation()
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"reverse_nested":{}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestReverseNestedAggregationWithPath(t *testing.T) {
agg := NewReverseNestedAggregation().Path("comments")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"reverse_nested":{"path":"comments"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestReverseNestedAggregationWithSubAggregation(t *testing.T) {
avgPriceAgg := NewAvgAggregation().Field("price")
agg := NewReverseNestedAggregation().
Path("a_path").
SubAggregation("avg_price", avgPriceAgg)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"aggregations":{"avg_price":{"avg":{"field":"price"}}},"reverse_nested":{"path":"a_path"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestReverseNestedAggregationWithMeta(t *testing.T) {
agg := NewReverseNestedAggregation().
Path("a_path").
Meta(map[string]interface{}{"name": "Oliver"})
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"meta":{"name":"Oliver"},"reverse_nested":{"path":"a_path"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_bucket_sampler.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// SamplerAggregation is a filtering aggregation used to limit any
// sub aggregations' processing to a sample of the top-scoring documents.
// Optionally, diversity settings can be used to limit the number of matches
// that share a common value such as an "author".
//
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-sampler-aggregation.html
type SamplerAggregation struct {
subAggregations map[string]Aggregation
meta map[string]interface{}
shardSize int
}
func NewSamplerAggregation() *SamplerAggregation {
return &SamplerAggregation{
shardSize: -1,
subAggregations: make(map[string]Aggregation),
}
}
func (a *SamplerAggregation) SubAggregation(name string, subAggregation Aggregation) *SamplerAggregation {
a.subAggregations[name] = subAggregation
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *SamplerAggregation) Meta(metaData map[string]interface{}) *SamplerAggregation {
a.meta = metaData
return a
}
// ShardSize sets the maximum number of docs returned from each shard.
func (a *SamplerAggregation) ShardSize(shardSize int) *SamplerAggregation {
a.shardSize = shardSize
return a
}
func (a *SamplerAggregation) Source() (interface{}, error) {
// Example:
// {
// "aggs" : {
// "sample" : {
// "sampler" : {
// "shard_size" : 200
// },
// "aggs": {
// "keywords": {
// "significant_terms": {
// "field": "text"
// }
// }
// }
// }
// }
// }
//
// This method returns only the { "sampler" : { ... } } part.
source := make(map[string]interface{})
opts := make(map[string]interface{})
source["sampler"] = opts
if a.shardSize >= 0 {
opts["shard_size"] = a.shardSize
}
// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_bucket_sampler_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestSamplerAggregation(t *testing.T) {
keywordsAgg := NewSignificantTermsAggregation().Field("text")
agg := NewSamplerAggregation().
ShardSize(200).
SubAggregation("keywords", keywordsAgg)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"aggregations":{"keywords":{"significant_terms":{"field":"text"}}},"sampler":{"shard_size":200}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_bucket_significant_terms.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// SignificantTermsAggregation is an aggregation that returns interesting
// or unusual occurrences of terms in a set.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-significantterms-aggregation.html
type SignificantTermsAggregation struct {
field string
subAggregations map[string]Aggregation
meta map[string]interface{}
minDocCount *int
shardMinDocCount *int
requiredSize *int
shardSize *int
filter Query
executionHint string
significanceHeuristic SignificanceHeuristic
includeExclude *TermsAggregationIncludeExclude
}
func NewSignificantTermsAggregation() *SignificantTermsAggregation {
return &SignificantTermsAggregation{
subAggregations: make(map[string]Aggregation),
}
}
func (a *SignificantTermsAggregation) Field(field string) *SignificantTermsAggregation {
a.field = field
return a
}
func (a *SignificantTermsAggregation) SubAggregation(name string, subAggregation Aggregation) *SignificantTermsAggregation {
a.subAggregations[name] = subAggregation
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *SignificantTermsAggregation) Meta(metaData map[string]interface{}) *SignificantTermsAggregation {
a.meta = metaData
return a
}
func (a *SignificantTermsAggregation) MinDocCount(minDocCount int) *SignificantTermsAggregation {
a.minDocCount = &minDocCount
return a
}
func (a *SignificantTermsAggregation) ShardMinDocCount(shardMinDocCount int) *SignificantTermsAggregation {
a.shardMinDocCount = &shardMinDocCount
return a
}
func (a *SignificantTermsAggregation) RequiredSize(requiredSize int) *SignificantTermsAggregation {
a.requiredSize = &requiredSize
return a
}
func (a *SignificantTermsAggregation) ShardSize(shardSize int) *SignificantTermsAggregation {
a.shardSize = &shardSize
return a
}
func (a *SignificantTermsAggregation) BackgroundFilter(filter Query) *SignificantTermsAggregation {
a.filter = filter
return a
}
func (a *SignificantTermsAggregation) ExecutionHint(hint string) *SignificantTermsAggregation {
a.executionHint = hint
return a
}
func (a *SignificantTermsAggregation) SignificanceHeuristic(heuristic SignificanceHeuristic) *SignificantTermsAggregation {
a.significanceHeuristic = heuristic
return a
}
func (a *SignificantTermsAggregation) Include(regexp string) *SignificantTermsAggregation {
if a.includeExclude == nil {
a.includeExclude = &TermsAggregationIncludeExclude{}
}
a.includeExclude.Include = regexp
return a
}
func (a *SignificantTermsAggregation) IncludeValues(values ...interface{}) *SignificantTermsAggregation {
if a.includeExclude == nil {
a.includeExclude = &TermsAggregationIncludeExclude{}
}
a.includeExclude.IncludeValues = append(a.includeExclude.IncludeValues, values...)
return a
}
func (a *SignificantTermsAggregation) Exclude(regexp string) *SignificantTermsAggregation {
if a.includeExclude == nil {
a.includeExclude = &TermsAggregationIncludeExclude{}
}
a.includeExclude.Exclude = regexp
return a
}
func (a *SignificantTermsAggregation) ExcludeValues(values ...interface{}) *SignificantTermsAggregation {
if a.includeExclude == nil {
a.includeExclude = &TermsAggregationIncludeExclude{}
}
a.includeExclude.ExcludeValues = append(a.includeExclude.ExcludeValues, values...)
return a
}
func (a *SignificantTermsAggregation) Partition(p int) *SignificantTermsAggregation {
if a.includeExclude == nil {
a.includeExclude = &TermsAggregationIncludeExclude{}
}
a.includeExclude.Partition = p
return a
}
func (a *SignificantTermsAggregation) NumPartitions(n int) *SignificantTermsAggregation {
if a.includeExclude == nil {
a.includeExclude = &TermsAggregationIncludeExclude{}
}
a.includeExclude.NumPartitions = n
return a
}
func (a *SignificantTermsAggregation) IncludeExclude(includeExclude *TermsAggregationIncludeExclude) *SignificantTermsAggregation {
a.includeExclude = includeExclude
return a
}
func (a *SignificantTermsAggregation) Source() (interface{}, error) {
// Example:
// {
// "query" : {
// "terms" : {"force" : [ "British Transport Police" ]}
// },
// "aggregations" : {
// "significantCrimeTypes" : {
// "significant_terms" : { "field" : "crime_type" }
// }
// }
// }
//
// This method returns only the
// { "significant_terms" : { "field" : "crime_type" }
// part.
source := make(map[string]interface{})
opts := make(map[string]interface{})
source["significant_terms"] = opts
if a.field != "" {
opts["field"] = a.field
}
if a.requiredSize != nil {
opts["size"] = *a.requiredSize // not a typo!
}
if a.shardSize != nil {
opts["shard_size"] = *a.shardSize
}
if a.minDocCount != nil {
opts["min_doc_count"] = *a.minDocCount
}
if a.shardMinDocCount != nil {
opts["shard_min_doc_count"] = *a.shardMinDocCount
}
if a.executionHint != "" {
opts["execution_hint"] = a.executionHint
}
if a.filter != nil {
src, err := a.filter.Source()
if err != nil {
return nil, err
}
opts["background_filter"] = src
}
if a.significanceHeuristic != nil {
name := a.significanceHeuristic.Name()
src, err := a.significanceHeuristic.Source()
if err != nil {
return nil, err
}
opts[name] = src
}
// Include/Exclude
if ie := a.includeExclude; ie != nil {
if err := ie.MergeInto(opts); err != nil {
return nil, err
}
}
// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
// -- Significance heuristics --
type SignificanceHeuristic interface {
Name() string
Source() (interface{}, error)
}
// -- Chi Square --
// ChiSquareSignificanceHeuristic implements Chi square as described
// in "Information Retrieval", Manning et al., Chapter 13.5.2.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-significantterms-aggregation.html#_chi_square
// for details.
type ChiSquareSignificanceHeuristic struct {
backgroundIsSuperset *bool
includeNegatives *bool
}
// NewChiSquareSignificanceHeuristic initializes a new ChiSquareSignificanceHeuristic.
func NewChiSquareSignificanceHeuristic() *ChiSquareSignificanceHeuristic {
return &ChiSquareSignificanceHeuristic{}
}
// Name returns the name of the heuristic in the REST interface.
func (sh *ChiSquareSignificanceHeuristic) Name() string {
return "chi_square"
}
// BackgroundIsSuperset indicates whether you defined a custom background
// filter that represents a difference set of documents that you want to
// compare to.
func (sh *ChiSquareSignificanceHeuristic) BackgroundIsSuperset(backgroundIsSuperset bool) *ChiSquareSignificanceHeuristic {
sh.backgroundIsSuperset = &backgroundIsSuperset
return sh
}
// IncludeNegatives indicates whether to filter out the terms that appear
// much less in the subset than in the background without the subset.
func (sh *ChiSquareSignificanceHeuristic) IncludeNegatives(includeNegatives bool) *ChiSquareSignificanceHeuristic {
sh.includeNegatives = &includeNegatives
return sh
}
// Source returns the parameters that need to be added to the REST parameters.
func (sh *ChiSquareSignificanceHeuristic) Source() (interface{}, error) {
source := make(map[string]interface{})
if sh.backgroundIsSuperset != nil {
source["background_is_superset"] = *sh.backgroundIsSuperset
}
if sh.includeNegatives != nil {
source["include_negatives"] = *sh.includeNegatives
}
return source, nil
}
// -- GND --
// GNDSignificanceHeuristic implements the "Google Normalized Distance"
// as described in "The Google Similarity Distance", Cilibrasi and Vitanyi,
// 2007.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-significantterms-aggregation.html#_google_normalized_distance
// for details.
type GNDSignificanceHeuristic struct {
backgroundIsSuperset *bool
}
// NewGNDSignificanceHeuristic implements a new GNDSignificanceHeuristic.
func NewGNDSignificanceHeuristic() *GNDSignificanceHeuristic {
return &GNDSignificanceHeuristic{}
}
// Name returns the name of the heuristic in the REST interface.
func (sh *GNDSignificanceHeuristic) Name() string {
return "gnd"
}
// BackgroundIsSuperset indicates whether you defined a custom background
// filter that represents a difference set of documents that you want to
// compare to.
func (sh *GNDSignificanceHeuristic) BackgroundIsSuperset(backgroundIsSuperset bool) *GNDSignificanceHeuristic {
sh.backgroundIsSuperset = &backgroundIsSuperset
return sh
}
// Source returns the parameters that need to be added to the REST parameters.
func (sh *GNDSignificanceHeuristic) Source() (interface{}, error) {
source := make(map[string]interface{})
if sh.backgroundIsSuperset != nil {
source["background_is_superset"] = *sh.backgroundIsSuperset
}
return source, nil
}
// -- JLH Score --
// JLHScoreSignificanceHeuristic implements the JLH score as described in
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-significantterms-aggregation.html#_jlh_score.
type JLHScoreSignificanceHeuristic struct{}
// NewJLHScoreSignificanceHeuristic initializes a new JLHScoreSignificanceHeuristic.
func NewJLHScoreSignificanceHeuristic() *JLHScoreSignificanceHeuristic {
return &JLHScoreSignificanceHeuristic{}
}
// Name returns the name of the heuristic in the REST interface.
func (sh *JLHScoreSignificanceHeuristic) Name() string {
return "jlh"
}
// Source returns the parameters that need to be added to the REST parameters.
func (sh *JLHScoreSignificanceHeuristic) Source() (interface{}, error) {
source := make(map[string]interface{})
return source, nil
}
// -- Mutual Information --
// MutualInformationSignificanceHeuristic implements Mutual information
// as described in "Information Retrieval", Manning et al., Chapter 13.5.1.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-significantterms-aggregation.html#_mutual_information
// for details.
type MutualInformationSignificanceHeuristic struct {
backgroundIsSuperset *bool
includeNegatives *bool
}
// NewMutualInformationSignificanceHeuristic initializes a new instance of
// MutualInformationSignificanceHeuristic.
func NewMutualInformationSignificanceHeuristic() *MutualInformationSignificanceHeuristic {
return &MutualInformationSignificanceHeuristic{}
}
// Name returns the name of the heuristic in the REST interface.
func (sh *MutualInformationSignificanceHeuristic) Name() string {
return "mutual_information"
}
// BackgroundIsSuperset indicates whether you defined a custom background
// filter that represents a difference set of documents that you want to
// compare to.
func (sh *MutualInformationSignificanceHeuristic) BackgroundIsSuperset(backgroundIsSuperset bool) *MutualInformationSignificanceHeuristic {
sh.backgroundIsSuperset = &backgroundIsSuperset
return sh
}
// IncludeNegatives indicates whether to filter out the terms that appear
// much less in the subset than in the background without the subset.
func (sh *MutualInformationSignificanceHeuristic) IncludeNegatives(includeNegatives bool) *MutualInformationSignificanceHeuristic {
sh.includeNegatives = &includeNegatives
return sh
}
// Source returns the parameters that need to be added to the REST parameters.
func (sh *MutualInformationSignificanceHeuristic) Source() (interface{}, error) {
source := make(map[string]interface{})
if sh.backgroundIsSuperset != nil {
source["background_is_superset"] = *sh.backgroundIsSuperset
}
if sh.includeNegatives != nil {
source["include_negatives"] = *sh.includeNegatives
}
return source, nil
}
// -- Percentage Score --
// PercentageScoreSignificanceHeuristic implements the algorithm described
// in https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-significantterms-aggregation.html#_percentage.
type PercentageScoreSignificanceHeuristic struct{}
// NewPercentageScoreSignificanceHeuristic initializes a new instance of
// PercentageScoreSignificanceHeuristic.
func NewPercentageScoreSignificanceHeuristic() *PercentageScoreSignificanceHeuristic {
return &PercentageScoreSignificanceHeuristic{}
}
// Name returns the name of the heuristic in the REST interface.
func (sh *PercentageScoreSignificanceHeuristic) Name() string {
return "percentage"
}
// Source returns the parameters that need to be added to the REST parameters.
func (sh *PercentageScoreSignificanceHeuristic) Source() (interface{}, error) {
source := make(map[string]interface{})
return source, nil
}
// -- Script --
// ScriptSignificanceHeuristic implements a scripted significance heuristic.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-significantterms-aggregation.html#_scripted
// for details.
type ScriptSignificanceHeuristic struct {
script *Script
}
// NewScriptSignificanceHeuristic initializes a new instance of
// ScriptSignificanceHeuristic.
func NewScriptSignificanceHeuristic() *ScriptSignificanceHeuristic {
return &ScriptSignificanceHeuristic{}
}
// Name returns the name of the heuristic in the REST interface.
func (sh *ScriptSignificanceHeuristic) Name() string {
return "script_heuristic"
}
// Script specifies the script to use to get custom scores. The following
// parameters are available in the script: `_subset_freq`, `_superset_freq`,
// `_subset_size`, and `_superset_size`.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-significantterms-aggregation.html#_scripted
// for details.
func (sh *ScriptSignificanceHeuristic) Script(script *Script) *ScriptSignificanceHeuristic {
sh.script = script
return sh
}
// Source returns the parameters that need to be added to the REST parameters.
func (sh *ScriptSignificanceHeuristic) Source() (interface{}, error) {
source := make(map[string]interface{})
if sh.script != nil {
src, err := sh.script.Source()
if err != nil {
return nil, err
}
source["script"] = src
}
return source, nil
}
================================================
FILE: search_aggs_bucket_significant_terms_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestSignificantTermsAggregation(t *testing.T) {
agg := NewSignificantTermsAggregation().Field("crime_type")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"significant_terms":{"field":"crime_type"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSignificantTermsAggregationWithArgs(t *testing.T) {
agg := NewSignificantTermsAggregation().
Field("crime_type").
ExecutionHint("map").
ShardSize(5).
MinDocCount(10).
BackgroundFilter(NewTermQuery("city", "London"))
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"significant_terms":{"background_filter":{"term":{"city":"London"}},"execution_hint":"map","field":"crime_type","min_doc_count":10,"shard_size":5}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSignificantTermsAggregationWithIncludeExclude(t *testing.T) {
agg := NewSignificantTermsAggregation().Field("crime_type").Include(".*sport.*").Exclude("water_.*")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"significant_terms":{"exclude":"water_.*","field":"crime_type","include":".*sport.*"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSignificantTermsAggregationWithIncludeExcludeValues(t *testing.T) {
agg := NewSignificantTermsAggregation().Field("crime_type").IncludeValues("mazda", "honda").ExcludeValues("rover", "jensen")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"significant_terms":{"exclude":["rover","jensen"],"field":"crime_type","include":["mazda","honda"]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSignificantTermsAggregationWithPartitions(t *testing.T) {
agg := NewSignificantTermsAggregation().Field("account_id").Partition(0).NumPartitions(20)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"significant_terms":{"field":"account_id","include":{"num_partitions":20,"partition":0}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSignificantTermsAggregationSubAggregation(t *testing.T) {
crimeTypesAgg := NewSignificantTermsAggregation().Field("crime_type")
agg := NewTermsAggregation().Field("force")
agg = agg.SubAggregation("significantCrimeTypes", crimeTypesAgg)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"aggregations":{"significantCrimeTypes":{"significant_terms":{"field":"crime_type"}}},"terms":{"field":"force"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSignificantTermsAggregationWithMetaData(t *testing.T) {
agg := NewSignificantTermsAggregation().Field("crime_type")
agg = agg.Meta(map[string]interface{}{"name": "Oliver"})
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"meta":{"name":"Oliver"},"significant_terms":{"field":"crime_type"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSignificantTermsAggregationWithChiSquare(t *testing.T) {
agg := NewSignificantTermsAggregation().Field("crime_type")
agg = agg.SignificanceHeuristic(
NewChiSquareSignificanceHeuristic().
BackgroundIsSuperset(true).
IncludeNegatives(false),
)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"significant_terms":{"chi_square":{"background_is_superset":true,"include_negatives":false},"field":"crime_type"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSignificantTermsAggregationWithGND(t *testing.T) {
agg := NewSignificantTermsAggregation().Field("crime_type")
agg = agg.SignificanceHeuristic(
NewGNDSignificanceHeuristic(),
)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"significant_terms":{"field":"crime_type","gnd":{}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSignificantTermsAggregationWithJLH(t *testing.T) {
agg := NewSignificantTermsAggregation().Field("crime_type")
agg = agg.SignificanceHeuristic(
NewJLHScoreSignificanceHeuristic(),
)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"significant_terms":{"field":"crime_type","jlh":{}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSignificantTermsAggregationWithMutualInformation(t *testing.T) {
agg := NewSignificantTermsAggregation().Field("crime_type")
agg = agg.SignificanceHeuristic(
NewMutualInformationSignificanceHeuristic().
BackgroundIsSuperset(false).
IncludeNegatives(true),
)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"significant_terms":{"field":"crime_type","mutual_information":{"background_is_superset":false,"include_negatives":true}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSignificantTermsAggregationWithPercentageScore(t *testing.T) {
agg := NewSignificantTermsAggregation().Field("crime_type")
agg = agg.SignificanceHeuristic(
NewPercentageScoreSignificanceHeuristic(),
)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"significant_terms":{"field":"crime_type","percentage":{}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSignificantTermsAggregationWithScript(t *testing.T) {
agg := NewSignificantTermsAggregation().Field("crime_type")
agg = agg.SignificanceHeuristic(
NewScriptSignificanceHeuristic().
Script(NewScript("_subset_freq/(_superset_freq - _subset_freq + 1)")),
)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"significant_terms":{"field":"crime_type","script_heuristic":{"script":{"source":"_subset_freq/(_superset_freq - _subset_freq + 1)"}}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_bucket_significant_text.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// SignificantTextAggregation returns interesting or unusual occurrences
// of free-text terms in a set.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-significanttext-aggregation.html
type SignificantTextAggregation struct {
field string
subAggregations map[string]Aggregation
meta map[string]interface{}
sourceFieldNames []string
filterDuplicateText *bool
includeExclude *TermsAggregationIncludeExclude
filter Query
bucketCountThresholds *BucketCountThresholds
significanceHeuristic SignificanceHeuristic
}
func NewSignificantTextAggregation() *SignificantTextAggregation {
return &SignificantTextAggregation{
subAggregations: make(map[string]Aggregation),
}
}
func (a *SignificantTextAggregation) Field(field string) *SignificantTextAggregation {
a.field = field
return a
}
func (a *SignificantTextAggregation) SubAggregation(name string, subAggregation Aggregation) *SignificantTextAggregation {
a.subAggregations[name] = subAggregation
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *SignificantTextAggregation) Meta(metaData map[string]interface{}) *SignificantTextAggregation {
a.meta = metaData
return a
}
func (a *SignificantTextAggregation) SourceFieldNames(names ...string) *SignificantTextAggregation {
a.sourceFieldNames = names
return a
}
func (a *SignificantTextAggregation) FilterDuplicateText(filter bool) *SignificantTextAggregation {
a.filterDuplicateText = &filter
return a
}
func (a *SignificantTextAggregation) MinDocCount(minDocCount int64) *SignificantTextAggregation {
if a.bucketCountThresholds == nil {
a.bucketCountThresholds = &BucketCountThresholds{}
}
a.bucketCountThresholds.MinDocCount = &minDocCount
return a
}
func (a *SignificantTextAggregation) ShardMinDocCount(shardMinDocCount int64) *SignificantTextAggregation {
if a.bucketCountThresholds == nil {
a.bucketCountThresholds = &BucketCountThresholds{}
}
a.bucketCountThresholds.ShardMinDocCount = &shardMinDocCount
return a
}
func (a *SignificantTextAggregation) Size(size int) *SignificantTextAggregation {
if a.bucketCountThresholds == nil {
a.bucketCountThresholds = &BucketCountThresholds{}
}
a.bucketCountThresholds.RequiredSize = &size
return a
}
func (a *SignificantTextAggregation) ShardSize(shardSize int) *SignificantTextAggregation {
if a.bucketCountThresholds == nil {
a.bucketCountThresholds = &BucketCountThresholds{}
}
a.bucketCountThresholds.ShardSize = &shardSize
return a
}
func (a *SignificantTextAggregation) BackgroundFilter(filter Query) *SignificantTextAggregation {
a.filter = filter
return a
}
func (a *SignificantTextAggregation) SignificanceHeuristic(heuristic SignificanceHeuristic) *SignificantTextAggregation {
a.significanceHeuristic = heuristic
return a
}
func (a *SignificantTextAggregation) Include(regexp string) *SignificantTextAggregation {
if a.includeExclude == nil {
a.includeExclude = &TermsAggregationIncludeExclude{}
}
a.includeExclude.Include = regexp
return a
}
func (a *SignificantTextAggregation) IncludeValues(values ...interface{}) *SignificantTextAggregation {
if a.includeExclude == nil {
a.includeExclude = &TermsAggregationIncludeExclude{}
}
a.includeExclude.IncludeValues = append(a.includeExclude.IncludeValues, values...)
return a
}
func (a *SignificantTextAggregation) Exclude(regexp string) *SignificantTextAggregation {
if a.includeExclude == nil {
a.includeExclude = &TermsAggregationIncludeExclude{}
}
a.includeExclude.Exclude = regexp
return a
}
func (a *SignificantTextAggregation) ExcludeValues(values ...interface{}) *SignificantTextAggregation {
if a.includeExclude == nil {
a.includeExclude = &TermsAggregationIncludeExclude{}
}
a.includeExclude.ExcludeValues = append(a.includeExclude.ExcludeValues, values...)
return a
}
func (a *SignificantTextAggregation) Partition(p int) *SignificantTextAggregation {
if a.includeExclude == nil {
a.includeExclude = &TermsAggregationIncludeExclude{}
}
a.includeExclude.Partition = p
return a
}
func (a *SignificantTextAggregation) NumPartitions(n int) *SignificantTextAggregation {
if a.includeExclude == nil {
a.includeExclude = &TermsAggregationIncludeExclude{}
}
a.includeExclude.NumPartitions = n
return a
}
func (a *SignificantTextAggregation) IncludeExclude(includeExclude *TermsAggregationIncludeExclude) *SignificantTextAggregation {
a.includeExclude = includeExclude
return a
}
func (a *SignificantTextAggregation) Source() (interface{}, error) {
// Example:
// {
// "query" : {
// "match" : {"content" : "Bird flu"}
// },
// "aggregations" : {
// "my_sample" : {
// "sampler": {
// "shard_size" : 100
// },
// "aggregations": {
// "keywords" : {
// "significant_text" : { "field" : "content" }
// }
// }
// }
// }
// }
//
// This method returns only the
// { "significant_text" : { "field" : "content" }
// part.
source := make(map[string]interface{})
opts := make(map[string]interface{})
source["significant_text"] = opts
if a.field != "" {
opts["field"] = a.field
}
if a.bucketCountThresholds != nil {
if a.bucketCountThresholds.RequiredSize != nil {
opts["size"] = (*a.bucketCountThresholds).RequiredSize
}
if a.bucketCountThresholds.ShardSize != nil {
opts["shard_size"] = (*a.bucketCountThresholds).ShardSize
}
if a.bucketCountThresholds.MinDocCount != nil {
opts["min_doc_count"] = (*a.bucketCountThresholds).MinDocCount
}
if a.bucketCountThresholds.ShardMinDocCount != nil {
opts["shard_min_doc_count"] = (*a.bucketCountThresholds).ShardMinDocCount
}
}
if a.filter != nil {
src, err := a.filter.Source()
if err != nil {
return nil, err
}
opts["background_filter"] = src
}
if a.significanceHeuristic != nil {
name := a.significanceHeuristic.Name()
src, err := a.significanceHeuristic.Source()
if err != nil {
return nil, err
}
opts[name] = src
}
// Include/Exclude
if ie := a.includeExclude; ie != nil {
// Include
if ie.Include != "" {
opts["include"] = ie.Include
} else if len(ie.IncludeValues) > 0 {
opts["include"] = ie.IncludeValues
} else if ie.NumPartitions > 0 {
inc := make(map[string]interface{})
inc["partition"] = ie.Partition
inc["num_partitions"] = ie.NumPartitions
opts["include"] = inc
}
// Exclude
if ie.Exclude != "" {
opts["exclude"] = ie.Exclude
} else if len(ie.ExcludeValues) > 0 {
opts["exclude"] = ie.ExcludeValues
}
}
// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_bucket_significant_text_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestSignificantTextAggregation(t *testing.T) {
agg := NewSignificantTextAggregation().Field("content")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"significant_text":{"field":"content"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSignificantTextAggregationWithArgs(t *testing.T) {
agg := NewSignificantTextAggregation().
Field("content").
ShardSize(5).
MinDocCount(10).
BackgroundFilter(NewTermQuery("city", "London"))
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"significant_text":{"background_filter":{"term":{"city":"London"}},"field":"content","min_doc_count":10,"shard_size":5}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSignificantTextAggregationWithPartitions(t *testing.T) {
agg := NewSignificantTextAggregation().Field("account_id").Partition(0).NumPartitions(20)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"significant_text":{"field":"account_id","include":{"num_partitions":20,"partition":0}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSignificantTextAggregationWithMetaData(t *testing.T) {
agg := NewSignificantTextAggregation().Field("content")
agg = agg.Meta(map[string]interface{}{"name": "Oliver"})
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"meta":{"name":"Oliver"},"significant_text":{"field":"content"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_bucket_terms.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import "fmt"
// TermsAggregation is a multi-bucket value source based aggregation
// where buckets are dynamically built - one per unique value.
//
// See: http://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-terms-aggregation.html
type TermsAggregation struct {
field string
script *Script
missing interface{}
subAggregations map[string]Aggregation
meta map[string]interface{}
size *int
shardSize *int
requiredSize *int
minDocCount *int
shardMinDocCount *int
valueType string
includeExclude *TermsAggregationIncludeExclude
executionHint string
collectionMode string
showTermDocCountError *bool
order []TermsOrder
}
func NewTermsAggregation() *TermsAggregation {
return &TermsAggregation{
subAggregations: make(map[string]Aggregation),
}
}
func (a *TermsAggregation) Field(field string) *TermsAggregation {
a.field = field
return a
}
func (a *TermsAggregation) Script(script *Script) *TermsAggregation {
a.script = script
return a
}
// Missing configures the value to use when documents miss a value.
func (a *TermsAggregation) Missing(missing interface{}) *TermsAggregation {
a.missing = missing
return a
}
func (a *TermsAggregation) SubAggregation(name string, subAggregation Aggregation) *TermsAggregation {
a.subAggregations[name] = subAggregation
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *TermsAggregation) Meta(metaData map[string]interface{}) *TermsAggregation {
a.meta = metaData
return a
}
func (a *TermsAggregation) Size(size int) *TermsAggregation {
a.size = &size
return a
}
func (a *TermsAggregation) RequiredSize(requiredSize int) *TermsAggregation {
a.requiredSize = &requiredSize
return a
}
func (a *TermsAggregation) ShardSize(shardSize int) *TermsAggregation {
a.shardSize = &shardSize
return a
}
func (a *TermsAggregation) MinDocCount(minDocCount int) *TermsAggregation {
a.minDocCount = &minDocCount
return a
}
func (a *TermsAggregation) ShardMinDocCount(shardMinDocCount int) *TermsAggregation {
a.shardMinDocCount = &shardMinDocCount
return a
}
func (a *TermsAggregation) Include(regexp string) *TermsAggregation {
if a.includeExclude == nil {
a.includeExclude = &TermsAggregationIncludeExclude{}
}
a.includeExclude.Include = regexp
return a
}
func (a *TermsAggregation) IncludeValues(values ...interface{}) *TermsAggregation {
if a.includeExclude == nil {
a.includeExclude = &TermsAggregationIncludeExclude{}
}
a.includeExclude.IncludeValues = append(a.includeExclude.IncludeValues, values...)
return a
}
func (a *TermsAggregation) Exclude(regexp string) *TermsAggregation {
if a.includeExclude == nil {
a.includeExclude = &TermsAggregationIncludeExclude{}
}
a.includeExclude.Exclude = regexp
return a
}
func (a *TermsAggregation) ExcludeValues(values ...interface{}) *TermsAggregation {
if a.includeExclude == nil {
a.includeExclude = &TermsAggregationIncludeExclude{}
}
a.includeExclude.ExcludeValues = append(a.includeExclude.ExcludeValues, values...)
return a
}
func (a *TermsAggregation) Partition(p int) *TermsAggregation {
if a.includeExclude == nil {
a.includeExclude = &TermsAggregationIncludeExclude{}
}
a.includeExclude.Partition = p
return a
}
func (a *TermsAggregation) NumPartitions(n int) *TermsAggregation {
if a.includeExclude == nil {
a.includeExclude = &TermsAggregationIncludeExclude{}
}
a.includeExclude.NumPartitions = n
return a
}
func (a *TermsAggregation) IncludeExclude(includeExclude *TermsAggregationIncludeExclude) *TermsAggregation {
a.includeExclude = includeExclude
return a
}
// ValueType can be string, long, or double.
func (a *TermsAggregation) ValueType(valueType string) *TermsAggregation {
a.valueType = valueType
return a
}
func (a *TermsAggregation) Order(order string, asc bool) *TermsAggregation {
a.order = append(a.order, TermsOrder{Field: order, Ascending: asc})
return a
}
func (a *TermsAggregation) OrderByCount(asc bool) *TermsAggregation {
// "order" : { "_count" : "asc" }
a.order = append(a.order, TermsOrder{Field: "_count", Ascending: asc})
return a
}
func (a *TermsAggregation) OrderByCountAsc() *TermsAggregation {
return a.OrderByCount(true)
}
func (a *TermsAggregation) OrderByCountDesc() *TermsAggregation {
return a.OrderByCount(false)
}
// Deprecated: Use OrderByKey instead.
func (a *TermsAggregation) OrderByTerm(asc bool) *TermsAggregation {
// "order" : { "_term" : "asc" }
a.order = append(a.order, TermsOrder{Field: "_term", Ascending: asc})
return a
}
// Deprecated: Use OrderByKeyAsc instead.
func (a *TermsAggregation) OrderByTermAsc() *TermsAggregation {
return a.OrderByTerm(true)
}
// Deprecated: Use OrderByKeyDesc instead.
func (a *TermsAggregation) OrderByTermDesc() *TermsAggregation {
return a.OrderByTerm(false)
}
func (a *TermsAggregation) OrderByKey(asc bool) *TermsAggregation {
// "order" : { "_term" : "asc" }
a.order = append(a.order, TermsOrder{Field: "_key", Ascending: asc})
return a
}
func (a *TermsAggregation) OrderByKeyAsc() *TermsAggregation {
return a.OrderByKey(true)
}
func (a *TermsAggregation) OrderByKeyDesc() *TermsAggregation {
return a.OrderByKey(false)
}
// OrderByAggregation creates a bucket ordering strategy which sorts buckets
// based on a single-valued calc get.
func (a *TermsAggregation) OrderByAggregation(aggName string, asc bool) *TermsAggregation {
// {
// "aggs" : {
// "genders" : {
// "terms" : {
// "field" : "gender",
// "order" : { "avg_height" : "desc" }
// },
// "aggs" : {
// "avg_height" : { "avg" : { "field" : "height" } }
// }
// }
// }
// }
a.order = append(a.order, TermsOrder{Field: aggName, Ascending: asc})
return a
}
// OrderByAggregationAndMetric creates a bucket ordering strategy which
// sorts buckets based on a multi-valued calc get.
func (a *TermsAggregation) OrderByAggregationAndMetric(aggName, metric string, asc bool) *TermsAggregation {
// {
// "aggs" : {
// "genders" : {
// "terms" : {
// "field" : "gender",
// "order" : { "height_stats.avg" : "desc" }
// },
// "aggs" : {
// "height_stats" : { "stats" : { "field" : "height" } }
// }
// }
// }
// }
a.order = append(a.order, TermsOrder{Field: aggName + "." + metric, Ascending: asc})
return a
}
func (a *TermsAggregation) ExecutionHint(hint string) *TermsAggregation {
a.executionHint = hint
return a
}
// Collection mode can be depth_first or breadth_first as of 1.4.0.
func (a *TermsAggregation) CollectionMode(collectionMode string) *TermsAggregation {
a.collectionMode = collectionMode
return a
}
func (a *TermsAggregation) ShowTermDocCountError(showTermDocCountError bool) *TermsAggregation {
a.showTermDocCountError = &showTermDocCountError
return a
}
func (a *TermsAggregation) Source() (interface{}, error) {
// Example:
// {
// "aggs" : {
// "genders" : {
// "terms" : { "field" : "gender" }
// }
// }
// }
// This method returns only the { "terms" : { "field" : "gender" } } part.
source := make(map[string]interface{})
opts := make(map[string]interface{})
source["terms"] = opts
// ValuesSourceAggregationBuilder
if a.field != "" {
opts["field"] = a.field
}
if a.script != nil {
src, err := a.script.Source()
if err != nil {
return nil, err
}
opts["script"] = src
}
if a.missing != nil {
opts["missing"] = a.missing
}
// TermsBuilder
if a.size != nil && *a.size >= 0 {
opts["size"] = *a.size
}
if a.shardSize != nil && *a.shardSize >= 0 {
opts["shard_size"] = *a.shardSize
}
if a.requiredSize != nil && *a.requiredSize >= 0 {
opts["required_size"] = *a.requiredSize
}
if a.minDocCount != nil && *a.minDocCount >= 0 {
opts["min_doc_count"] = *a.minDocCount
}
if a.shardMinDocCount != nil && *a.shardMinDocCount >= 0 {
opts["shard_min_doc_count"] = *a.shardMinDocCount
}
if a.showTermDocCountError != nil {
opts["show_term_doc_count_error"] = *a.showTermDocCountError
}
if a.collectionMode != "" {
opts["collect_mode"] = a.collectionMode
}
if a.valueType != "" {
opts["value_type"] = a.valueType
}
if len(a.order) > 0 {
var orderSlice []interface{}
for _, order := range a.order {
src, err := order.Source()
if err != nil {
return nil, err
}
orderSlice = append(orderSlice, src)
}
opts["order"] = orderSlice
}
// Include/Exclude
if ie := a.includeExclude; ie != nil {
if err := ie.MergeInto(opts); err != nil {
return nil, err
}
}
if a.executionHint != "" {
opts["execution_hint"] = a.executionHint
}
// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
// TermsAggregationIncludeExclude allows for include/exclude in a TermsAggregation.
type TermsAggregationIncludeExclude struct {
Include string
Exclude string
IncludeValues []interface{}
ExcludeValues []interface{}
Partition int
NumPartitions int
}
// Source returns a JSON serializable struct.
func (ie *TermsAggregationIncludeExclude) Source() (interface{}, error) {
source := make(map[string]interface{})
// Include
if ie.Include != "" {
source["include"] = ie.Include
} else if len(ie.IncludeValues) > 0 {
source["include"] = ie.IncludeValues
} else if ie.NumPartitions > 0 {
inc := make(map[string]interface{})
inc["partition"] = ie.Partition
inc["num_partitions"] = ie.NumPartitions
source["include"] = inc
}
// Exclude
if ie.Exclude != "" {
source["exclude"] = ie.Exclude
} else if len(ie.ExcludeValues) > 0 {
source["exclude"] = ie.ExcludeValues
}
return source, nil
}
// MergeInto merges the values of the include/exclude options into source.
func (ie *TermsAggregationIncludeExclude) MergeInto(source map[string]interface{}) error {
values, err := ie.Source()
if err != nil {
return err
}
mv, ok := values.(map[string]interface{})
if !ok {
return fmt.Errorf("IncludeExclude: expected a map[string]interface{}, got %T", values)
}
for k, v := range mv {
source[k] = v
}
return nil
}
// TermsOrder specifies a single order field for a terms aggregation.
type TermsOrder struct {
Field string
Ascending bool
}
// Source returns serializable JSON of the TermsOrder.
func (order *TermsOrder) Source() (interface{}, error) {
source := make(map[string]string)
if order.Ascending {
source[order.Field] = "asc"
} else {
source[order.Field] = "desc"
}
return source, nil
}
================================================
FILE: search_aggs_bucket_terms_integration_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"testing"
)
func TestAggsBucketTermsIntegration(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t) //, SetTraceLog(log.New(os.Stdout, "", log.LstdFlags)))
resp, err := client.Search().
Index(testIndexName).
Query(NewMatchAllQuery()).
Aggregation("retweets", NewTermsAggregation().Field("retweets")).
Pretty(true).
Do(context.Background())
if err != nil {
t.Fatal(err)
}
if resp.Hits == nil {
t.Errorf("expected Hits != nil")
}
if want, have := int64(3), resp.TotalHits(); want != have {
t.Errorf("TotalHits(): want %d, have %d", want, have)
}
aggs := resp.Aggregations
if aggs == nil {
t.Fatalf("expected Aggregations != nil")
}
agg, found := aggs.Terms("retweets")
if !found {
t.Fatal("expected to find aggregation by name")
}
if agg == nil {
t.Fatalf("expected != nil")
}
if want, have := 3, len(agg.Buckets); want != have {
t.Fatalf("len(Buckets): want %d, have %d", want, have)
}
// Element #0
if want, have := float64(0), agg.Buckets[0].Key; want != have {
t.Errorf("Buckets[0].Key: want %v, have %v", want, have)
}
if want, have := json.Number("0"), agg.Buckets[0].KeyNumber; want != have {
t.Errorf("agg.Buckets[0].KeyNumber: want %q, have %q", want, have)
}
if have, err := agg.Buckets[0].KeyNumber.Int64(); err != nil {
t.Errorf("Buckets[0].KeyNumber.Int64(): %v", err)
} else if want := int64(0); want != have {
t.Errorf("Buckets[0].KeyNumber.Int64(): want %d, have %d", want, have)
}
if want, have := "0", agg.Buckets[0].KeyNumber.String(); want != have {
t.Errorf("Buckets[0].KeyNumber.String(): want %q, have %q", want, have)
}
if want, have := int64(1), agg.Buckets[0].DocCount; want != have {
t.Errorf("Buckets[0].DocCount: want %d, have %d", want, have)
}
// Element #1
if want, have := float64(12), agg.Buckets[1].Key; want != have {
t.Errorf("Buckets[1].Key: want %v, have %v", want, have)
}
if have, err := agg.Buckets[1].KeyNumber.Int64(); err != nil {
t.Errorf("Buckets[1].KeyNumber.Int64(): %v", err)
} else if want := int64(12); want != have {
t.Errorf("Buckets[1].KeyNumber.Int64(): want %d, have %d", want, have)
}
if want, have := "12", agg.Buckets[1].KeyNumber.String(); want != have {
t.Errorf("Buckets[1].KeyNumber.String(): want %q, have %q", want, have)
}
if want, have := int64(1), agg.Buckets[1].DocCount; want != have {
t.Errorf("Buckets[1].DocCount: want %d, have %d", want, have)
}
// Element #2
if want, have := float64(108), agg.Buckets[2].Key; want != have {
t.Errorf("Buckets[2].Key: want %v, have %v", want, have)
}
if want, have := json.Number("108"), agg.Buckets[2].KeyNumber; want != have {
t.Errorf("Buckets[2].KeyNumber: want %q, have %q", want, have)
}
if have, err := agg.Buckets[2].KeyNumber.Int64(); err != nil {
t.Errorf("Buckets[2].KeyNumber.Int64(): %v", err)
} else if want := int64(108); want != have {
t.Errorf("Buckets[2].KeyNumber.Int64(): want %d, have %d", want, have)
}
if want, have := "108", agg.Buckets[2].KeyNumber.String(); want != have {
t.Errorf("Buckets[2].KeyNumber.String(): want %q, have %q", want, have)
}
if want, have := int64(1), agg.Buckets[2].DocCount; want != have {
t.Errorf("Buckets[2].DocCount: want %d, have %d", want, have)
}
}
================================================
FILE: search_aggs_bucket_terms_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestTermsAggregation(t *testing.T) {
agg := NewTermsAggregation().Field("gender").Size(10).OrderByKeyDesc()
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"terms":{"field":"gender","order":[{"_key":"desc"}],"size":10}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestTermsAggregationWithSubAggregation(t *testing.T) {
subAgg := NewAvgAggregation().Field("height")
agg := NewTermsAggregation().Field("gender").Size(10).
OrderByAggregation("avg_height", false)
agg = agg.SubAggregation("avg_height", subAgg)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"aggregations":{"avg_height":{"avg":{"field":"height"}}},"terms":{"field":"gender","order":[{"avg_height":"desc"}],"size":10}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestTermsAggregationWithMultipleSubAggregation(t *testing.T) {
subAgg1 := NewAvgAggregation().Field("height")
subAgg2 := NewAvgAggregation().Field("width")
agg := NewTermsAggregation().Field("gender").Size(10).
OrderByAggregation("avg_height", false)
agg = agg.SubAggregation("avg_height", subAgg1)
agg = agg.SubAggregation("avg_width", subAgg2)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"aggregations":{"avg_height":{"avg":{"field":"height"}},"avg_width":{"avg":{"field":"width"}}},"terms":{"field":"gender","order":[{"avg_height":"desc"}],"size":10}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestTermsAggregationWithMetaData(t *testing.T) {
agg := NewTermsAggregation().Field("gender").Size(10).OrderByKeyDesc()
agg = agg.Meta(map[string]interface{}{"name": "Oliver"})
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"meta":{"name":"Oliver"},"terms":{"field":"gender","order":[{"_key":"desc"}],"size":10}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestTermsAggregationWithMissing(t *testing.T) {
agg := NewTermsAggregation().Field("gender").Size(10).Missing("n/a")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"terms":{"field":"gender","missing":"n/a","size":10}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestTermsAggregationWithIncludeExclude(t *testing.T) {
agg := NewTermsAggregation().Field("tags").Include(".*sport.*").Exclude("water_.*")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"terms":{"exclude":"water_.*","field":"tags","include":".*sport.*"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestTermsAggregationWithIncludeExcludeValues(t *testing.T) {
agg := NewTermsAggregation().Field("make").IncludeValues("mazda", "honda").ExcludeValues("rover", "jensen")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"terms":{"exclude":["rover","jensen"],"field":"make","include":["mazda","honda"]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestTermsAggregationWithPartitions(t *testing.T) {
agg := NewTermsAggregation().Field("account_id").Partition(0).NumPartitions(20)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"terms":{"field":"account_id","include":{"num_partitions":20,"partition":0}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_matrix_stats.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// MatrixMatrixStatsAggregation is a multi-value metrics aggregation
// that computes stats over numeric values extracted from the
// aggregated documents. These values can be extracted either from
// specific numeric fields in the documents, or be generated by a provided script.
//
// The stats that are returned consist of: min, max, sum, count and avg.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-metrics-stats-aggregation.html
// for details.
type MatrixStatsAggregation struct {
fields []string
missing interface{}
format string
valueType interface{}
mode string
subAggregations map[string]Aggregation
meta map[string]interface{}
}
// NewMatrixStatsAggregation initializes a new MatrixStatsAggregation.
func NewMatrixStatsAggregation() *MatrixStatsAggregation {
return &MatrixStatsAggregation{
subAggregations: make(map[string]Aggregation),
}
}
func (a *MatrixStatsAggregation) Fields(fields ...string) *MatrixStatsAggregation {
a.fields = append(a.fields, fields...)
return a
}
// Missing configures the value to use when documents miss a value.
func (a *MatrixStatsAggregation) Missing(missing interface{}) *MatrixStatsAggregation {
a.missing = missing
return a
}
// Mode specifies how to operate. Valid values are: sum, avg, median, min, or max.
func (a *MatrixStatsAggregation) Mode(mode string) *MatrixStatsAggregation {
a.mode = mode
return a
}
func (a *MatrixStatsAggregation) Format(format string) *MatrixStatsAggregation {
a.format = format
return a
}
func (a *MatrixStatsAggregation) ValueType(valueType interface{}) *MatrixStatsAggregation {
a.valueType = valueType
return a
}
func (a *MatrixStatsAggregation) SubAggregation(name string, subAggregation Aggregation) *MatrixStatsAggregation {
a.subAggregations[name] = subAggregation
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *MatrixStatsAggregation) Meta(metaData map[string]interface{}) *MatrixStatsAggregation {
a.meta = metaData
return a
}
// Source returns the JSON to serialize into the request, or an error.
func (a *MatrixStatsAggregation) Source() (interface{}, error) {
// Example:
// {
// "aggs" : {
// "matrixstats" : {
// "matrix_stats" : {
// "fields" : ["poverty", "income"],
// "missing": {"income": 50000},
// "mode": "avg",
// ...
// }
// }
// }
// }
// This method returns only the { "matrix_stats" : { ... } } part.
source := make(map[string]interface{})
opts := make(map[string]interface{})
source["matrix_stats"] = opts
// MatrixStatsAggregationBuilder
opts["fields"] = a.fields
if a.missing != nil {
opts["missing"] = a.missing
}
if a.format != "" {
opts["format"] = a.format
}
if a.valueType != nil {
opts["value_type"] = a.valueType
}
if a.mode != "" {
opts["mode"] = a.mode
}
// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_matrix_stats_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestMatrixStatsAggregation(t *testing.T) {
agg := NewMatrixStatsAggregation().
Fields("poverty", "income").
Missing(map[string]interface{}{
"income": 50000,
}).
Mode("avg").
Format("0000.0").
ValueType("double")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"matrix_stats":{"fields":["poverty","income"],"format":"0000.0","missing":{"income":50000},"mode":"avg","value_type":"double"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestMatrixStatsAggregationWithMetaData(t *testing.T) {
agg := NewMatrixStatsAggregation().
Fields("poverty", "income").
Meta(map[string]interface{}{"name": "Oliver"})
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"matrix_stats":{"fields":["poverty","income"]},"meta":{"name":"Oliver"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_metrics_avg.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// AvgAggregation is a single-value metrics aggregation that computes
// the average of numeric values that are extracted from the
// aggregated documents. These values can be extracted either from
// specific numeric fields in the documents, or be generated by
// a provided script.
//
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-metrics-avg-aggregation.html
type AvgAggregation struct {
field string
script *Script
format string
missing interface{}
subAggregations map[string]Aggregation
meta map[string]interface{}
}
func NewAvgAggregation() *AvgAggregation {
return &AvgAggregation{
subAggregations: make(map[string]Aggregation),
}
}
func (a *AvgAggregation) Field(field string) *AvgAggregation {
a.field = field
return a
}
func (a *AvgAggregation) Script(script *Script) *AvgAggregation {
a.script = script
return a
}
func (a *AvgAggregation) Format(format string) *AvgAggregation {
a.format = format
return a
}
func (a *AvgAggregation) Missing(missing interface{}) *AvgAggregation {
a.missing = missing
return a
}
func (a *AvgAggregation) SubAggregation(name string, subAggregation Aggregation) *AvgAggregation {
a.subAggregations[name] = subAggregation
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *AvgAggregation) Meta(metaData map[string]interface{}) *AvgAggregation {
a.meta = metaData
return a
}
func (a *AvgAggregation) Source() (interface{}, error) {
// Example:
// {
// "aggs" : {
// "avg_grade" : { "avg" : { "field" : "grade" } }
// }
// }
// This method returns only the { "avg" : { "field" : "grade" } } part.
source := make(map[string]interface{})
opts := make(map[string]interface{})
source["avg"] = opts
// ValuesSourceAggregationBuilder
if a.field != "" {
opts["field"] = a.field
}
if a.script != nil {
src, err := a.script.Source()
if err != nil {
return nil, err
}
opts["script"] = src
}
if a.format != "" {
opts["format"] = a.format
}
if a.missing != nil {
opts["missing"] = a.missing
}
// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_metrics_avg_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestAvgAggregation(t *testing.T) {
agg := NewAvgAggregation().Field("grade")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"avg":{"field":"grade"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestAvgAggregationWithOptions(t *testing.T) {
agg := NewAvgAggregation().Field("grade").Format("000.0").Missing(1.2)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"avg":{"field":"grade","format":"000.0","missing":1.2}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestAvgAggregationWithMetaData(t *testing.T) {
agg := NewAvgAggregation().Field("grade").Meta(map[string]interface{}{"name": "Oliver"})
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"avg":{"field":"grade"},"meta":{"name":"Oliver"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_metrics_cardinality.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// CardinalityAggregation is a single-value metrics aggregation that
// calculates an approximate count of distinct values.
// Values can be extracted either from specific fields in the document
// or generated by a script.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-metrics-cardinality-aggregation.html
type CardinalityAggregation struct {
field string
script *Script
format string
missing interface{}
subAggregations map[string]Aggregation
meta map[string]interface{}
precisionThreshold *int64
rehash *bool
}
func NewCardinalityAggregation() *CardinalityAggregation {
return &CardinalityAggregation{
subAggregations: make(map[string]Aggregation),
}
}
func (a *CardinalityAggregation) Field(field string) *CardinalityAggregation {
a.field = field
return a
}
func (a *CardinalityAggregation) Script(script *Script) *CardinalityAggregation {
a.script = script
return a
}
func (a *CardinalityAggregation) Format(format string) *CardinalityAggregation {
a.format = format
return a
}
func (a *CardinalityAggregation) Missing(missing interface{}) *CardinalityAggregation {
a.missing = missing
return a
}
func (a *CardinalityAggregation) SubAggregation(name string, subAggregation Aggregation) *CardinalityAggregation {
a.subAggregations[name] = subAggregation
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *CardinalityAggregation) Meta(metaData map[string]interface{}) *CardinalityAggregation {
a.meta = metaData
return a
}
func (a *CardinalityAggregation) PrecisionThreshold(threshold int64) *CardinalityAggregation {
a.precisionThreshold = &threshold
return a
}
func (a *CardinalityAggregation) Rehash(rehash bool) *CardinalityAggregation {
a.rehash = &rehash
return a
}
func (a *CardinalityAggregation) Source() (interface{}, error) {
// Example:
// {
// "aggs" : {
// "author_count" : {
// "cardinality" : { "field" : "author" }
// }
// }
// }
// This method returns only the "cardinality" : { "field" : "author" } part.
source := make(map[string]interface{})
opts := make(map[string]interface{})
source["cardinality"] = opts
// ValuesSourceAggregationBuilder
if a.field != "" {
opts["field"] = a.field
}
if a.script != nil {
src, err := a.script.Source()
if err != nil {
return nil, err
}
opts["script"] = src
}
if a.missing != nil {
opts["missing"] = a.missing
}
if a.format != "" {
opts["format"] = a.format
}
if a.precisionThreshold != nil {
opts["precision_threshold"] = *a.precisionThreshold
}
if a.rehash != nil {
opts["rehash"] = *a.rehash
}
// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_metrics_cardinality_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestCardinalityAggregation(t *testing.T) {
agg := NewCardinalityAggregation().Field("author.hash")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"cardinality":{"field":"author.hash"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestCardinalityAggregationWithOptions(t *testing.T) {
agg := NewCardinalityAggregation().
Field("author.hash").
PrecisionThreshold(100).
Rehash(true).
Missing(1.2)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"cardinality":{"field":"author.hash","missing":1.2,"precision_threshold":100,"rehash":true}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestCardinalityAggregationWithFormat(t *testing.T) {
agg := NewCardinalityAggregation().Field("author.hash").Format("00000")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"cardinality":{"field":"author.hash","format":"00000"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestCardinalityAggregationWithMetaData(t *testing.T) {
agg := NewCardinalityAggregation().Field("author.hash").Meta(map[string]interface{}{"name": "Oliver"})
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"cardinality":{"field":"author.hash"},"meta":{"name":"Oliver"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_metrics_extended_stats.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// ExtendedExtendedStatsAggregation is a multi-value metrics aggregation that
// computes stats over numeric values extracted from the aggregated documents.
// These values can be extracted either from specific numeric fields
// in the documents, or be generated by a provided script.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-metrics-extendedstats-aggregation.html
type ExtendedStatsAggregation struct {
field string
script *Script
format string
missing interface{}
sigma *float64
subAggregations map[string]Aggregation
meta map[string]interface{}
}
func NewExtendedStatsAggregation() *ExtendedStatsAggregation {
return &ExtendedStatsAggregation{
subAggregations: make(map[string]Aggregation),
}
}
func (a *ExtendedStatsAggregation) Field(field string) *ExtendedStatsAggregation {
a.field = field
return a
}
func (a *ExtendedStatsAggregation) Script(script *Script) *ExtendedStatsAggregation {
a.script = script
return a
}
func (a *ExtendedStatsAggregation) Format(format string) *ExtendedStatsAggregation {
a.format = format
return a
}
func (a *ExtendedStatsAggregation) Missing(missing interface{}) *ExtendedStatsAggregation {
a.missing = missing
return a
}
func (a *ExtendedStatsAggregation) Sigma(sigma float64) *ExtendedStatsAggregation {
a.sigma = &sigma
return a
}
func (a *ExtendedStatsAggregation) SubAggregation(name string, subAggregation Aggregation) *ExtendedStatsAggregation {
a.subAggregations[name] = subAggregation
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *ExtendedStatsAggregation) Meta(metaData map[string]interface{}) *ExtendedStatsAggregation {
a.meta = metaData
return a
}
func (a *ExtendedStatsAggregation) Source() (interface{}, error) {
// Example:
// {
// "aggs" : {
// "grades_stats" : { "extended_stats" : { "field" : "grade" } }
// }
// }
// This method returns only the { "extended_stats" : { "field" : "grade" } } part.
source := make(map[string]interface{})
opts := make(map[string]interface{})
source["extended_stats"] = opts
// ValuesSourceAggregationBuilder
if a.field != "" {
opts["field"] = a.field
}
if a.script != nil {
src, err := a.script.Source()
if err != nil {
return nil, err
}
opts["script"] = src
}
if a.format != "" {
opts["format"] = a.format
}
if v := a.missing; v != nil {
opts["missing"] = v
}
if v := a.sigma; v != nil {
opts["sigma"] = *v
}
// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_metrics_extended_stats_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestExtendedStatsAggregation(t *testing.T) {
agg := NewExtendedStatsAggregation().Field("grade")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"extended_stats":{"field":"grade"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestExtendedStatsAggregationWithOptions(t *testing.T) {
agg := NewExtendedStatsAggregation().
Field("grade").
Format("000.0").
Missing(1.2).
Sigma(3.1415)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"extended_stats":{"field":"grade","format":"000.0","missing":1.2,"sigma":3.1415}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_metrics_geo_bounds.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// GeoBoundsAggregation is a metric aggregation that computes the
// bounding box containing all geo_point values for a field.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-metrics-geobounds-aggregation.html
type GeoBoundsAggregation struct {
field string
script *Script
wrapLongitude *bool
subAggregations map[string]Aggregation
meta map[string]interface{}
}
func NewGeoBoundsAggregation() *GeoBoundsAggregation {
return &GeoBoundsAggregation{
subAggregations: make(map[string]Aggregation),
}
}
func (a *GeoBoundsAggregation) Field(field string) *GeoBoundsAggregation {
a.field = field
return a
}
func (a *GeoBoundsAggregation) Script(script *Script) *GeoBoundsAggregation {
a.script = script
return a
}
func (a *GeoBoundsAggregation) WrapLongitude(wrapLongitude bool) *GeoBoundsAggregation {
a.wrapLongitude = &wrapLongitude
return a
}
func (a *GeoBoundsAggregation) SubAggregation(name string, subAggregation Aggregation) *GeoBoundsAggregation {
a.subAggregations[name] = subAggregation
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *GeoBoundsAggregation) Meta(metaData map[string]interface{}) *GeoBoundsAggregation {
a.meta = metaData
return a
}
func (a *GeoBoundsAggregation) Source() (interface{}, error) {
// Example:
// {
// "query" : {
// "match" : { "business_type" : "shop" }
// },
// "aggs" : {
// "viewport" : {
// "geo_bounds" : {
// "field" : "location"
// "wrap_longitude" : "true"
// }
// }
// }
// }
//
// This method returns only the { "geo_bounds" : { ... } } part.
source := make(map[string]interface{})
opts := make(map[string]interface{})
source["geo_bounds"] = opts
if a.field != "" {
opts["field"] = a.field
}
if a.script != nil {
src, err := a.script.Source()
if err != nil {
return nil, err
}
opts["script"] = src
}
if a.wrapLongitude != nil {
opts["wrap_longitude"] = *a.wrapLongitude
}
// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_metrics_geo_bounds_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestGeoBoundsAggregation(t *testing.T) {
agg := NewGeoBoundsAggregation().Field("location")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"geo_bounds":{"field":"location"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestGeoBoundsAggregationWithWrapLongitude(t *testing.T) {
agg := NewGeoBoundsAggregation().Field("location").WrapLongitude(true)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"geo_bounds":{"field":"location","wrap_longitude":true}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestGeoBoundsAggregationWithMetaData(t *testing.T) {
agg := NewGeoBoundsAggregation().Field("location").Meta(map[string]interface{}{"name": "Oliver"})
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"geo_bounds":{"field":"location"},"meta":{"name":"Oliver"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_metrics_geo_centroid.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// GeoCentroidAggregation is a metric aggregation that computes the weighted centroid
// from all coordinate values for a Geo-point datatype field.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-metrics-geocentroid-aggregation.html
type GeoCentroidAggregation struct {
field string
script *Script
subAggregations map[string]Aggregation
meta map[string]interface{}
}
func NewGeoCentroidAggregation() *GeoCentroidAggregation {
return &GeoCentroidAggregation{
subAggregations: make(map[string]Aggregation),
}
}
func (a *GeoCentroidAggregation) Field(field string) *GeoCentroidAggregation {
a.field = field
return a
}
func (a *GeoCentroidAggregation) Script(script *Script) *GeoCentroidAggregation {
a.script = script
return a
}
func (a *GeoCentroidAggregation) SubAggregation(name string, subAggregation Aggregation) *GeoCentroidAggregation {
a.subAggregations[name] = subAggregation
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *GeoCentroidAggregation) Meta(metaData map[string]interface{}) *GeoCentroidAggregation {
a.meta = metaData
return a
}
func (a *GeoCentroidAggregation) Source() (interface{}, error) {
// Example:
// {
// "query" : {
// "match" : { "business_type" : "shop" }
// },
// "aggs" : {
// "centroid" : {
// "geo_centroid" : {
// "field" : "location"
// }
// }
// }
// }
//
// This method returns only the { "geo_centroid" : { ... } } part.
source := make(map[string]interface{})
opts := make(map[string]interface{})
source["geo_centroid"] = opts
if a.field != "" {
opts["field"] = a.field
}
if a.script != nil {
src, err := a.script.Source()
if err != nil {
return nil, err
}
opts["script"] = src
}
// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_metrics_geo_centroid_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestGeoCentroidAggregation(t *testing.T) {
agg := NewGeoCentroidAggregation().Field("location")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"geo_centroid":{"field":"location"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestGeoCentroidAggregationWithMetaData(t *testing.T) {
agg := NewGeoCentroidAggregation().Field("location").Meta(map[string]interface{}{"name": "Oliver"})
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"geo_centroid":{"field":"location"},"meta":{"name":"Oliver"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_metrics_max.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// MaxAggregation is a single-value metrics aggregation that keeps track and
// returns the maximum value among the numeric values extracted from
// the aggregated documents. These values can be extracted either from
// specific numeric fields in the documents, or be generated by
// a provided script.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-metrics-max-aggregation.html
type MaxAggregation struct {
field string
script *Script
format string
missing interface{}
subAggregations map[string]Aggregation
meta map[string]interface{}
}
func NewMaxAggregation() *MaxAggregation {
return &MaxAggregation{
subAggregations: make(map[string]Aggregation),
}
}
func (a *MaxAggregation) Field(field string) *MaxAggregation {
a.field = field
return a
}
func (a *MaxAggregation) Script(script *Script) *MaxAggregation {
a.script = script
return a
}
func (a *MaxAggregation) Format(format string) *MaxAggregation {
a.format = format
return a
}
func (a *MaxAggregation) Missing(missing interface{}) *MaxAggregation {
a.missing = missing
return a
}
func (a *MaxAggregation) SubAggregation(name string, subAggregation Aggregation) *MaxAggregation {
a.subAggregations[name] = subAggregation
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *MaxAggregation) Meta(metaData map[string]interface{}) *MaxAggregation {
a.meta = metaData
return a
}
func (a *MaxAggregation) Source() (interface{}, error) {
// Example:
// {
// "aggs" : {
// "max_price" : { "max" : { "field" : "price" } }
// }
// }
// This method returns only the { "max" : { "field" : "price" } } part.
source := make(map[string]interface{})
opts := make(map[string]interface{})
source["max"] = opts
// ValuesSourceAggregationBuilder
if a.field != "" {
opts["field"] = a.field
}
if a.script != nil {
src, err := a.script.Source()
if err != nil {
return nil, err
}
opts["script"] = src
}
if a.format != "" {
opts["format"] = a.format
}
if a.missing != nil {
opts["missing"] = a.missing
}
// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_metrics_max_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestMaxAggregation(t *testing.T) {
agg := NewMaxAggregation().Field("price")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"max":{"field":"price"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestMaxAggregationWithOptions(t *testing.T) {
agg := NewMaxAggregation().
Field("price").
Format("00000.00").
Missing(1.2)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"max":{"field":"price","format":"00000.00","missing":1.2}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestMaxAggregationWithMetaData(t *testing.T) {
agg := NewMaxAggregation().Field("price").Meta(map[string]interface{}{"name": "Oliver"})
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"max":{"field":"price"},"meta":{"name":"Oliver"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_metrics_median_absolute_deviation.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// MedianAbsoluteDeviationAggregation is a measure of variability.
// It is a robust statistic, meaning that it is useful for describing data
// that may have outliers, or may not be normally distributed.
// For such data it can be more descriptive than standard deviation.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.6/search-aggregations-metrics-median-absolute-deviation-aggregation.html
// for details.
type MedianAbsoluteDeviationAggregation struct {
field string
compression *float64
script *Script
format string
missing interface{}
subAggregations map[string]Aggregation
meta map[string]interface{}
}
func NewMedianAbsoluteDeviationAggregation() *MedianAbsoluteDeviationAggregation {
return &MedianAbsoluteDeviationAggregation{
subAggregations: make(map[string]Aggregation),
}
}
func (a *MedianAbsoluteDeviationAggregation) Field(field string) *MedianAbsoluteDeviationAggregation {
a.field = field
return a
}
func (a *MedianAbsoluteDeviationAggregation) Compression(compression float64) *MedianAbsoluteDeviationAggregation {
a.compression = &compression
return a
}
func (a *MedianAbsoluteDeviationAggregation) Script(script *Script) *MedianAbsoluteDeviationAggregation {
a.script = script
return a
}
func (a *MedianAbsoluteDeviationAggregation) Format(format string) *MedianAbsoluteDeviationAggregation {
a.format = format
return a
}
func (a *MedianAbsoluteDeviationAggregation) Missing(missing interface{}) *MedianAbsoluteDeviationAggregation {
a.missing = missing
return a
}
func (a *MedianAbsoluteDeviationAggregation) SubAggregation(name string, subAggregation Aggregation) *MedianAbsoluteDeviationAggregation {
a.subAggregations[name] = subAggregation
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *MedianAbsoluteDeviationAggregation) Meta(metaData map[string]interface{}) *MedianAbsoluteDeviationAggregation {
a.meta = metaData
return a
}
func (a *MedianAbsoluteDeviationAggregation) Source() (interface{}, error) {
// Example:
// {
// "aggs" : {
// "review_variability" : { "median_absolute_deviation" : { "field" : "rating" } }
// }
// }
// This method returns only the { "median_absolute_deviation" : { "field" : "rating" } } part.
source := make(map[string]interface{})
opts := make(map[string]interface{})
source["median_absolute_deviation"] = opts
// ValuesSourceAggregationBuilder
if a.field != "" {
opts["field"] = a.field
}
if v := a.compression; v != nil {
opts["compression"] = *v
}
if a.script != nil {
src, err := a.script.Source()
if err != nil {
return nil, err
}
opts["script"] = src
}
if a.format != "" {
opts["format"] = a.format
}
if a.missing != nil {
opts["missing"] = a.missing
}
// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_metrics_median_absolute_deviation_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestMedianAbsoluteDeviationAggregation(t *testing.T) {
agg := NewMedianAbsoluteDeviationAggregation().Field("rating")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"median_absolute_deviation":{"field":"rating"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestMedianAbsoluteDeviationAggregationWithOptions(t *testing.T) {
agg := NewMedianAbsoluteDeviationAggregation().
Field("rating").
Compression(100).
Missing(5)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"median_absolute_deviation":{"compression":100,"field":"rating","missing":5}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestMedianAbsoluteDeviationAggregationWithScript(t *testing.T) {
agg := NewMedianAbsoluteDeviationAggregation().
Script(
NewScript(`doc['rating'].value * params.scaleFactor`).
Lang("painless").
Param("scaleFactor", 2.0),
)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"median_absolute_deviation":{"script":{"lang":"painless","params":{"scaleFactor":2},"source":"doc['rating'].value * params.scaleFactor"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestMedianAbsoluteDeviationAggregationWithMetaData(t *testing.T) {
agg := NewMedianAbsoluteDeviationAggregation().Field("rating").Meta(map[string]interface{}{"name": "Oliver"})
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"median_absolute_deviation":{"field":"rating"},"meta":{"name":"Oliver"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_metrics_min.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// MinAggregation is a single-value metrics aggregation that keeps track and
// returns the minimum value among numeric values extracted from the
// aggregated documents. These values can be extracted either from
// specific numeric fields in the documents, or be generated by a
// provided script.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-metrics-min-aggregation.html
type MinAggregation struct {
field string
script *Script
format string
missing interface{}
subAggregations map[string]Aggregation
meta map[string]interface{}
}
func NewMinAggregation() *MinAggregation {
return &MinAggregation{
subAggregations: make(map[string]Aggregation),
}
}
func (a *MinAggregation) Field(field string) *MinAggregation {
a.field = field
return a
}
func (a *MinAggregation) Script(script *Script) *MinAggregation {
a.script = script
return a
}
func (a *MinAggregation) Format(format string) *MinAggregation {
a.format = format
return a
}
func (a *MinAggregation) Missing(missing interface{}) *MinAggregation {
a.missing = missing
return a
}
func (a *MinAggregation) SubAggregation(name string, subAggregation Aggregation) *MinAggregation {
a.subAggregations[name] = subAggregation
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *MinAggregation) Meta(metaData map[string]interface{}) *MinAggregation {
a.meta = metaData
return a
}
func (a *MinAggregation) Source() (interface{}, error) {
// Example:
// {
// "aggs" : {
// "min_price" : { "min" : { "field" : "price" } }
// }
// }
// This method returns only the { "min" : { "field" : "price" } } part.
source := make(map[string]interface{})
opts := make(map[string]interface{})
source["min"] = opts
// ValuesSourceAggregationBuilder
if a.field != "" {
opts["field"] = a.field
}
if a.script != nil {
src, err := a.script.Source()
if err != nil {
return nil, err
}
opts["script"] = src
}
if a.format != "" {
opts["format"] = a.format
}
if a.missing != nil {
opts["missing"] = a.missing
}
// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_metrics_min_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestMinAggregation(t *testing.T) {
agg := NewMinAggregation().Field("price")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"min":{"field":"price"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestMinAggregationWithOptions(t *testing.T) {
agg := NewMinAggregation().
Field("price").
Format("00000.00").
Missing(1.2)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"min":{"field":"price","format":"00000.00","missing":1.2}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestMinAggregationWithMetaData(t *testing.T) {
agg := NewMinAggregation().Field("price").Meta(map[string]interface{}{"name": "Oliver"})
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"meta":{"name":"Oliver"},"min":{"field":"price"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_metrics_percentile_ranks.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// PercentileRanksAggregation
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-metrics-percentile-rank-aggregation.html
type PercentileRanksAggregation struct {
field string
script *Script
format string
missing interface{}
subAggregations map[string]Aggregation
meta map[string]interface{}
values []float64
compression *float64
estimator string
}
func NewPercentileRanksAggregation() *PercentileRanksAggregation {
return &PercentileRanksAggregation{
subAggregations: make(map[string]Aggregation),
values: make([]float64, 0),
}
}
func (a *PercentileRanksAggregation) Field(field string) *PercentileRanksAggregation {
a.field = field
return a
}
func (a *PercentileRanksAggregation) Script(script *Script) *PercentileRanksAggregation {
a.script = script
return a
}
func (a *PercentileRanksAggregation) Format(format string) *PercentileRanksAggregation {
a.format = format
return a
}
func (a *PercentileRanksAggregation) Missing(missing interface{}) *PercentileRanksAggregation {
a.missing = missing
return a
}
func (a *PercentileRanksAggregation) SubAggregation(name string, subAggregation Aggregation) *PercentileRanksAggregation {
a.subAggregations[name] = subAggregation
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *PercentileRanksAggregation) Meta(metaData map[string]interface{}) *PercentileRanksAggregation {
a.meta = metaData
return a
}
func (a *PercentileRanksAggregation) Values(values ...float64) *PercentileRanksAggregation {
a.values = append(a.values, values...)
return a
}
func (a *PercentileRanksAggregation) Compression(compression float64) *PercentileRanksAggregation {
a.compression = &compression
return a
}
func (a *PercentileRanksAggregation) Estimator(estimator string) *PercentileRanksAggregation {
a.estimator = estimator
return a
}
func (a *PercentileRanksAggregation) Source() (interface{}, error) {
// Example:
// {
// "aggs" : {
// "load_time_outlier" : {
// "percentile_ranks" : {
// "field" : "load_time"
// "values" : [15, 30]
// }
// }
// }
// }
// This method returns only the
// { "percentile_ranks" : { "field" : "load_time", "values" : [15, 30] } }
// part.
source := make(map[string]interface{})
opts := make(map[string]interface{})
source["percentile_ranks"] = opts
// ValuesSourceAggregationBuilder
if a.field != "" {
opts["field"] = a.field
}
if a.script != nil {
src, err := a.script.Source()
if err != nil {
return nil, err
}
opts["script"] = src
}
if a.format != "" {
opts["format"] = a.format
}
if a.missing != nil {
opts["missing"] = a.missing
}
if len(a.values) > 0 {
opts["values"] = a.values
}
if a.compression != nil {
opts["compression"] = *a.compression
}
if a.estimator != "" {
opts["estimator"] = a.estimator
}
// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_metrics_percentile_ranks_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestPercentileRanksAggregation(t *testing.T) {
agg := NewPercentileRanksAggregation().Field("load_time")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"percentile_ranks":{"field":"load_time"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestPercentileRanksAggregationWithCustomValues(t *testing.T) {
agg := NewPercentileRanksAggregation().Field("load_time").Values(15, 30)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"percentile_ranks":{"field":"load_time","values":[15,30]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestPercentileRanksAggregationWithOptions(t *testing.T) {
agg := NewPercentileRanksAggregation().
Field("load_time").
Format("000.0").
Missing(1.2)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"percentile_ranks":{"field":"load_time","format":"000.0","missing":1.2}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestPercentileRanksAggregationWithMetaData(t *testing.T) {
agg := NewPercentileRanksAggregation().Field("load_time").Meta(map[string]interface{}{"name": "Oliver"})
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"meta":{"name":"Oliver"},"percentile_ranks":{"field":"load_time"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_metrics_percentiles.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// PercentilesAggregation is a multi-value metrics aggregation
// that calculates one or more percentiles over numeric values
// extracted from the aggregated documents. These values can
// be extracted either from specific numeric fields in the documents,
// or be generated by a provided script.
//
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-metrics-percentile-aggregation.html
type PercentilesAggregation struct {
field string
script *Script
format string
missing interface{}
subAggregations map[string]Aggregation
meta map[string]interface{}
percentiles []float64
method string
compression *float64
numberOfSignificantValueDigits *int
estimator string
}
func NewPercentilesAggregation() *PercentilesAggregation {
return &PercentilesAggregation{
subAggregations: make(map[string]Aggregation),
percentiles: make([]float64, 0),
method: "tdigest",
}
}
func (a *PercentilesAggregation) Field(field string) *PercentilesAggregation {
a.field = field
return a
}
func (a *PercentilesAggregation) Script(script *Script) *PercentilesAggregation {
a.script = script
return a
}
func (a *PercentilesAggregation) Format(format string) *PercentilesAggregation {
a.format = format
return a
}
func (a *PercentilesAggregation) Missing(missing interface{}) *PercentilesAggregation {
a.missing = missing
return a
}
func (a *PercentilesAggregation) SubAggregation(name string, subAggregation Aggregation) *PercentilesAggregation {
a.subAggregations[name] = subAggregation
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *PercentilesAggregation) Meta(metaData map[string]interface{}) *PercentilesAggregation {
a.meta = metaData
return a
}
func (a *PercentilesAggregation) Percentiles(percentiles ...float64) *PercentilesAggregation {
a.percentiles = append(a.percentiles, percentiles...)
return a
}
// Method is the percentiles method, which can be "tdigest" (default) or "hdr".
func (a *PercentilesAggregation) Method(method string) *PercentilesAggregation {
a.method = method
return a
}
func (a *PercentilesAggregation) Compression(compression float64) *PercentilesAggregation {
a.compression = &compression
return a
}
func (a *PercentilesAggregation) NumberOfSignificantValueDigits(digits int) *PercentilesAggregation {
a.numberOfSignificantValueDigits = &digits
return a
}
func (a *PercentilesAggregation) Estimator(estimator string) *PercentilesAggregation {
a.estimator = estimator
return a
}
func (a *PercentilesAggregation) Source() (interface{}, error) {
// Example:
// {
// "aggs" : {
// "load_time_outlier" : {
// "percentiles" : {
// "field" : "load_time"
// }
// }
// }
// }
// This method returns only the
// { "percentiles" : { "field" : "load_time" } }
// part.
source := make(map[string]interface{})
opts := make(map[string]interface{})
source["percentiles"] = opts
// ValuesSourceAggregationBuilder
if a.field != "" {
opts["field"] = a.field
}
if a.script != nil {
src, err := a.script.Source()
if err != nil {
return nil, err
}
opts["script"] = src
}
if a.format != "" {
opts["format"] = a.format
}
if a.missing != nil {
opts["missing"] = a.missing
}
if len(a.percentiles) > 0 {
opts["percents"] = a.percentiles
}
switch a.method {
case "tdigest":
if c := a.compression; c != nil {
opts[a.method] = map[string]interface{}{
"compression": *c,
}
}
case "hdr":
if n := a.numberOfSignificantValueDigits; n != nil {
opts[a.method] = map[string]interface{}{
"number_of_significant_value_digits": *n,
}
}
}
if a.estimator != "" {
opts["estimator"] = a.estimator
}
// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_metrics_percentiles_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestPercentilesAggregation(t *testing.T) {
agg := NewPercentilesAggregation().Field("price")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"percentiles":{"field":"price"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestPercentilesAggregationWithCustomPercents(t *testing.T) {
agg := NewPercentilesAggregation().Field("price").Percentiles(0.2, 0.5, 0.9)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"percentiles":{"field":"price","percents":[0.2,0.5,0.9]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestPercentilesAggregationWithOptions(t *testing.T) {
agg := NewPercentilesAggregation().
Field("price").
Format("00000.00").
Missing(1.2)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"percentiles":{"field":"price","format":"00000.00","missing":1.2}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestPercentilesAggregationWithMetaData(t *testing.T) {
agg := NewPercentilesAggregation().Field("price").Meta(map[string]interface{}{"name": "Oliver"})
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"meta":{"name":"Oliver"},"percentiles":{"field":"price"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestPercentilesAggregationWithCompression(t *testing.T) {
agg := NewPercentilesAggregation().Field("load_time").Compression(200.0)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"percentiles":{"field":"load_time","tdigest":{"compression":200}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestPercentilesAggregationWithNumberOfSignificantValueDigits(t *testing.T) {
agg := NewPercentilesAggregation().
Field("load_time").
Percentiles(95, 99, 99.9).
Method("hdr").
NumberOfSignificantValueDigits(5)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"percentiles":{"field":"load_time","hdr":{"number_of_significant_value_digits":5},"percents":[95,99,99.9]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_metrics_scripted_metric.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// ScriptedMetricAggregation is a a metric aggregation that executes using scripts to provide a metric output.
//
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-metrics-scripted-metric-aggregation.html
type ScriptedMetricAggregation struct {
initScript *Script
mapScript *Script
combineScript *Script
reduceScript *Script
params map[string]interface{}
meta map[string]interface{}
}
func NewScriptedMetricAggregation() *ScriptedMetricAggregation {
a := &ScriptedMetricAggregation{}
return a
}
func (a *ScriptedMetricAggregation) InitScript(script *Script) *ScriptedMetricAggregation {
a.initScript = script
return a
}
func (a *ScriptedMetricAggregation) MapScript(script *Script) *ScriptedMetricAggregation {
a.mapScript = script
return a
}
func (a *ScriptedMetricAggregation) CombineScript(script *Script) *ScriptedMetricAggregation {
a.combineScript = script
return a
}
func (a *ScriptedMetricAggregation) ReduceScript(script *Script) *ScriptedMetricAggregation {
a.reduceScript = script
return a
}
func (a *ScriptedMetricAggregation) Params(params map[string]interface{}) *ScriptedMetricAggregation {
a.params = params
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *ScriptedMetricAggregation) Meta(metaData map[string]interface{}) *ScriptedMetricAggregation {
a.meta = metaData
return a
}
func (a *ScriptedMetricAggregation) Source() (interface{}, error) {
// Example:
// {
// "aggs" : {
// "magic_script" : { "scripted_metric" : {
// "init_script" : "state.transactions = []",
// "map_script" : "state.transactions.add(doc.type.value == 'sale' ? doc.amount.value : -1 * doc.amount.value)",
// "combine_script" : "double profit = 0; for (t in state.transactions) { profit += t } return profit",
// "reduce_script" : "double profit = 0; for (a in states) { profit += a } return profit"
// } }
// }
// }
// This method returns only the { "scripted_metric" : { ... } } part.
source := make(map[string]interface{})
opts := make(map[string]interface{})
source["scripted_metric"] = opts
if a.initScript != nil {
src, err := a.initScript.Source()
if err != nil {
return nil, err
}
opts["init_script"] = src
}
if a.mapScript != nil {
src, err := a.mapScript.Source()
if err != nil {
return nil, err
}
opts["map_script"] = src
}
if a.combineScript != nil {
src, err := a.combineScript.Source()
if err != nil {
return nil, err
}
opts["combine_script"] = src
}
if a.reduceScript != nil {
src, err := a.reduceScript.Source()
if err != nil {
return nil, err
}
opts["reduce_script"] = src
}
if a.params != nil && len(a.params) > 0 {
opts["params"] = a.params
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_metrics_scripted_metric_test.go
================================================
package elastic
import (
"encoding/json"
"testing"
)
func TestScriptedMetricAggregation(t *testing.T) {
agg := NewScriptedMetricAggregation().
InitScript(NewScript("state.transactions = []")).
MapScript(NewScript("state.transactions.add(doc.type.value == 'sale' ? doc.amount.value : -1 * doc.amount.value)")).
CombineScript(NewScript("double profit = 0; for (t in state.transactions) { profit += t } return profit")).
ReduceScript(NewScript("double profit = 0; for (a in states) { profit += a } return profit"))
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"scripted_metric":{"combine_script":{"source":"double profit = 0; for (t in state.transactions) { profit += t } return profit"},"init_script":{"source":"state.transactions = []"},"map_script":{"source":"state.transactions.add(doc.type.value == 'sale' ? doc.amount.value : -1 * doc.amount.value)"},"reduce_script":{"source":"double profit = 0; for (a in states) { profit += a } return profit"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestScriptedMetricAggregationWithParams(t *testing.T) {
agg := NewScriptedMetricAggregation().
MapScript(NewScript("r=0;")).
ReduceScript(NewScript("return params.a;")).
Params(map[string]interface{}{"a": 3})
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"scripted_metric":{"map_script":{"source":"r=0;"},"params":{"a":3},"reduce_script":{"source":"return params.a;"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestScriptedMetricAggregationWithMeta(t *testing.T) {
agg := NewScriptedMetricAggregation().
MapScript(NewScript("r=0;")).
ReduceScript(NewScript("return params.a;")).
Meta(map[string]interface{}{"foo": "bar"})
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"meta":{"foo":"bar"},"scripted_metric":{"map_script":{"source":"r=0;"},"reduce_script":{"source":"return params.a;"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_metrics_stats.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// StatsAggregation is a multi-value metrics aggregation that computes stats
// over numeric values extracted from the aggregated documents.
// These values can be extracted either from specific numeric fields
// in the documents, or be generated by a provided script.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-metrics-stats-aggregation.html
type StatsAggregation struct {
field string
script *Script
format string
missing interface{}
subAggregations map[string]Aggregation
meta map[string]interface{}
}
func NewStatsAggregation() *StatsAggregation {
return &StatsAggregation{
subAggregations: make(map[string]Aggregation),
}
}
func (a *StatsAggregation) Field(field string) *StatsAggregation {
a.field = field
return a
}
func (a *StatsAggregation) Script(script *Script) *StatsAggregation {
a.script = script
return a
}
func (a *StatsAggregation) Format(format string) *StatsAggregation {
a.format = format
return a
}
func (a *StatsAggregation) Missing(missing interface{}) *StatsAggregation {
a.missing = missing
return a
}
func (a *StatsAggregation) SubAggregation(name string, subAggregation Aggregation) *StatsAggregation {
a.subAggregations[name] = subAggregation
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *StatsAggregation) Meta(metaData map[string]interface{}) *StatsAggregation {
a.meta = metaData
return a
}
func (a *StatsAggregation) Source() (interface{}, error) {
// Example:
// {
// "aggs" : {
// "grades_stats" : { "stats" : { "field" : "grade" } }
// }
// }
// This method returns only the { "stats" : { "field" : "grade" } } part.
source := make(map[string]interface{})
opts := make(map[string]interface{})
source["stats"] = opts
// ValuesSourceAggregationBuilder
if a.field != "" {
opts["field"] = a.field
}
if a.script != nil {
src, err := a.script.Source()
if err != nil {
return nil, err
}
opts["script"] = src
}
if a.format != "" {
opts["format"] = a.format
}
if a.missing != nil {
opts["missing"] = a.missing
}
// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_metrics_stats_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestStatsAggregation(t *testing.T) {
agg := NewStatsAggregation().Field("grade")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"stats":{"field":"grade"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestStatsAggregationWithOptions(t *testing.T) {
agg := NewStatsAggregation().
Field("grade").
Format("0000.0").
Missing(1.2)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"stats":{"field":"grade","format":"0000.0","missing":1.2}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestStatsAggregationWithMetaData(t *testing.T) {
agg := NewStatsAggregation().Field("grade").Meta(map[string]interface{}{"name": "Oliver"})
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"meta":{"name":"Oliver"},"stats":{"field":"grade"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_metrics_sum.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// SumAggregation is a single-value metrics aggregation that sums up
// numeric values that are extracted from the aggregated documents.
// These values can be extracted either from specific numeric fields
// in the documents, or be generated by a provided script.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-metrics-sum-aggregation.html
type SumAggregation struct {
field string
script *Script
format string
missing interface{}
subAggregations map[string]Aggregation
meta map[string]interface{}
}
func NewSumAggregation() *SumAggregation {
return &SumAggregation{
subAggregations: make(map[string]Aggregation),
}
}
func (a *SumAggregation) Field(field string) *SumAggregation {
a.field = field
return a
}
func (a *SumAggregation) Script(script *Script) *SumAggregation {
a.script = script
return a
}
func (a *SumAggregation) Format(format string) *SumAggregation {
a.format = format
return a
}
func (a *SumAggregation) Missing(missing interface{}) *SumAggregation {
a.missing = missing
return a
}
func (a *SumAggregation) SubAggregation(name string, subAggregation Aggregation) *SumAggregation {
a.subAggregations[name] = subAggregation
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *SumAggregation) Meta(metaData map[string]interface{}) *SumAggregation {
a.meta = metaData
return a
}
func (a *SumAggregation) Source() (interface{}, error) {
// Example:
// {
// "aggs" : {
// "intraday_return" : { "sum" : { "field" : "change" } }
// }
// }
// This method returns only the { "sum" : { "field" : "change" } } part.
source := make(map[string]interface{})
opts := make(map[string]interface{})
source["sum"] = opts
// ValuesSourceAggregationBuilder
if a.field != "" {
opts["field"] = a.field
}
if a.script != nil {
src, err := a.script.Source()
if err != nil {
return nil, err
}
opts["script"] = src
}
if a.format != "" {
opts["format"] = a.format
}
if a.missing != nil {
opts["missing"] = a.missing
}
// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_metrics_sum_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestSumAggregation(t *testing.T) {
agg := NewSumAggregation().Field("price")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"sum":{"field":"price"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSumAggregationWithOptions(t *testing.T) {
agg := NewSumAggregation().
Field("price").
Format("00000.00").
Missing(1.2)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"sum":{"field":"price","format":"00000.00","missing":1.2}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSumAggregationWithMetaData(t *testing.T) {
agg := NewSumAggregation().Field("price").Meta(map[string]interface{}{"name": "Oliver"})
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"meta":{"name":"Oliver"},"sum":{"field":"price"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_metrics_top_hits.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// TopHitsAggregation keeps track of the most relevant document
// being aggregated. This aggregator is intended to be used as a
// sub aggregator, so that the top matching documents
// can be aggregated per bucket.
//
// It can effectively be used to group result sets by certain fields via
// a bucket aggregator. One or more bucket aggregators determines by
// which properties a result set get sliced into.
//
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-metrics-top-hits-aggregation.html
type TopHitsAggregation struct {
searchSource *SearchSource
}
func NewTopHitsAggregation() *TopHitsAggregation {
return &TopHitsAggregation{
searchSource: NewSearchSource(),
}
}
func (a *TopHitsAggregation) SearchSource(searchSource *SearchSource) *TopHitsAggregation {
a.searchSource = searchSource
if a.searchSource == nil {
a.searchSource = NewSearchSource()
}
return a
}
func (a *TopHitsAggregation) From(from int) *TopHitsAggregation {
a.searchSource = a.searchSource.From(from)
return a
}
func (a *TopHitsAggregation) Size(size int) *TopHitsAggregation {
a.searchSource = a.searchSource.Size(size)
return a
}
func (a *TopHitsAggregation) TrackScores(trackScores bool) *TopHitsAggregation {
a.searchSource = a.searchSource.TrackScores(trackScores)
return a
}
func (a *TopHitsAggregation) Explain(explain bool) *TopHitsAggregation {
a.searchSource = a.searchSource.Explain(explain)
return a
}
func (a *TopHitsAggregation) Version(version bool) *TopHitsAggregation {
a.searchSource = a.searchSource.Version(version)
return a
}
func (a *TopHitsAggregation) NoStoredFields() *TopHitsAggregation {
a.searchSource = a.searchSource.NoStoredFields()
return a
}
func (a *TopHitsAggregation) FetchSource(fetchSource bool) *TopHitsAggregation {
a.searchSource = a.searchSource.FetchSource(fetchSource)
return a
}
func (a *TopHitsAggregation) FetchSourceContext(fetchSourceContext *FetchSourceContext) *TopHitsAggregation {
a.searchSource = a.searchSource.FetchSourceContext(fetchSourceContext)
return a
}
func (a *TopHitsAggregation) DocvalueFields(docvalueFields ...string) *TopHitsAggregation {
a.searchSource = a.searchSource.DocvalueFields(docvalueFields...)
return a
}
func (a *TopHitsAggregation) DocvalueFieldsWithFormat(docvalueFields ...DocvalueField) *TopHitsAggregation {
a.searchSource = a.searchSource.DocvalueFieldsWithFormat(docvalueFields...)
return a
}
func (a *TopHitsAggregation) DocvalueField(docvalueField string) *TopHitsAggregation {
a.searchSource = a.searchSource.DocvalueField(docvalueField)
return a
}
func (a *TopHitsAggregation) DocvalueFieldWithFormat(docvalueField DocvalueField) *TopHitsAggregation {
a.searchSource = a.searchSource.DocvalueFieldWithFormat(docvalueField)
return a
}
func (a *TopHitsAggregation) ScriptFields(scriptFields ...*ScriptField) *TopHitsAggregation {
a.searchSource = a.searchSource.ScriptFields(scriptFields...)
return a
}
func (a *TopHitsAggregation) ScriptField(scriptField *ScriptField) *TopHitsAggregation {
a.searchSource = a.searchSource.ScriptField(scriptField)
return a
}
func (a *TopHitsAggregation) Sort(field string, ascending bool) *TopHitsAggregation {
a.searchSource = a.searchSource.Sort(field, ascending)
return a
}
func (a *TopHitsAggregation) SortWithInfo(info SortInfo) *TopHitsAggregation {
a.searchSource = a.searchSource.SortWithInfo(info)
return a
}
func (a *TopHitsAggregation) SortBy(sorter ...Sorter) *TopHitsAggregation {
a.searchSource = a.searchSource.SortBy(sorter...)
return a
}
func (a *TopHitsAggregation) Highlight(highlight *Highlight) *TopHitsAggregation {
a.searchSource = a.searchSource.Highlight(highlight)
return a
}
func (a *TopHitsAggregation) Highlighter() *Highlight {
return a.searchSource.Highlighter()
}
func (a *TopHitsAggregation) Source() (interface{}, error) {
// Example:
// {
// "aggs": {
// "top_tag_hits": {
// "top_hits": {
// "sort": [
// {
// "last_activity_date": {
// "order": "desc"
// }
// }
// ],
// "_source": {
// "include": [
// "title"
// ]
// },
// "size" : 1
// }
// }
// }
// }
// This method returns only the { "top_hits" : { ... } } part.
source := make(map[string]interface{})
src, err := a.searchSource.Source()
if err != nil {
return nil, err
}
source["top_hits"] = src
return source, nil
}
================================================
FILE: search_aggs_metrics_top_hits_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestTopHitsAggregation(t *testing.T) {
fsc := NewFetchSourceContext(true).Include("title")
agg := NewTopHitsAggregation().
Sort("last_activity_date", false).
FetchSourceContext(fsc).
Size(1)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"top_hits":{"_source":{"includes":["title"]},"size":1,"sort":[{"last_activity_date":{"order":"desc"}}]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_metrics_top_metrics.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import "errors"
// TopMetricsAggregation selects metrics from the document with the largest or smallest "sort" value.
// top_metrics is fairly similar to top_hits in spirit but because it is more limited it is able to do
// its job using less memory and is often faster.
//
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-metrics-top-metrics.html
type TopMetricsAggregation struct {
fields []string
sorter Sorter
size int
}
func NewTopMetricsAggregation() *TopMetricsAggregation {
return &TopMetricsAggregation{}
}
// Field adds a field to run aggregation against.
func (a *TopMetricsAggregation) Field(field string) *TopMetricsAggregation {
a.fields = append(a.fields, field)
return a
}
// Sort adds a sort order.
func (a *TopMetricsAggregation) Sort(field string, ascending bool) *TopMetricsAggregation {
a.sorter = SortInfo{Field: field, Ascending: ascending}
return a
}
// SortWithInfo adds a sort order.
func (a *TopMetricsAggregation) SortWithInfo(info SortInfo) *TopMetricsAggregation {
a.sorter = info
return a
}
// SortBy adds a sort order.
func (a *TopMetricsAggregation) SortBy(sorter Sorter) *TopMetricsAggregation {
a.sorter = sorter
return a
}
// Size sets the number of top documents returned by the aggregation. The default size is 1.
func (a *TopMetricsAggregation) Size(size int) *TopMetricsAggregation {
a.size = size
return a
}
func (a *TopMetricsAggregation) Source() (interface{}, error) {
params := make(map[string]interface{})
if len(a.fields) == 0 {
return nil, errors.New("field list is required for the top metrics aggregation")
}
metrics := make([]interface{}, len(a.fields))
for idx, field := range a.fields {
metrics[idx] = map[string]string{"field": field}
}
params["metrics"] = metrics
if a.sorter == nil {
return nil, errors.New("sorter is required for the top metrics aggregation")
}
sortSource, err := a.sorter.Source()
if err != nil {
return nil, err
}
params["sort"] = sortSource
if a.size > 1 {
params["size"] = a.size
}
source := map[string]interface{}{
"top_metrics": params,
}
return source, nil
}
================================================
FILE: search_aggs_metrics_top_metrics_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestTopMetricsAggregation(t *testing.T) {
agg := NewTopMetricsAggregation().
Sort("f1", false).
Field("a").
Field("b").
Size(3)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"top_metrics":{"metrics":[{"field":"a"},{"field":"b"}],"size":3,"sort":{"f1":{"order":"desc"}}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestTopMetricsAggregation_SortBy(t *testing.T) {
agg := NewTopMetricsAggregation().
SortBy(SortByDoc{}).
Field("a")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"top_metrics":{"metrics":[{"field":"a"}],"sort":"_doc"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestTopMetricsAggregation_SortWithInfo(t *testing.T) {
agg := NewTopMetricsAggregation().
SortWithInfo(SortInfo{Field: "f2", Ascending: true, UnmappedType: "int"}).
Field("b")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"top_metrics":{"metrics":[{"field":"b"}],"sort":{"f2":{"order":"asc","unmapped_type":"int"}}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestTopMetricsAggregation_FailNoSorter(t *testing.T) {
agg := NewTopMetricsAggregation().
Field("a").
Field("b")
_, err := agg.Source()
if err == nil || err.Error() != "sorter is required for the top metrics aggregation" {
t.Fatal(err)
}
}
func TestTopMetricsAggregation_FailNoFields(t *testing.T) {
agg := NewTopMetricsAggregation().
Sort("f1", false)
_, err := agg.Source()
if err == nil || err.Error() != "field list is required for the top metrics aggregation" {
t.Fatal(err)
}
}
================================================
FILE: search_aggs_metrics_value_count.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// ValueCountAggregation is a single-value metrics aggregation that counts
// the number of values that are extracted from the aggregated documents.
// These values can be extracted either from specific fields in the documents,
// or be generated by a provided script. Typically, this aggregator will be
// used in conjunction with other single-value aggregations.
// For example, when computing the avg one might be interested in the
// number of values the average is computed over.
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-metrics-valuecount-aggregation.html
type ValueCountAggregation struct {
field string
script *Script
format string
subAggregations map[string]Aggregation
meta map[string]interface{}
}
func NewValueCountAggregation() *ValueCountAggregation {
return &ValueCountAggregation{
subAggregations: make(map[string]Aggregation),
}
}
func (a *ValueCountAggregation) Field(field string) *ValueCountAggregation {
a.field = field
return a
}
func (a *ValueCountAggregation) Script(script *Script) *ValueCountAggregation {
a.script = script
return a
}
func (a *ValueCountAggregation) Format(format string) *ValueCountAggregation {
a.format = format
return a
}
func (a *ValueCountAggregation) SubAggregation(name string, subAggregation Aggregation) *ValueCountAggregation {
a.subAggregations[name] = subAggregation
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *ValueCountAggregation) Meta(metaData map[string]interface{}) *ValueCountAggregation {
a.meta = metaData
return a
}
func (a *ValueCountAggregation) Source() (interface{}, error) {
// Example:
// {
// "aggs" : {
// "grades_count" : { "value_count" : { "field" : "grade" } }
// }
// }
// This method returns only the { "value_count" : { "field" : "grade" } } part.
source := make(map[string]interface{})
opts := make(map[string]interface{})
source["value_count"] = opts
// ValuesSourceAggregationBuilder
if a.field != "" {
opts["field"] = a.field
}
if a.script != nil {
src, err := a.script.Source()
if err != nil {
return nil, err
}
opts["script"] = src
}
if a.format != "" {
opts["format"] = a.format
}
// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_metrics_value_count_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestValueCountAggregation(t *testing.T) {
agg := NewValueCountAggregation().Field("grade")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"value_count":{"field":"grade"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestValueCountAggregationWithFormat(t *testing.T) {
// Format comes with 1.5.0+
agg := NewValueCountAggregation().Field("grade").Format("0000.0")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"value_count":{"field":"grade","format":"0000.0"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestValueCountAggregationWithMetaData(t *testing.T) {
agg := NewValueCountAggregation().Field("grade")
agg = agg.Meta(map[string]interface{}{"name": "Oliver"})
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"meta":{"name":"Oliver"},"value_count":{"field":"grade"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_metrics_weighted_avg.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// WeightedAvgAggregation is a single-value metrics aggregation that
// computes the weighted average of numeric values that are extracted
// from the aggregated documents. These values can be extracted either
// from specific numeric fields in the documents.
//
// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-metrics-weight-avg-aggregation.html
type WeightedAvgAggregation struct {
fields map[string]*MultiValuesSourceFieldConfig
valueType string
format string
value *MultiValuesSourceFieldConfig
weight *MultiValuesSourceFieldConfig
subAggregations map[string]Aggregation
meta map[string]interface{}
}
func NewWeightedAvgAggregation() *WeightedAvgAggregation {
return &WeightedAvgAggregation{
fields: make(map[string]*MultiValuesSourceFieldConfig),
subAggregations: make(map[string]Aggregation),
}
}
func (a *WeightedAvgAggregation) Field(field string, config *MultiValuesSourceFieldConfig) *WeightedAvgAggregation {
a.fields[field] = config
return a
}
func (a *WeightedAvgAggregation) ValueType(valueType string) *WeightedAvgAggregation {
a.valueType = valueType
return a
}
func (a *WeightedAvgAggregation) Format(format string) *WeightedAvgAggregation {
a.format = format
return a
}
func (a *WeightedAvgAggregation) Value(value *MultiValuesSourceFieldConfig) *WeightedAvgAggregation {
a.value = value
return a
}
func (a *WeightedAvgAggregation) Weight(weight *MultiValuesSourceFieldConfig) *WeightedAvgAggregation {
a.weight = weight
return a
}
func (a *WeightedAvgAggregation) SubAggregation(name string, subAggregation Aggregation) *WeightedAvgAggregation {
a.subAggregations[name] = subAggregation
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *WeightedAvgAggregation) Meta(metaData map[string]interface{}) *WeightedAvgAggregation {
a.meta = metaData
return a
}
func (a *WeightedAvgAggregation) Source() (interface{}, error) {
source := make(map[string]interface{})
opts := make(map[string]interface{})
source["weighted_avg"] = opts
if len(a.fields) > 0 {
f := make(map[string]interface{})
for name, config := range a.fields {
cfg, err := config.Source()
if err != nil {
return nil, err
}
f[name] = cfg
}
opts["fields"] = f
}
if v := a.format; v != "" {
opts["format"] = v
}
if v := a.valueType; v != "" {
opts["value_type"] = v
}
if v := a.value; v != nil {
cfg, err := v.Source()
if err != nil {
return nil, err
}
opts["value"] = cfg
}
if v := a.weight; v != nil {
cfg, err := v.Source()
if err != nil {
return nil, err
}
opts["weight"] = cfg
}
// AggregationBuilder (SubAggregations)
if len(a.subAggregations) > 0 {
aggsMap := make(map[string]interface{})
source["aggregations"] = aggsMap
for name, aggregate := range a.subAggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
// MultiValuesSourceFieldConfig represents a field configuration
// used e.g. in WeightedAvgAggregation.
type MultiValuesSourceFieldConfig struct {
FieldName string
Missing interface{}
Script *Script
TimeZone string
}
func (f *MultiValuesSourceFieldConfig) Source() (interface{}, error) {
source := make(map[string]interface{})
if v := f.Missing; v != nil {
source["missing"] = v
}
if v := f.Script; v != nil {
src, err := v.Source()
if err != nil {
return nil, err
}
source["script"] = src
}
if v := f.FieldName; v != "" {
source["field"] = v
}
if v := f.TimeZone; v != "" {
source["time_zone"] = v
}
return source, nil
}
================================================
FILE: search_aggs_metrics_weighted_avg_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestWeightedAvgAggregation(t *testing.T) {
agg := NewWeightedAvgAggregation().
Value(&MultiValuesSourceFieldConfig{
FieldName: "grade",
}).
Weight(&MultiValuesSourceFieldConfig{
FieldName: "weight",
Missing: 3,
})
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"weighted_avg":{"value":{"field":"grade"},"weight":{"field":"weight","missing":3}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_pipeline_avg_bucket.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// AvgBucketAggregation is a sibling pipeline aggregation which calculates
// the (mean) average value of a specified metric in a sibling aggregation.
// The specified metric must be numeric and the sibling aggregation must
// be a multi-bucket aggregation.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-pipeline-avg-bucket-aggregation.html
type AvgBucketAggregation struct {
format string
gapPolicy string
meta map[string]interface{}
bucketsPaths []string
}
// NewAvgBucketAggregation creates and initializes a new AvgBucketAggregation.
func NewAvgBucketAggregation() *AvgBucketAggregation {
return &AvgBucketAggregation{
bucketsPaths: make([]string, 0),
}
}
// Format to use on the output of this aggregation.
func (a *AvgBucketAggregation) Format(format string) *AvgBucketAggregation {
a.format = format
return a
}
// GapPolicy defines what should be done when a gap in the series is discovered.
// Valid values include "insert_zeros" or "skip". Default is "insert_zeros".
func (a *AvgBucketAggregation) GapPolicy(gapPolicy string) *AvgBucketAggregation {
a.gapPolicy = gapPolicy
return a
}
// GapInsertZeros inserts zeros for gaps in the series.
func (a *AvgBucketAggregation) GapInsertZeros() *AvgBucketAggregation {
a.gapPolicy = "insert_zeros"
return a
}
// GapSkip skips gaps in the series.
func (a *AvgBucketAggregation) GapSkip() *AvgBucketAggregation {
a.gapPolicy = "skip"
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *AvgBucketAggregation) Meta(metaData map[string]interface{}) *AvgBucketAggregation {
a.meta = metaData
return a
}
// BucketsPath sets the paths to the buckets to use for this pipeline aggregator.
func (a *AvgBucketAggregation) BucketsPath(bucketsPaths ...string) *AvgBucketAggregation {
a.bucketsPaths = append(a.bucketsPaths, bucketsPaths...)
return a
}
// Source returns the a JSON-serializable interface.
func (a *AvgBucketAggregation) Source() (interface{}, error) {
source := make(map[string]interface{})
params := make(map[string]interface{})
source["avg_bucket"] = params
if a.format != "" {
params["format"] = a.format
}
if a.gapPolicy != "" {
params["gap_policy"] = a.gapPolicy
}
// Add buckets paths
switch len(a.bucketsPaths) {
case 0:
case 1:
params["buckets_path"] = a.bucketsPaths[0]
default:
params["buckets_path"] = a.bucketsPaths
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_pipeline_avg_bucket_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestAvgBucketAggregation(t *testing.T) {
agg := NewAvgBucketAggregation().BucketsPath("the_sum").GapPolicy("skip")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"avg_bucket":{"buckets_path":"the_sum","gap_policy":"skip"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_pipeline_bucket_script.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// BucketScriptAggregation is a parent pipeline aggregation which executes
// a script which can perform per bucket computations on specified metrics
// in the parent multi-bucket aggregation. The specified metric must be
// numeric and the script must return a numeric value.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-pipeline-bucket-script-aggregation.html
type BucketScriptAggregation struct {
format string
gapPolicy string
script *Script
meta map[string]interface{}
bucketsPathsMap map[string]string
}
// NewBucketScriptAggregation creates and initializes a new BucketScriptAggregation.
func NewBucketScriptAggregation() *BucketScriptAggregation {
return &BucketScriptAggregation{
bucketsPathsMap: make(map[string]string),
}
}
// Format to use on the output of this aggregation.
func (a *BucketScriptAggregation) Format(format string) *BucketScriptAggregation {
a.format = format
return a
}
// GapPolicy defines what should be done when a gap in the series is discovered.
// Valid values include "insert_zeros" or "skip". Default is "insert_zeros".
func (a *BucketScriptAggregation) GapPolicy(gapPolicy string) *BucketScriptAggregation {
a.gapPolicy = gapPolicy
return a
}
// GapInsertZeros inserts zeros for gaps in the series.
func (a *BucketScriptAggregation) GapInsertZeros() *BucketScriptAggregation {
a.gapPolicy = "insert_zeros"
return a
}
// GapSkip skips gaps in the series.
func (a *BucketScriptAggregation) GapSkip() *BucketScriptAggregation {
a.gapPolicy = "skip"
return a
}
// Script is the script to run.
func (a *BucketScriptAggregation) Script(script *Script) *BucketScriptAggregation {
a.script = script
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *BucketScriptAggregation) Meta(metaData map[string]interface{}) *BucketScriptAggregation {
a.meta = metaData
return a
}
// BucketsPathsMap sets the paths to the buckets to use for this pipeline aggregator.
func (a *BucketScriptAggregation) BucketsPathsMap(bucketsPathsMap map[string]string) *BucketScriptAggregation {
a.bucketsPathsMap = bucketsPathsMap
return a
}
// AddBucketsPath adds a bucket path to use for this pipeline aggregator.
func (a *BucketScriptAggregation) AddBucketsPath(name, path string) *BucketScriptAggregation {
if a.bucketsPathsMap == nil {
a.bucketsPathsMap = make(map[string]string)
}
a.bucketsPathsMap[name] = path
return a
}
// Source returns the a JSON-serializable interface.
func (a *BucketScriptAggregation) Source() (interface{}, error) {
source := make(map[string]interface{})
params := make(map[string]interface{})
source["bucket_script"] = params
if a.format != "" {
params["format"] = a.format
}
if a.gapPolicy != "" {
params["gap_policy"] = a.gapPolicy
}
if a.script != nil {
src, err := a.script.Source()
if err != nil {
return nil, err
}
params["script"] = src
}
// Add buckets paths
if len(a.bucketsPathsMap) > 0 {
params["buckets_path"] = a.bucketsPathsMap
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_pipeline_bucket_script_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestBucketScriptAggregation(t *testing.T) {
agg := NewBucketScriptAggregation().
AddBucketsPath("tShirtSales", "t-shirts>sales").
AddBucketsPath("totalSales", "total_sales").
Script(NewScript("tShirtSales / totalSales * 100"))
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"bucket_script":{"buckets_path":{"tShirtSales":"t-shirts\u003esales","totalSales":"total_sales"},"script":{"source":"tShirtSales / totalSales * 100"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_pipeline_bucket_selector.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// BucketSelectorAggregation is a parent pipeline aggregation which
// determines whether the current bucket will be retained in the parent
// multi-bucket aggregation. The specific metric must be numeric and
// the script must return a boolean value. If the script language is
// expression then a numeric return value is permitted. In this case 0.0
// will be evaluated as false and all other values will evaluate to true.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-pipeline-bucket-selector-aggregation.html
type BucketSelectorAggregation struct {
format string
gapPolicy string
script *Script
meta map[string]interface{}
bucketsPathsMap map[string]string
}
// NewBucketSelectorAggregation creates and initializes a new BucketSelectorAggregation.
func NewBucketSelectorAggregation() *BucketSelectorAggregation {
return &BucketSelectorAggregation{
bucketsPathsMap: make(map[string]string),
}
}
// Format to use on the output of this aggregation.
func (a *BucketSelectorAggregation) Format(format string) *BucketSelectorAggregation {
a.format = format
return a
}
// GapPolicy defines what should be done when a gap in the series is discovered.
// Valid values include "insert_zeros" or "skip". Default is "insert_zeros".
func (a *BucketSelectorAggregation) GapPolicy(gapPolicy string) *BucketSelectorAggregation {
a.gapPolicy = gapPolicy
return a
}
// GapInsertZeros inserts zeros for gaps in the series.
func (a *BucketSelectorAggregation) GapInsertZeros() *BucketSelectorAggregation {
a.gapPolicy = "insert_zeros"
return a
}
// GapSkip skips gaps in the series.
func (a *BucketSelectorAggregation) GapSkip() *BucketSelectorAggregation {
a.gapPolicy = "skip"
return a
}
// Script is the script to run.
func (a *BucketSelectorAggregation) Script(script *Script) *BucketSelectorAggregation {
a.script = script
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *BucketSelectorAggregation) Meta(metaData map[string]interface{}) *BucketSelectorAggregation {
a.meta = metaData
return a
}
// BucketsPathsMap sets the paths to the buckets to use for this pipeline aggregator.
func (a *BucketSelectorAggregation) BucketsPathsMap(bucketsPathsMap map[string]string) *BucketSelectorAggregation {
a.bucketsPathsMap = bucketsPathsMap
return a
}
// AddBucketsPath adds a bucket path to use for this pipeline aggregator.
func (a *BucketSelectorAggregation) AddBucketsPath(name, path string) *BucketSelectorAggregation {
if a.bucketsPathsMap == nil {
a.bucketsPathsMap = make(map[string]string)
}
a.bucketsPathsMap[name] = path
return a
}
// Source returns the a JSON-serializable interface.
func (a *BucketSelectorAggregation) Source() (interface{}, error) {
source := make(map[string]interface{})
params := make(map[string]interface{})
source["bucket_selector"] = params
if a.format != "" {
params["format"] = a.format
}
if a.gapPolicy != "" {
params["gap_policy"] = a.gapPolicy
}
if a.script != nil {
src, err := a.script.Source()
if err != nil {
return nil, err
}
params["script"] = src
}
// Add buckets paths
if len(a.bucketsPathsMap) > 0 {
params["buckets_path"] = a.bucketsPathsMap
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_pipeline_bucket_selector_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestBucketSelectorAggregation(t *testing.T) {
agg := NewBucketSelectorAggregation().
AddBucketsPath("totalSales", "total_sales").
Script(NewScript("totalSales >= 1000"))
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"bucket_selector":{"buckets_path":{"totalSales":"total_sales"},"script":{"source":"totalSales \u003e= 1000"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_pipeline_bucket_sort.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// BucketSortAggregation parent pipeline aggregation which sorts the buckets
// of its parent multi-bucket aggregation. Zero or more sort fields may be
// specified together with the corresponding sort order. Each bucket may be
// sorted based on its _key, _count or its sub-aggregations. In addition,
// parameters from and size may be set in order to truncate the result buckets.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-pipeline-bucket-sort-aggregation.html
type BucketSortAggregation struct {
sorters []Sorter
from int
size int
gapPolicy string
meta map[string]interface{}
}
// NewBucketSortAggregation creates and initializes a new BucketSortAggregation.
func NewBucketSortAggregation() *BucketSortAggregation {
return &BucketSortAggregation{
size: -1,
}
}
// Sort adds a sort order to the list of sorters.
func (a *BucketSortAggregation) Sort(field string, ascending bool) *BucketSortAggregation {
a.sorters = append(a.sorters, SortInfo{Field: field, Ascending: ascending})
return a
}
// SortWithInfo adds a SortInfo to the list of sorters.
func (a *BucketSortAggregation) SortWithInfo(info SortInfo) *BucketSortAggregation {
a.sorters = append(a.sorters, info)
return a
}
// From adds the "from" parameter to the aggregation.
func (a *BucketSortAggregation) From(from int) *BucketSortAggregation {
a.from = from
return a
}
// Size adds the "size" parameter to the aggregation.
func (a *BucketSortAggregation) Size(size int) *BucketSortAggregation {
a.size = size
return a
}
// GapPolicy defines what should be done when a gap in the series is discovered.
// Valid values include "insert_zeros" or "skip". Default is "skip".
func (a *BucketSortAggregation) GapPolicy(gapPolicy string) *BucketSortAggregation {
a.gapPolicy = gapPolicy
return a
}
// GapInsertZeros inserts zeros for gaps in the series.
func (a *BucketSortAggregation) GapInsertZeros() *BucketSortAggregation {
a.gapPolicy = "insert_zeros"
return a
}
// GapSkip skips gaps in the series.
func (a *BucketSortAggregation) GapSkip() *BucketSortAggregation {
a.gapPolicy = "skip"
return a
}
// Meta sets the meta data in the aggregation.
// Although metadata is supported for this aggregation by Elasticsearch, it's important to
// note that there's no use to it because this aggregation does not include new data in the
// response. It merely reorders parent buckets.
func (a *BucketSortAggregation) Meta(meta map[string]interface{}) *BucketSortAggregation {
a.meta = meta
return a
}
// Source returns the a JSON-serializable interface.
func (a *BucketSortAggregation) Source() (interface{}, error) {
source := make(map[string]interface{})
params := make(map[string]interface{})
source["bucket_sort"] = params
if a.from != 0 {
params["from"] = a.from
}
if a.size != -1 {
params["size"] = a.size
}
if a.gapPolicy != "" {
params["gap_policy"] = a.gapPolicy
}
// Parses sorters to JSON-serializable interface.
if len(a.sorters) > 0 {
sorters := make([]interface{}, len(a.sorters))
params["sort"] = sorters
for idx, sorter := range a.sorters {
src, err := sorter.Source()
if err != nil {
return nil, err
}
sorters[idx] = src
}
}
// Add metadata if available.
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_pipeline_bucket_sort_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestBuckerSortAggregation(t *testing.T) {
agg := NewBucketSortAggregation().
From(2).
Size(5).
GapInsertZeros().
Sort("sort_field_1", true).
SortWithInfo(SortInfo{Field: "sort_field_2", Ascending: false})
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"bucket_sort":{"from":2,"gap_policy":"insert_zeros","size":5,"sort":[{"sort_field_1":{"order":"asc"}},{"sort_field_2":{"order":"desc"}}]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_pipeline_cumulative_sum.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// CumulativeSumAggregation is a parent pipeline aggregation which calculates
// the cumulative sum of a specified metric in a parent histogram (or date_histogram)
// aggregation. The specified metric must be numeric and the enclosing
// histogram must have min_doc_count set to 0 (default for histogram aggregations).
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-pipeline-cumulative-sum-aggregation.html
type CumulativeSumAggregation struct {
format string
meta map[string]interface{}
bucketsPaths []string
}
// NewCumulativeSumAggregation creates and initializes a new CumulativeSumAggregation.
func NewCumulativeSumAggregation() *CumulativeSumAggregation {
return &CumulativeSumAggregation{
bucketsPaths: make([]string, 0),
}
}
// Format to use on the output of this aggregation.
func (a *CumulativeSumAggregation) Format(format string) *CumulativeSumAggregation {
a.format = format
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *CumulativeSumAggregation) Meta(metaData map[string]interface{}) *CumulativeSumAggregation {
a.meta = metaData
return a
}
// BucketsPath sets the paths to the buckets to use for this pipeline aggregator.
func (a *CumulativeSumAggregation) BucketsPath(bucketsPaths ...string) *CumulativeSumAggregation {
a.bucketsPaths = append(a.bucketsPaths, bucketsPaths...)
return a
}
// Source returns the a JSON-serializable interface.
func (a *CumulativeSumAggregation) Source() (interface{}, error) {
source := make(map[string]interface{})
params := make(map[string]interface{})
source["cumulative_sum"] = params
if a.format != "" {
params["format"] = a.format
}
// Add buckets paths
switch len(a.bucketsPaths) {
case 0:
case 1:
params["buckets_path"] = a.bucketsPaths[0]
default:
params["buckets_path"] = a.bucketsPaths
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_pipeline_cumulative_sum_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestCumulativeSumAggregation(t *testing.T) {
agg := NewCumulativeSumAggregation().BucketsPath("sales")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"cumulative_sum":{"buckets_path":"sales"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_pipeline_derivative.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// DerivativeAggregation is a parent pipeline aggregation which calculates
// the derivative of a specified metric in a parent histogram (or date_histogram)
// aggregation. The specified metric must be numeric and the enclosing
// histogram must have min_doc_count set to 0 (default for histogram aggregations).
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-pipeline-derivative-aggregation.html
type DerivativeAggregation struct {
format string
gapPolicy string
unit string
meta map[string]interface{}
bucketsPaths []string
}
// NewDerivativeAggregation creates and initializes a new DerivativeAggregation.
func NewDerivativeAggregation() *DerivativeAggregation {
return &DerivativeAggregation{
bucketsPaths: make([]string, 0),
}
}
// Format to use on the output of this aggregation.
func (a *DerivativeAggregation) Format(format string) *DerivativeAggregation {
a.format = format
return a
}
// GapPolicy defines what should be done when a gap in the series is discovered.
// Valid values include "insert_zeros" or "skip". Default is "insert_zeros".
func (a *DerivativeAggregation) GapPolicy(gapPolicy string) *DerivativeAggregation {
a.gapPolicy = gapPolicy
return a
}
// GapInsertZeros inserts zeros for gaps in the series.
func (a *DerivativeAggregation) GapInsertZeros() *DerivativeAggregation {
a.gapPolicy = "insert_zeros"
return a
}
// GapSkip skips gaps in the series.
func (a *DerivativeAggregation) GapSkip() *DerivativeAggregation {
a.gapPolicy = "skip"
return a
}
// Unit sets the unit provided, e.g. "1d" or "1y".
// It is only useful when calculating the derivative using a date_histogram.
func (a *DerivativeAggregation) Unit(unit string) *DerivativeAggregation {
a.unit = unit
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *DerivativeAggregation) Meta(metaData map[string]interface{}) *DerivativeAggregation {
a.meta = metaData
return a
}
// BucketsPath sets the paths to the buckets to use for this pipeline aggregator.
func (a *DerivativeAggregation) BucketsPath(bucketsPaths ...string) *DerivativeAggregation {
a.bucketsPaths = append(a.bucketsPaths, bucketsPaths...)
return a
}
// Source returns the a JSON-serializable interface.
func (a *DerivativeAggregation) Source() (interface{}, error) {
source := make(map[string]interface{})
params := make(map[string]interface{})
source["derivative"] = params
if a.format != "" {
params["format"] = a.format
}
if a.gapPolicy != "" {
params["gap_policy"] = a.gapPolicy
}
if a.unit != "" {
params["unit"] = a.unit
}
// Add buckets paths
switch len(a.bucketsPaths) {
case 0:
case 1:
params["buckets_path"] = a.bucketsPaths[0]
default:
params["buckets_path"] = a.bucketsPaths
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_pipeline_derivative_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestDerivativeAggregation(t *testing.T) {
agg := NewDerivativeAggregation().BucketsPath("sales")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"derivative":{"buckets_path":"sales"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_pipeline_extended_stats_bucket.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// ExtendedStatsBucketAggregation is a sibling pipeline aggregation which calculates
// a variety of stats across all bucket of a specified metric in a sibling aggregation.
// The specified metric must be numeric and the sibling aggregation must
// be a multi-bucket aggregation.
//
// This aggregation provides a few more statistics (sum of squares, standard deviation, etc)
// compared to the stats_bucket aggregation.
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-pipeline-extended-stats-bucket-aggregation.html
type ExtendedStatsBucketAggregation struct {
format string
gapPolicy string
sigma *float32
meta map[string]interface{}
bucketsPaths []string
}
// NewExtendedStatsBucketAggregation creates and initializes a new ExtendedStatsBucketAggregation.
func NewExtendedStatsBucketAggregation() *ExtendedStatsBucketAggregation {
return &ExtendedStatsBucketAggregation{
bucketsPaths: make([]string, 0),
}
}
// Format to use on the output of this aggregation.
func (s *ExtendedStatsBucketAggregation) Format(format string) *ExtendedStatsBucketAggregation {
s.format = format
return s
}
// GapPolicy defines what should be done when a gap in the series is discovered.
// Valid values include "insert_zeros" or "skip". Default is "insert_zeros".
func (s *ExtendedStatsBucketAggregation) GapPolicy(gapPolicy string) *ExtendedStatsBucketAggregation {
s.gapPolicy = gapPolicy
return s
}
// GapInsertZeros inserts zeros for gaps in the series.
func (s *ExtendedStatsBucketAggregation) GapInsertZeros() *ExtendedStatsBucketAggregation {
s.gapPolicy = "insert_zeros"
return s
}
// GapSkip skips gaps in the series.
func (s *ExtendedStatsBucketAggregation) GapSkip() *ExtendedStatsBucketAggregation {
s.gapPolicy = "skip"
return s
}
// Meta sets the meta data to be included in the aggregation response.
func (s *ExtendedStatsBucketAggregation) Meta(metaData map[string]interface{}) *ExtendedStatsBucketAggregation {
s.meta = metaData
return s
}
// BucketsPath sets the paths to the buckets to use for this pipeline aggregator.
func (s *ExtendedStatsBucketAggregation) BucketsPath(bucketsPaths ...string) *ExtendedStatsBucketAggregation {
s.bucketsPaths = append(s.bucketsPaths, bucketsPaths...)
return s
}
// Sigma sets number of standard deviations above/below the mean to display
func (s *ExtendedStatsBucketAggregation) Sigma(sigma float32) *ExtendedStatsBucketAggregation {
s.sigma = &sigma
return s
}
// Source returns the a JSON-serializable interface.
func (s *ExtendedStatsBucketAggregation) Source() (interface{}, error) {
source := make(map[string]interface{})
params := make(map[string]interface{})
source["extended_stats_bucket"] = params
if s.format != "" {
params["format"] = s.format
}
if s.gapPolicy != "" {
params["gap_policy"] = s.gapPolicy
}
// Add buckets paths
switch len(s.bucketsPaths) {
case 0:
case 1:
params["buckets_path"] = s.bucketsPaths[0]
default:
params["buckets_path"] = s.bucketsPaths
}
// Add sigma is not zero or less
if s.sigma != nil && *s.sigma >= 0 {
params["sigma"] = *s.sigma
}
// Add Meta data if available
if len(s.meta) > 0 {
source["meta"] = s.meta
}
return source, nil
}
================================================
FILE: search_aggs_pipeline_extended_stats_bucket_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestExtendedStatsBucketAggregationWithGapPolicy(t *testing.T) {
agg := NewExtendedStatsBucketAggregation().BucketsPath("the_sum").GapPolicy("skip")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"extended_stats_bucket":{"buckets_path":"the_sum","gap_policy":"skip"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestExtendedStatsBucketAggregation(t *testing.T) {
agg := NewExtendedStatsBucketAggregation().BucketsPath("another_test")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"extended_stats_bucket":{"buckets_path":"another_test"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestExtendedStatsBucketAggregationWithSigma(t *testing.T) {
agg := NewExtendedStatsBucketAggregation().BucketsPath("sigma_test")
agg.Sigma(3)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"extended_stats_bucket":{"buckets_path":"sigma_test","sigma":3}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_pipeline_max_bucket.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// MaxBucketAggregation is a sibling pipeline aggregation which identifies
// the bucket(s) with the maximum value of a specified metric in a sibling
// aggregation and outputs both the value and the key(s) of the bucket(s).
// The specified metric must be numeric and the sibling aggregation must
// be a multi-bucket aggregation.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-pipeline-max-bucket-aggregation.html
type MaxBucketAggregation struct {
format string
gapPolicy string
meta map[string]interface{}
bucketsPaths []string
}
// NewMaxBucketAggregation creates and initializes a new MaxBucketAggregation.
func NewMaxBucketAggregation() *MaxBucketAggregation {
return &MaxBucketAggregation{
bucketsPaths: make([]string, 0),
}
}
// Format to use on the output of this aggregation.
func (a *MaxBucketAggregation) Format(format string) *MaxBucketAggregation {
a.format = format
return a
}
// GapPolicy defines what should be done when a gap in the series is discovered.
// Valid values include "insert_zeros" or "skip". Default is "insert_zeros".
func (a *MaxBucketAggregation) GapPolicy(gapPolicy string) *MaxBucketAggregation {
a.gapPolicy = gapPolicy
return a
}
// GapInsertZeros inserts zeros for gaps in the series.
func (a *MaxBucketAggregation) GapInsertZeros() *MaxBucketAggregation {
a.gapPolicy = "insert_zeros"
return a
}
// GapSkip skips gaps in the series.
func (a *MaxBucketAggregation) GapSkip() *MaxBucketAggregation {
a.gapPolicy = "skip"
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *MaxBucketAggregation) Meta(metaData map[string]interface{}) *MaxBucketAggregation {
a.meta = metaData
return a
}
// BucketsPath sets the paths to the buckets to use for this pipeline aggregator.
func (a *MaxBucketAggregation) BucketsPath(bucketsPaths ...string) *MaxBucketAggregation {
a.bucketsPaths = append(a.bucketsPaths, bucketsPaths...)
return a
}
// Source returns the a JSON-serializable interface.
func (a *MaxBucketAggregation) Source() (interface{}, error) {
source := make(map[string]interface{})
params := make(map[string]interface{})
source["max_bucket"] = params
if a.format != "" {
params["format"] = a.format
}
if a.gapPolicy != "" {
params["gap_policy"] = a.gapPolicy
}
// Add buckets paths
switch len(a.bucketsPaths) {
case 0:
case 1:
params["buckets_path"] = a.bucketsPaths[0]
default:
params["buckets_path"] = a.bucketsPaths
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_pipeline_max_bucket_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestMaxBucketAggregation(t *testing.T) {
agg := NewMaxBucketAggregation().BucketsPath("the_sum").GapPolicy("skip")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"max_bucket":{"buckets_path":"the_sum","gap_policy":"skip"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_pipeline_min_bucket.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// MinBucketAggregation is a sibling pipeline aggregation which identifies
// the bucket(s) with the maximum value of a specified metric in a sibling
// aggregation and outputs both the value and the key(s) of the bucket(s).
// The specified metric must be numeric and the sibling aggregation must
// be a multi-bucket aggregation.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-pipeline-min-bucket-aggregation.html
type MinBucketAggregation struct {
format string
gapPolicy string
meta map[string]interface{}
bucketsPaths []string
}
// NewMinBucketAggregation creates and initializes a new MinBucketAggregation.
func NewMinBucketAggregation() *MinBucketAggregation {
return &MinBucketAggregation{
bucketsPaths: make([]string, 0),
}
}
// Format to use on the output of this aggregation.
func (a *MinBucketAggregation) Format(format string) *MinBucketAggregation {
a.format = format
return a
}
// GapPolicy defines what should be done when a gap in the series is discovered.
// Valid values include "insert_zeros" or "skip". Default is "insert_zeros".
func (a *MinBucketAggregation) GapPolicy(gapPolicy string) *MinBucketAggregation {
a.gapPolicy = gapPolicy
return a
}
// GapInsertZeros inserts zeros for gaps in the series.
func (a *MinBucketAggregation) GapInsertZeros() *MinBucketAggregation {
a.gapPolicy = "insert_zeros"
return a
}
// GapSkip skips gaps in the series.
func (a *MinBucketAggregation) GapSkip() *MinBucketAggregation {
a.gapPolicy = "skip"
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *MinBucketAggregation) Meta(metaData map[string]interface{}) *MinBucketAggregation {
a.meta = metaData
return a
}
// BucketsPath sets the paths to the buckets to use for this pipeline aggregator.
func (a *MinBucketAggregation) BucketsPath(bucketsPaths ...string) *MinBucketAggregation {
a.bucketsPaths = append(a.bucketsPaths, bucketsPaths...)
return a
}
// Source returns the a JSON-serializable interface.
func (a *MinBucketAggregation) Source() (interface{}, error) {
source := make(map[string]interface{})
params := make(map[string]interface{})
source["min_bucket"] = params
if a.format != "" {
params["format"] = a.format
}
if a.gapPolicy != "" {
params["gap_policy"] = a.gapPolicy
}
// Add buckets paths
switch len(a.bucketsPaths) {
case 0:
case 1:
params["buckets_path"] = a.bucketsPaths[0]
default:
params["buckets_path"] = a.bucketsPaths
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_pipeline_min_bucket_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestMinBucketAggregation(t *testing.T) {
agg := NewMinBucketAggregation().BucketsPath("sales_per_month>sales").GapPolicy("skip")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"min_bucket":{"buckets_path":"sales_per_month\u003esales","gap_policy":"skip"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_pipeline_mov_avg.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// MovAvgAggregation operates on a series of data. It will slide a window
// across the data and emit the average value of that window.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-pipeline-movavg-aggregation.html
//
// Deprecated: The MovAvgAggregation has been deprecated in 6.4.0. Use the more generate MovFnAggregation instead.
type MovAvgAggregation struct {
format string
gapPolicy string
model MovAvgModel
window *int
predict *int
minimize *bool
meta map[string]interface{}
bucketsPaths []string
}
// NewMovAvgAggregation creates and initializes a new MovAvgAggregation.
//
// Deprecated: The MovAvgAggregation has been deprecated in 6.4.0. Use the more generate MovFnAggregation instead.
func NewMovAvgAggregation() *MovAvgAggregation {
return &MovAvgAggregation{
bucketsPaths: make([]string, 0),
}
}
// Format to use on the output of this aggregation.
func (a *MovAvgAggregation) Format(format string) *MovAvgAggregation {
a.format = format
return a
}
// GapPolicy defines what should be done when a gap in the series is discovered.
// Valid values include "insert_zeros" or "skip". Default is "insert_zeros".
func (a *MovAvgAggregation) GapPolicy(gapPolicy string) *MovAvgAggregation {
a.gapPolicy = gapPolicy
return a
}
// GapInsertZeros inserts zeros for gaps in the series.
func (a *MovAvgAggregation) GapInsertZeros() *MovAvgAggregation {
a.gapPolicy = "insert_zeros"
return a
}
// GapSkip skips gaps in the series.
func (a *MovAvgAggregation) GapSkip() *MovAvgAggregation {
a.gapPolicy = "skip"
return a
}
// Model is used to define what type of moving average you want to use
// in the series.
func (a *MovAvgAggregation) Model(model MovAvgModel) *MovAvgAggregation {
a.model = model
return a
}
// Window sets the window size for the moving average. This window will
// "slide" across the series, and the values inside that window will
// be used to calculate the moving avg value.
func (a *MovAvgAggregation) Window(window int) *MovAvgAggregation {
a.window = &window
return a
}
// Predict sets the number of predictions that should be returned.
// Each prediction will be spaced at the intervals in the histogram.
// E.g. a predict of 2 will return two new buckets at the end of the
// histogram with the predicted values.
func (a *MovAvgAggregation) Predict(numPredictions int) *MovAvgAggregation {
a.predict = &numPredictions
return a
}
// Minimize determines if the model should be fit to the data using a
// cost minimizing algorithm.
func (a *MovAvgAggregation) Minimize(minimize bool) *MovAvgAggregation {
a.minimize = &minimize
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *MovAvgAggregation) Meta(metaData map[string]interface{}) *MovAvgAggregation {
a.meta = metaData
return a
}
// BucketsPath sets the paths to the buckets to use for this pipeline aggregator.
func (a *MovAvgAggregation) BucketsPath(bucketsPaths ...string) *MovAvgAggregation {
a.bucketsPaths = append(a.bucketsPaths, bucketsPaths...)
return a
}
// Source returns the a JSON-serializable interface.
func (a *MovAvgAggregation) Source() (interface{}, error) {
source := make(map[string]interface{})
params := make(map[string]interface{})
source["moving_avg"] = params
if a.format != "" {
params["format"] = a.format
}
if a.gapPolicy != "" {
params["gap_policy"] = a.gapPolicy
}
if a.model != nil {
params["model"] = a.model.Name()
settings := a.model.Settings()
if len(settings) > 0 {
params["settings"] = settings
}
}
if a.window != nil {
params["window"] = *a.window
}
if a.predict != nil {
params["predict"] = *a.predict
}
if a.minimize != nil {
params["minimize"] = *a.minimize
}
// Add buckets paths
switch len(a.bucketsPaths) {
case 0:
case 1:
params["buckets_path"] = a.bucketsPaths[0]
default:
params["buckets_path"] = a.bucketsPaths
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
// -- Models for moving averages --
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-pipeline-movavg-aggregation.html#_models
// MovAvgModel specifies the model to use with the MovAvgAggregation.
type MovAvgModel interface {
Name() string
Settings() map[string]interface{}
}
// -- EWMA --
// EWMAMovAvgModel calculates an exponentially weighted moving average.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-pipeline-movavg-aggregation.html#_ewma_exponentially_weighted
type EWMAMovAvgModel struct {
alpha *float64
}
// NewEWMAMovAvgModel creates and initializes a new EWMAMovAvgModel.
func NewEWMAMovAvgModel() *EWMAMovAvgModel {
return &EWMAMovAvgModel{}
}
// Alpha controls the smoothing of the data. Alpha = 1 retains no memory
// of past values (e.g. a random walk), while alpha = 0 retains infinite
// memory of past values (e.g. the series mean). Useful values are somewhere
// in between. Defaults to 0.5.
func (m *EWMAMovAvgModel) Alpha(alpha float64) *EWMAMovAvgModel {
m.alpha = &alpha
return m
}
// Name of the model.
func (m *EWMAMovAvgModel) Name() string {
return "ewma"
}
// Settings of the model.
func (m *EWMAMovAvgModel) Settings() map[string]interface{} {
settings := make(map[string]interface{})
if m.alpha != nil {
settings["alpha"] = *m.alpha
}
return settings
}
// -- Holt linear --
// HoltLinearMovAvgModel calculates a doubly exponential weighted moving average.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-pipeline-movavg-aggregation.html#_holt_linear
type HoltLinearMovAvgModel struct {
alpha *float64
beta *float64
}
// NewHoltLinearMovAvgModel creates and initializes a new HoltLinearMovAvgModel.
func NewHoltLinearMovAvgModel() *HoltLinearMovAvgModel {
return &HoltLinearMovAvgModel{}
}
// Alpha controls the smoothing of the data. Alpha = 1 retains no memory
// of past values (e.g. a random walk), while alpha = 0 retains infinite
// memory of past values (e.g. the series mean). Useful values are somewhere
// in between. Defaults to 0.5.
func (m *HoltLinearMovAvgModel) Alpha(alpha float64) *HoltLinearMovAvgModel {
m.alpha = &alpha
return m
}
// Beta is equivalent to Alpha but controls the smoothing of the trend
// instead of the data.
func (m *HoltLinearMovAvgModel) Beta(beta float64) *HoltLinearMovAvgModel {
m.beta = &beta
return m
}
// Name of the model.
func (m *HoltLinearMovAvgModel) Name() string {
return "holt"
}
// Settings of the model.
func (m *HoltLinearMovAvgModel) Settings() map[string]interface{} {
settings := make(map[string]interface{})
if m.alpha != nil {
settings["alpha"] = *m.alpha
}
if m.beta != nil {
settings["beta"] = *m.beta
}
return settings
}
// -- Holt Winters --
// HoltWintersMovAvgModel calculates a triple exponential weighted moving average.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-pipeline-movavg-aggregation.html#_holt_winters
type HoltWintersMovAvgModel struct {
alpha *float64
beta *float64
gamma *float64
period *int
seasonalityType string
pad *bool
}
// NewHoltWintersMovAvgModel creates and initializes a new HoltWintersMovAvgModel.
func NewHoltWintersMovAvgModel() *HoltWintersMovAvgModel {
return &HoltWintersMovAvgModel{}
}
// Alpha controls the smoothing of the data. Alpha = 1 retains no memory
// of past values (e.g. a random walk), while alpha = 0 retains infinite
// memory of past values (e.g. the series mean). Useful values are somewhere
// in between. Defaults to 0.5.
func (m *HoltWintersMovAvgModel) Alpha(alpha float64) *HoltWintersMovAvgModel {
m.alpha = &alpha
return m
}
// Beta is equivalent to Alpha but controls the smoothing of the trend
// instead of the data.
func (m *HoltWintersMovAvgModel) Beta(beta float64) *HoltWintersMovAvgModel {
m.beta = &beta
return m
}
func (m *HoltWintersMovAvgModel) Gamma(gamma float64) *HoltWintersMovAvgModel {
m.gamma = &gamma
return m
}
func (m *HoltWintersMovAvgModel) Period(period int) *HoltWintersMovAvgModel {
m.period = &period
return m
}
func (m *HoltWintersMovAvgModel) SeasonalityType(typ string) *HoltWintersMovAvgModel {
m.seasonalityType = typ
return m
}
func (m *HoltWintersMovAvgModel) Pad(pad bool) *HoltWintersMovAvgModel {
m.pad = &pad
return m
}
// Name of the model.
func (m *HoltWintersMovAvgModel) Name() string {
return "holt_winters"
}
// Settings of the model.
func (m *HoltWintersMovAvgModel) Settings() map[string]interface{} {
settings := make(map[string]interface{})
if m.alpha != nil {
settings["alpha"] = *m.alpha
}
if m.beta != nil {
settings["beta"] = *m.beta
}
if m.gamma != nil {
settings["gamma"] = *m.gamma
}
if m.period != nil {
settings["period"] = *m.period
}
if m.pad != nil {
settings["pad"] = *m.pad
}
if m.seasonalityType != "" {
settings["type"] = m.seasonalityType
}
return settings
}
// -- Linear --
// LinearMovAvgModel calculates a linearly weighted moving average, such
// that older values are linearly less important. "Time" is determined
// by position in collection.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-pipeline-movavg-aggregation.html#_linear
type LinearMovAvgModel struct {
}
// NewLinearMovAvgModel creates and initializes a new LinearMovAvgModel.
func NewLinearMovAvgModel() *LinearMovAvgModel {
return &LinearMovAvgModel{}
}
// Name of the model.
func (m *LinearMovAvgModel) Name() string {
return "linear"
}
// Settings of the model.
func (m *LinearMovAvgModel) Settings() map[string]interface{} {
return nil
}
// -- Simple --
// SimpleMovAvgModel calculates a simple unweighted (arithmetic) moving average.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-pipeline-movavg-aggregation.html#_simple
type SimpleMovAvgModel struct {
}
// NewSimpleMovAvgModel creates and initializes a new SimpleMovAvgModel.
func NewSimpleMovAvgModel() *SimpleMovAvgModel {
return &SimpleMovAvgModel{}
}
// Name of the model.
func (m *SimpleMovAvgModel) Name() string {
return "simple"
}
// Settings of the model.
func (m *SimpleMovAvgModel) Settings() map[string]interface{} {
return nil
}
================================================
FILE: search_aggs_pipeline_mov_avg_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestMovAvgAggregation(t *testing.T) {
agg := NewMovAvgAggregation().BucketsPath("the_sum")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"moving_avg":{"buckets_path":"the_sum"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestMovAvgAggregationWithSimpleModel(t *testing.T) {
agg := NewMovAvgAggregation().BucketsPath("the_sum").Window(30).Model(NewSimpleMovAvgModel())
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"moving_avg":{"buckets_path":"the_sum","model":"simple","window":30}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestMovAvgAggregationWithLinearModel(t *testing.T) {
agg := NewMovAvgAggregation().BucketsPath("the_sum").Window(30).Model(NewLinearMovAvgModel())
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"moving_avg":{"buckets_path":"the_sum","model":"linear","window":30}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestMovAvgAggregationWithEWMAModel(t *testing.T) {
agg := NewMovAvgAggregation().BucketsPath("the_sum").Window(30).Model(NewEWMAMovAvgModel().Alpha(0.5))
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"moving_avg":{"buckets_path":"the_sum","model":"ewma","settings":{"alpha":0.5},"window":30}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestMovAvgAggregationWithHoltLinearModel(t *testing.T) {
agg := NewMovAvgAggregation().BucketsPath("the_sum").Window(30).
Model(NewHoltLinearMovAvgModel().Alpha(0.5).Beta(0.4))
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"moving_avg":{"buckets_path":"the_sum","model":"holt","settings":{"alpha":0.5,"beta":0.4},"window":30}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestMovAvgAggregationWithHoltWintersModel(t *testing.T) {
agg := NewMovAvgAggregation().BucketsPath("the_sum").Window(30).Predict(10).Minimize(true).
Model(NewHoltWintersMovAvgModel().Alpha(0.5).Beta(0.4).Gamma(0.3).Period(7).Pad(true))
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"moving_avg":{"buckets_path":"the_sum","minimize":true,"model":"holt_winters","predict":10,"settings":{"alpha":0.5,"beta":0.4,"gamma":0.3,"pad":true,"period":7},"window":30}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_pipeline_mov_fn.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// MovFnAggregation, given an ordered series of data, will slice a window across
// the data and allow the user to specify a custom script that is executed for
// each window of data.
//
// You must pass a script to process the values. There are a number of predefined
// script functions you can use as described here:
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-pipeline-movfn-aggregation.html#_pre_built_functions.
//
// Example:
// agg := elastic.NewMovFnAggregation(
// "the_sum", // bucket path
// elastic.NewScript("MovingFunctions.stdDev(values, MovingFunctions.unweightedAvg(values))"),
// 10, // window size
// )
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-pipeline-movfn-aggregation.html.
type MovFnAggregation struct {
script *Script
format string
gapPolicy string
window int
meta map[string]interface{}
bucketsPaths []string
}
// NewMovFnAggregation creates and initializes a new MovFnAggregation.
//
// Deprecated: The MovFnAggregation has been deprecated in 6.4.0. Use the more generate MovFnAggregation instead.
func NewMovFnAggregation(bucketsPath string, script *Script, window int) *MovFnAggregation {
return &MovFnAggregation{
bucketsPaths: []string{bucketsPath},
script: script,
window: window,
}
}
// Script is the script to run.
func (a *MovFnAggregation) Script(script *Script) *MovFnAggregation {
a.script = script
return a
}
// Format to use on the output of this aggregation.
func (a *MovFnAggregation) Format(format string) *MovFnAggregation {
a.format = format
return a
}
// GapPolicy defines what should be done when a gap in the series is discovered.
// Valid values include "insert_zeros" or "skip". Default is "insert_zeros".
func (a *MovFnAggregation) GapPolicy(gapPolicy string) *MovFnAggregation {
a.gapPolicy = gapPolicy
return a
}
// GapInsertZeros inserts zeros for gaps in the series.
func (a *MovFnAggregation) GapInsertZeros() *MovFnAggregation {
a.gapPolicy = "insert_zeros"
return a
}
// GapSkip skips gaps in the series.
func (a *MovFnAggregation) GapSkip() *MovFnAggregation {
a.gapPolicy = "skip"
return a
}
// Window sets the window size for this aggregation.
func (a *MovFnAggregation) Window(window int) *MovFnAggregation {
a.window = window
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *MovFnAggregation) Meta(metaData map[string]interface{}) *MovFnAggregation {
a.meta = metaData
return a
}
// BucketsPath sets the paths to the buckets to use for this pipeline aggregator.
func (a *MovFnAggregation) BucketsPath(bucketsPaths ...string) *MovFnAggregation {
a.bucketsPaths = append(a.bucketsPaths, bucketsPaths...)
return a
}
// Source returns the a JSON-serializable interface.
func (a *MovFnAggregation) Source() (interface{}, error) {
source := make(map[string]interface{})
params := make(map[string]interface{})
source["moving_fn"] = params
// Add buckets paths
switch len(a.bucketsPaths) {
case 0:
case 1:
params["buckets_path"] = a.bucketsPaths[0]
default:
params["buckets_path"] = a.bucketsPaths
}
// Script
if a.script != nil {
src, err := a.script.Source()
if err != nil {
return nil, err
}
params["script"] = src
}
if a.format != "" {
params["format"] = a.format
}
if a.gapPolicy != "" {
params["gap_policy"] = a.gapPolicy
}
params["window"] = a.window
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_pipeline_mov_fn_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestMovFnAggregation(t *testing.T) {
agg := NewMovFnAggregation(
"the_sum",
NewScript("MovingFunctions.min(values)"),
10,
)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"moving_fn":{"buckets_path":"the_sum","script":{"source":"MovingFunctions.min(values)"},"window":10}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_pipeline_percentiles_bucket.go
================================================
// Copyright 2012-2015 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// PercentilesBucketAggregation is a sibling pipeline aggregation which calculates
// percentiles across all bucket of a specified metric in a sibling aggregation.
// The specified metric must be numeric and the sibling aggregation must
// be a multi-bucket aggregation.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-pipeline-percentiles-bucket-aggregation.html
type PercentilesBucketAggregation struct {
format string
gapPolicy string
percents []float64
bucketsPaths []string
meta map[string]interface{}
}
// NewPercentilesBucketAggregation creates and initializes a new PercentilesBucketAggregation.
func NewPercentilesBucketAggregation() *PercentilesBucketAggregation {
return &PercentilesBucketAggregation{}
}
// Format to apply the output value of this aggregation.
func (p *PercentilesBucketAggregation) Format(format string) *PercentilesBucketAggregation {
p.format = format
return p
}
// Percents to calculate percentiles for in this aggregation.
func (p *PercentilesBucketAggregation) Percents(percents ...float64) *PercentilesBucketAggregation {
p.percents = percents
return p
}
// GapPolicy defines what should be done when a gap in the series is discovered.
// Valid values include "insert_zeros" or "skip". Default is "insert_zeros".
func (p *PercentilesBucketAggregation) GapPolicy(gapPolicy string) *PercentilesBucketAggregation {
p.gapPolicy = gapPolicy
return p
}
// GapInsertZeros inserts zeros for gaps in the series.
func (p *PercentilesBucketAggregation) GapInsertZeros() *PercentilesBucketAggregation {
p.gapPolicy = "insert_zeros"
return p
}
// GapSkip skips gaps in the series.
func (p *PercentilesBucketAggregation) GapSkip() *PercentilesBucketAggregation {
p.gapPolicy = "skip"
return p
}
// Meta sets the meta data to be included in the aggregation response.
func (p *PercentilesBucketAggregation) Meta(metaData map[string]interface{}) *PercentilesBucketAggregation {
p.meta = metaData
return p
}
// BucketsPath sets the paths to the buckets to use for this pipeline aggregator.
func (p *PercentilesBucketAggregation) BucketsPath(bucketsPaths ...string) *PercentilesBucketAggregation {
p.bucketsPaths = append(p.bucketsPaths, bucketsPaths...)
return p
}
// Source returns the a JSON-serializable interface.
func (p *PercentilesBucketAggregation) Source() (interface{}, error) {
source := make(map[string]interface{})
params := make(map[string]interface{})
source["percentiles_bucket"] = params
if p.format != "" {
params["format"] = p.format
}
if p.gapPolicy != "" {
params["gap_policy"] = p.gapPolicy
}
// Add buckets paths
switch len(p.bucketsPaths) {
case 0:
case 1:
params["buckets_path"] = p.bucketsPaths[0]
default:
params["buckets_path"] = p.bucketsPaths
}
// Add percents
if len(p.percents) > 0 {
params["percents"] = p.percents
}
// Add Meta data if available
if len(p.meta) > 0 {
source["meta"] = p.meta
}
return source, nil
}
================================================
FILE: search_aggs_pipeline_percentiles_bucket_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestPercentilesBucketAggregation(t *testing.T) {
agg := NewPercentilesBucketAggregation().BucketsPath("the_sum").GapPolicy("skip")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"percentiles_bucket":{"buckets_path":"the_sum","gap_policy":"skip"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestPercentilesBucketAggregationWithPercents(t *testing.T) {
agg := NewPercentilesBucketAggregation().BucketsPath("the_sum").Percents(0.1, 1.0, 5.0, 25, 50)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"percentiles_bucket":{"buckets_path":"the_sum","percents":[0.1,1,5,25,50]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_pipeline_serial_diff.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// SerialDiffAggregation implements serial differencing.
// Serial differencing is a technique where values in a time series are
// subtracted from itself at different time lags or periods.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-pipeline-serialdiff-aggregation.html
type SerialDiffAggregation struct {
format string
gapPolicy string
lag *int
meta map[string]interface{}
bucketsPaths []string
}
// NewSerialDiffAggregation creates and initializes a new SerialDiffAggregation.
func NewSerialDiffAggregation() *SerialDiffAggregation {
return &SerialDiffAggregation{
bucketsPaths: make([]string, 0),
}
}
// Format to use on the output of this aggregation.
func (a *SerialDiffAggregation) Format(format string) *SerialDiffAggregation {
a.format = format
return a
}
// GapPolicy defines what should be done when a gap in the series is discovered.
// Valid values include "insert_zeros" or "skip". Default is "insert_zeros".
func (a *SerialDiffAggregation) GapPolicy(gapPolicy string) *SerialDiffAggregation {
a.gapPolicy = gapPolicy
return a
}
// GapInsertZeros inserts zeros for gaps in the series.
func (a *SerialDiffAggregation) GapInsertZeros() *SerialDiffAggregation {
a.gapPolicy = "insert_zeros"
return a
}
// GapSkip skips gaps in the series.
func (a *SerialDiffAggregation) GapSkip() *SerialDiffAggregation {
a.gapPolicy = "skip"
return a
}
// Lag specifies the historical bucket to subtract from the current value.
// E.g. a lag of 7 will subtract the current value from the value 7 buckets
// ago. Lag must be a positive, non-zero integer.
func (a *SerialDiffAggregation) Lag(lag int) *SerialDiffAggregation {
a.lag = &lag
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *SerialDiffAggregation) Meta(metaData map[string]interface{}) *SerialDiffAggregation {
a.meta = metaData
return a
}
// BucketsPath sets the paths to the buckets to use for this pipeline aggregator.
func (a *SerialDiffAggregation) BucketsPath(bucketsPaths ...string) *SerialDiffAggregation {
a.bucketsPaths = append(a.bucketsPaths, bucketsPaths...)
return a
}
// Source returns the a JSON-serializable interface.
func (a *SerialDiffAggregation) Source() (interface{}, error) {
source := make(map[string]interface{})
params := make(map[string]interface{})
source["serial_diff"] = params
if a.format != "" {
params["format"] = a.format
}
if a.gapPolicy != "" {
params["gap_policy"] = a.gapPolicy
}
if a.lag != nil {
params["lag"] = *a.lag
}
// Add buckets paths
switch len(a.bucketsPaths) {
case 0:
case 1:
params["buckets_path"] = a.bucketsPaths[0]
default:
params["buckets_path"] = a.bucketsPaths
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_pipeline_serial_diff_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestSerialDiffAggregation(t *testing.T) {
agg := NewSerialDiffAggregation().BucketsPath("the_sum").Lag(7)
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"serial_diff":{"buckets_path":"the_sum","lag":7}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_pipeline_stats_bucket.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// StatsBucketAggregation is a sibling pipeline aggregation which calculates
// a variety of stats across all bucket of a specified metric in a sibling aggregation.
// The specified metric must be numeric and the sibling aggregation must
// be a multi-bucket aggregation.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-pipeline-stats-bucket-aggregation.html
type StatsBucketAggregation struct {
format string
gapPolicy string
meta map[string]interface{}
bucketsPaths []string
}
// NewStatsBucketAggregation creates and initializes a new StatsBucketAggregation.
func NewStatsBucketAggregation() *StatsBucketAggregation {
return &StatsBucketAggregation{
bucketsPaths: make([]string, 0),
}
}
// Format to use on the output of this aggregation.
func (s *StatsBucketAggregation) Format(format string) *StatsBucketAggregation {
s.format = format
return s
}
// GapPolicy defines what should be done when a gap in the series is discovered.
// Valid values include "insert_zeros" or "skip". Default is "insert_zeros".
func (s *StatsBucketAggregation) GapPolicy(gapPolicy string) *StatsBucketAggregation {
s.gapPolicy = gapPolicy
return s
}
// GapInsertZeros inserts zeros for gaps in the series.
func (s *StatsBucketAggregation) GapInsertZeros() *StatsBucketAggregation {
s.gapPolicy = "insert_zeros"
return s
}
// GapSkip skips gaps in the series.
func (s *StatsBucketAggregation) GapSkip() *StatsBucketAggregation {
s.gapPolicy = "skip"
return s
}
// Meta sets the meta data to be included in the aggregation response.
func (s *StatsBucketAggregation) Meta(metaData map[string]interface{}) *StatsBucketAggregation {
s.meta = metaData
return s
}
// BucketsPath sets the paths to the buckets to use for this pipeline aggregator.
func (s *StatsBucketAggregation) BucketsPath(bucketsPaths ...string) *StatsBucketAggregation {
s.bucketsPaths = append(s.bucketsPaths, bucketsPaths...)
return s
}
// Source returns the a JSON-serializable interface.
func (s *StatsBucketAggregation) Source() (interface{}, error) {
source := make(map[string]interface{})
params := make(map[string]interface{})
source["stats_bucket"] = params
if s.format != "" {
params["format"] = s.format
}
if s.gapPolicy != "" {
params["gap_policy"] = s.gapPolicy
}
// Add buckets paths
switch len(s.bucketsPaths) {
case 0:
case 1:
params["buckets_path"] = s.bucketsPaths[0]
default:
params["buckets_path"] = s.bucketsPaths
}
// Add Meta data if available
if len(s.meta) > 0 {
source["meta"] = s.meta
}
return source, nil
}
================================================
FILE: search_aggs_pipeline_stats_bucket_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestStatsBucketAggregation(t *testing.T) {
agg := NewStatsBucketAggregation().BucketsPath("the_sum").GapPolicy("skip")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"stats_bucket":{"buckets_path":"the_sum","gap_policy":"skip"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_pipeline_sum_bucket.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// SumBucketAggregation is a sibling pipeline aggregation which calculates
// the sum across all buckets of a specified metric in a sibling aggregation.
// The specified metric must be numeric and the sibling aggregation must
// be a multi-bucket aggregation.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-pipeline-sum-bucket-aggregation.html
type SumBucketAggregation struct {
format string
gapPolicy string
meta map[string]interface{}
bucketsPaths []string
}
// NewSumBucketAggregation creates and initializes a new SumBucketAggregation.
func NewSumBucketAggregation() *SumBucketAggregation {
return &SumBucketAggregation{
bucketsPaths: make([]string, 0),
}
}
// Format to use on the output of this aggregation.
func (a *SumBucketAggregation) Format(format string) *SumBucketAggregation {
a.format = format
return a
}
// GapPolicy defines what should be done when a gap in the series is discovered.
// Valid values include "insert_zeros" or "skip". Default is "insert_zeros".
func (a *SumBucketAggregation) GapPolicy(gapPolicy string) *SumBucketAggregation {
a.gapPolicy = gapPolicy
return a
}
// GapInsertZeros inserts zeros for gaps in the series.
func (a *SumBucketAggregation) GapInsertZeros() *SumBucketAggregation {
a.gapPolicy = "insert_zeros"
return a
}
// GapSkip skips gaps in the series.
func (a *SumBucketAggregation) GapSkip() *SumBucketAggregation {
a.gapPolicy = "skip"
return a
}
// Meta sets the meta data to be included in the aggregation response.
func (a *SumBucketAggregation) Meta(metaData map[string]interface{}) *SumBucketAggregation {
a.meta = metaData
return a
}
// BucketsPath sets the paths to the buckets to use for this pipeline aggregator.
func (a *SumBucketAggregation) BucketsPath(bucketsPaths ...string) *SumBucketAggregation {
a.bucketsPaths = append(a.bucketsPaths, bucketsPaths...)
return a
}
// Source returns the a JSON-serializable interface.
func (a *SumBucketAggregation) Source() (interface{}, error) {
source := make(map[string]interface{})
params := make(map[string]interface{})
source["sum_bucket"] = params
if a.format != "" {
params["format"] = a.format
}
if a.gapPolicy != "" {
params["gap_policy"] = a.gapPolicy
}
// Add buckets paths
switch len(a.bucketsPaths) {
case 0:
case 1:
params["buckets_path"] = a.bucketsPaths[0]
default:
params["buckets_path"] = a.bucketsPaths
}
// Add Meta data if available
if len(a.meta) > 0 {
source["meta"] = a.meta
}
return source, nil
}
================================================
FILE: search_aggs_pipeline_sum_bucket_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestSumBucketAggregation(t *testing.T) {
agg := NewSumBucketAggregation().BucketsPath("the_sum")
src, err := agg.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"sum_bucket":{"buckets_path":"the_sum"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_aggs_pipeline_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestAggsIntegrationAvgBucket(t *testing.T) {
//client := setupTestClientAndCreateIndexAndAddDocs(t, SetTraceLog(log.New(os.Stdout, "", log.LstdFlags)))
client := setupTestClientAndCreateIndexAndAddDocs(t)
// Match all should return all documents
builder := client.Search().
Index(testOrderIndex).
Query(NewMatchAllQuery()).
Pretty(true)
h := NewDateHistogramAggregation().Field("time").CalendarInterval("month")
h = h.SubAggregation("sales", NewSumAggregation().Field("price"))
builder = builder.Aggregation("sales_per_month", h)
builder = builder.Aggregation("avg_monthly_sales", NewAvgBucketAggregation().BucketsPath("sales_per_month>sales"))
res, err := builder.Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if res.Hits == nil {
t.Errorf("expected Hits != nil; got: nil")
}
aggs := res.Aggregations
if aggs == nil {
t.Fatal("expected aggregations != nil; got: nil")
}
agg, found := aggs.AvgBucket("avg_monthly_sales")
if !found {
t.Fatal("expected avg_monthly_sales aggregation")
}
if agg == nil {
t.Fatal("expected avg_monthly_sales aggregation")
}
if agg.Value == nil {
t.Fatal("expected avg_monthly_sales.value != nil")
}
if got, want := *agg.Value, float64(939.2); got != want {
t.Fatalf("expected avg_monthly_sales.value=%v; got: %v", want, got)
}
}
func TestAggsIntegrationDerivative(t *testing.T) {
//client := setupTestClientAndCreateIndexAndAddDocs(t, SetTraceLog(log.New(os.Stdout, "", log.LstdFlags)))
client := setupTestClientAndCreateIndexAndAddDocs(t)
// Match all should return all documents
builder := client.Search().
Index(testOrderIndex).
Query(NewMatchAllQuery()).
Pretty(true)
h := NewDateHistogramAggregation().Field("time").CalendarInterval("month")
h = h.SubAggregation("sales", NewSumAggregation().Field("price"))
h = h.SubAggregation("sales_deriv", NewDerivativeAggregation().BucketsPath("sales"))
builder = builder.Aggregation("sales_per_month", h)
res, err := builder.Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if res.Hits == nil {
t.Errorf("expected Hits != nil; got: nil")
}
aggs := res.Aggregations
if aggs == nil {
t.Fatal("expected aggregations != nil; got: nil")
}
agg, found := aggs.DateHistogram("sales_per_month")
if !found {
t.Fatal("expected sales_per_month aggregation")
}
if agg == nil {
t.Fatal("expected sales_per_month aggregation")
}
if got, want := len(agg.Buckets), 6; got != want {
t.Fatalf("expected %d buckets; got: %d", want, got)
}
if got, want := agg.Buckets[0].DocCount, int64(1); got != want {
t.Fatalf("expected DocCount=%d; got: %d", want, got)
}
if got, want := agg.Buckets[1].DocCount, int64(0); got != want {
t.Fatalf("expected DocCount=%d; got: %d", want, got)
}
if got, want := agg.Buckets[2].DocCount, int64(1); got != want {
t.Fatalf("expected DocCount=%d; got: %d", want, got)
}
if got, want := agg.Buckets[3].DocCount, int64(3); got != want {
t.Fatalf("expected DocCount=%d; got: %d", want, got)
}
if got, want := agg.Buckets[4].DocCount, int64(1); got != want {
t.Fatalf("expected DocCount=%d; got: %d", want, got)
}
if got, want := agg.Buckets[5].DocCount, int64(2); got != want {
t.Fatalf("expected DocCount=%d; got: %d", want, got)
}
d, found := agg.Buckets[0].Derivative("sales_deriv")
if found {
t.Fatal("expected no sales_deriv aggregation")
}
if d != nil {
t.Fatal("expected no sales_deriv aggregation")
}
d, found = agg.Buckets[1].Derivative("sales_deriv")
if !found {
t.Fatal("expected sales_deriv aggregation")
}
if d == nil {
t.Fatal("expected sales_deriv aggregation")
}
if d.Value != nil {
t.Fatal("expected sales_deriv value == nil")
}
d, found = agg.Buckets[2].Derivative("sales_deriv")
if !found {
t.Fatal("expected sales_deriv aggregation")
}
if d == nil {
t.Fatal("expected sales_deriv aggregation")
}
if d.Value != nil {
t.Fatal("expected sales_deriv value == nil")
}
d, found = agg.Buckets[3].Derivative("sales_deriv")
if !found {
t.Fatal("expected sales_deriv aggregation")
}
if d == nil {
t.Fatal("expected sales_deriv aggregation")
}
if d.Value == nil {
t.Fatal("expected sales_deriv value != nil")
}
if got, want := *d.Value, float64(2348.0); got != want {
t.Fatalf("expected sales_deriv.value=%v; got: %v", want, got)
}
d, found = agg.Buckets[4].Derivative("sales_deriv")
if !found {
t.Fatal("expected sales_deriv aggregation")
}
if d == nil {
t.Fatal("expected sales_deriv aggregation")
}
if d.Value == nil {
t.Fatal("expected sales_deriv value != nil")
}
if got, want := *d.Value, float64(-1658.0); got != want {
t.Fatalf("expected sales_deriv.value=%v; got: %v", want, got)
}
d, found = agg.Buckets[5].Derivative("sales_deriv")
if !found {
t.Fatal("expected sales_deriv aggregation")
}
if d == nil {
t.Fatal("expected sales_deriv aggregation")
}
if d.Value == nil {
t.Fatal("expected sales_deriv value != nil")
}
if got, want := *d.Value, float64(-722.0); got != want {
t.Fatalf("expected sales_deriv.value=%v; got: %v", want, got)
}
}
func TestAggsIntegrationMaxBucket(t *testing.T) {
//client := setupTestClientAndCreateIndexAndAddDocs(t, SetTraceLog(log.New(os.Stdout, "", log.LstdFlags)))
client := setupTestClientAndCreateIndexAndAddDocs(t)
// Match all should return all documents
builder := client.Search().
Index(testOrderIndex).
Query(NewMatchAllQuery()).
Pretty(true)
h := NewDateHistogramAggregation().Field("time").CalendarInterval("month")
h = h.SubAggregation("sales", NewSumAggregation().Field("price"))
builder = builder.Aggregation("sales_per_month", h)
builder = builder.Aggregation("max_monthly_sales", NewMaxBucketAggregation().BucketsPath("sales_per_month>sales"))
res, err := builder.Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if res.Hits == nil {
t.Errorf("expected Hits != nil; got: nil")
}
aggs := res.Aggregations
if aggs == nil {
t.Fatal("expected aggregations != nil; got: nil")
}
agg, found := aggs.MaxBucket("max_monthly_sales")
if !found {
t.Fatal("expected max_monthly_sales aggregation")
}
if agg == nil {
t.Fatal("expected max_monthly_sales aggregation")
}
if got, want := len(agg.Keys), 1; got != want {
t.Fatalf("expected len(max_monthly_sales.keys)=%d; got: %d", want, got)
}
if got, want := agg.Keys[0], "2015-04-01"; got != want {
t.Fatalf("expected max_monthly_sales.keys[0]=%v; got: %v", want, got)
}
if agg.Value == nil {
t.Fatal("expected max_monthly_sales.value != nil")
}
if got, want := *agg.Value, float64(2448); got != want {
t.Fatalf("expected max_monthly_sales.value=%v; got: %v", want, got)
}
}
func TestAggsIntegrationMinBucket(t *testing.T) {
//client := setupTestClientAndCreateIndexAndAddDocs(t, SetTraceLog(log.New(os.Stdout, "", log.LstdFlags)))
client := setupTestClientAndCreateIndexAndAddDocs(t)
// Match all should return all documents
builder := client.Search().
Index(testOrderIndex).
Query(NewMatchAllQuery()).
Pretty(true)
h := NewDateHistogramAggregation().Field("time").CalendarInterval("month")
h = h.SubAggregation("sales", NewSumAggregation().Field("price"))
builder = builder.Aggregation("sales_per_month", h)
builder = builder.Aggregation("min_monthly_sales", NewMinBucketAggregation().BucketsPath("sales_per_month>sales"))
res, err := builder.Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if res.Hits == nil {
t.Errorf("expected Hits != nil; got: nil")
}
aggs := res.Aggregations
if aggs == nil {
t.Fatal("expected aggregations != nil; got: nil")
}
agg, found := aggs.MinBucket("min_monthly_sales")
if !found {
t.Fatal("expected min_monthly_sales aggregation")
}
if agg == nil {
t.Fatal("expected min_monthly_sales aggregation")
}
if got, want := len(agg.Keys), 1; got != want {
t.Fatalf("expected len(min_monthly_sales.keys)=%d; got: %d", want, got)
}
if got, want := agg.Keys[0], "2015-06-01"; got != want {
t.Fatalf("expected min_monthly_sales.keys[0]=%v; got: %v", want, got)
}
if agg.Value == nil {
t.Fatal("expected min_monthly_sales.value != nil")
}
if got, want := *agg.Value, float64(68); got != want {
t.Fatalf("expected min_monthly_sales.value=%v; got: %v", want, got)
}
}
func TestAggsIntegrationSumBucket(t *testing.T) {
//client := setupTestClientAndCreateIndexAndAddDocs(t, SetTraceLog(log.New(os.Stdout, "", log.LstdFlags)))
client := setupTestClientAndCreateIndexAndAddDocs(t)
// Match all should return all documents
builder := client.Search().
Index(testOrderIndex).
Query(NewMatchAllQuery()).
Pretty(true)
h := NewDateHistogramAggregation().Field("time").CalendarInterval("month")
h = h.SubAggregation("sales", NewSumAggregation().Field("price"))
builder = builder.Aggregation("sales_per_month", h)
builder = builder.Aggregation("sum_monthly_sales", NewSumBucketAggregation().BucketsPath("sales_per_month>sales"))
res, err := builder.Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if res.Hits == nil {
t.Errorf("expected Hits != nil; got: nil")
}
aggs := res.Aggregations
if aggs == nil {
t.Fatal("expected aggregations != nil; got: nil")
}
agg, found := aggs.SumBucket("sum_monthly_sales")
if !found {
t.Fatal("expected sum_monthly_sales aggregation")
}
if agg == nil {
t.Fatal("expected sum_monthly_sales aggregation")
}
if agg.Value == nil {
t.Fatal("expected sum_monthly_sales.value != nil")
}
if got, want := *agg.Value, float64(4696.0); got != want {
t.Fatalf("expected sum_monthly_sales.value=%v; got: %v", want, got)
}
}
func TestAggsIntegrationMovAvg(t *testing.T) {
//client := setupTestClientAndCreateIndexAndAddDocs(t, SetTraceLog(log.New(os.Stdout, "", log.LstdFlags)))
client := setupTestClientAndCreateIndexAndAddDocs(t)
// Match all should return all documents
builder := client.Search().
Index(testOrderIndex).
Query(NewMatchAllQuery()).
Pretty(true)
h := NewDateHistogramAggregation().Field("time").CalendarInterval("month")
h = h.SubAggregation("the_sum", NewSumAggregation().Field("price"))
h = h.SubAggregation("the_movavg", NewMovAvgAggregation().BucketsPath("the_sum"))
builder = builder.Aggregation("my_date_histo", h)
res, err := builder.Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if res.Hits == nil {
t.Errorf("expected Hits != nil; got: nil")
}
aggs := res.Aggregations
if aggs == nil {
t.Fatal("expected aggregations != nil; got: nil")
}
agg, found := aggs.DateHistogram("my_date_histo")
if !found {
t.Fatal("expected sum_monthly_sales aggregation")
}
if agg == nil {
t.Fatal("expected sum_monthly_sales aggregation")
}
if got, want := len(agg.Buckets), 6; got != want {
t.Fatalf("expected %d buckets; got: %d", want, got)
}
d, found := agg.Buckets[0].MovAvg("the_movavg")
if found {
t.Fatal("expected no the_movavg aggregation")
}
if d != nil {
t.Fatal("expected no the_movavg aggregation")
}
d, found = agg.Buckets[1].MovAvg("the_movavg")
if found {
t.Fatal("expected no the_movavg aggregation")
}
if d != nil {
t.Fatal("expected no the_movavg aggregation")
}
d, found = agg.Buckets[2].MovAvg("the_movavg")
if !found {
t.Fatal("expected the_movavg aggregation")
}
if d == nil {
t.Fatal("expected the_movavg aggregation")
}
if d.Value == nil {
t.Fatal("expected the_movavg value")
}
if got, want := *d.Value, float64(1290.0); got != want {
t.Fatalf("expected %v buckets; got: %v", want, got)
}
d, found = agg.Buckets[3].MovAvg("the_movavg")
if !found {
t.Fatal("expected the_movavg aggregation")
}
if d == nil {
t.Fatal("expected the_movavg aggregation")
}
if d.Value == nil {
t.Fatal("expected the_movavg value")
}
if got, want := *d.Value, float64(695.0); got != want {
t.Fatalf("expected %v buckets; got: %v", want, got)
}
d, found = agg.Buckets[4].MovAvg("the_movavg")
if !found {
t.Fatal("expected the_movavg aggregation")
}
if d == nil {
t.Fatal("expected the_movavg aggregation")
}
if d.Value == nil {
t.Fatal("expected the_movavg value")
}
if got, want := *d.Value, float64(1279.3333333333333); got != want {
t.Fatalf("expected %v buckets; got: %v", want, got)
}
d, found = agg.Buckets[5].MovAvg("the_movavg")
if !found {
t.Fatal("expected the_movavg aggregation")
}
if d == nil {
t.Fatal("expected the_movavg aggregation")
}
if d.Value == nil {
t.Fatal("expected the_movavg value")
}
if got, want := *d.Value, float64(1157.0); got != want {
t.Fatalf("expected %v buckets; got: %v", want, got)
}
}
func TestAggsIntegrationCumulativeSum(t *testing.T) {
//client := setupTestClientAndCreateIndexAndAddDocs(t, SetTraceLog(log.New(os.Stdout, "", log.LstdFlags)))
client := setupTestClientAndCreateIndexAndAddDocs(t)
// Match all should return all documents
builder := client.Search().
Index(testOrderIndex).
Query(NewMatchAllQuery()).
Pretty(true)
h := NewDateHistogramAggregation().Field("time").CalendarInterval("month")
h = h.SubAggregation("sales", NewSumAggregation().Field("price"))
h = h.SubAggregation("cumulative_sales", NewCumulativeSumAggregation().BucketsPath("sales"))
builder = builder.Aggregation("sales_per_month", h)
res, err := builder.Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if res.Hits == nil {
t.Errorf("expected Hits != nil; got: nil")
}
aggs := res.Aggregations
if aggs == nil {
t.Fatal("expected aggregations != nil; got: nil")
}
agg, found := aggs.DateHistogram("sales_per_month")
if !found {
t.Fatal("expected sales_per_month aggregation")
}
if agg == nil {
t.Fatal("expected sales_per_month aggregation")
}
if got, want := len(agg.Buckets), 6; got != want {
t.Fatalf("expected %d buckets; got: %d", want, got)
}
if got, want := agg.Buckets[0].DocCount, int64(1); got != want {
t.Fatalf("expected DocCount=%d; got: %d", want, got)
}
if got, want := agg.Buckets[1].DocCount, int64(0); got != want {
t.Fatalf("expected DocCount=%d; got: %d", want, got)
}
if got, want := agg.Buckets[2].DocCount, int64(1); got != want {
t.Fatalf("expected DocCount=%d; got: %d", want, got)
}
if got, want := agg.Buckets[3].DocCount, int64(3); got != want {
t.Fatalf("expected DocCount=%d; got: %d", want, got)
}
if got, want := agg.Buckets[4].DocCount, int64(1); got != want {
t.Fatalf("expected DocCount=%d; got: %d", want, got)
}
if got, want := agg.Buckets[5].DocCount, int64(2); got != want {
t.Fatalf("expected DocCount=%d; got: %d", want, got)
}
d, found := agg.Buckets[0].CumulativeSum("cumulative_sales")
if !found {
t.Fatal("expected cumulative_sales aggregation")
}
if d == nil {
t.Fatal("expected cumulative_sales aggregation")
}
if d.Value == nil {
t.Fatal("expected cumulative_sales value != nil")
}
if got, want := *d.Value, float64(1290.0); got != want {
t.Fatalf("expected cumulative_sales.value=%v; got: %v", want, got)
}
d, found = agg.Buckets[1].CumulativeSum("cumulative_sales")
if !found {
t.Fatal("expected cumulative_sales aggregation")
}
if d == nil {
t.Fatal("expected cumulative_sales aggregation")
}
if d.Value == nil {
t.Fatal("expected cumulative_sales value != nil")
}
if got, want := *d.Value, float64(1290.0); got != want {
t.Fatalf("expected cumulative_sales.value=%v; got: %v", want, got)
}
d, found = agg.Buckets[2].CumulativeSum("cumulative_sales")
if !found {
t.Fatal("expected cumulative_sales aggregation")
}
if d == nil {
t.Fatal("expected cumulative_sales aggregation")
}
if d.Value == nil {
t.Fatal("expected cumulative_sales value != nil")
}
if got, want := *d.Value, float64(1390.0); got != want {
t.Fatalf("expected cumulative_sales.value=%v; got: %v", want, got)
}
d, found = agg.Buckets[3].CumulativeSum("cumulative_sales")
if !found {
t.Fatal("expected cumulative_sales aggregation")
}
if d == nil {
t.Fatal("expected cumulative_sales aggregation")
}
if d.Value == nil {
t.Fatal("expected cumulative_sales value != nil")
}
if got, want := *d.Value, float64(3838.0); got != want {
t.Fatalf("expected cumulative_sales.value=%v; got: %v", want, got)
}
d, found = agg.Buckets[4].CumulativeSum("cumulative_sales")
if !found {
t.Fatal("expected cumulative_sales aggregation")
}
if d == nil {
t.Fatal("expected cumulative_sales aggregation")
}
if d.Value == nil {
t.Fatal("expected cumulative_sales value != nil")
}
if got, want := *d.Value, float64(4628.0); got != want {
t.Fatalf("expected cumulative_sales.value=%v; got: %v", want, got)
}
d, found = agg.Buckets[5].CumulativeSum("cumulative_sales")
if !found {
t.Fatal("expected cumulative_sales aggregation")
}
if d == nil {
t.Fatal("expected cumulative_sales aggregation")
}
if d.Value == nil {
t.Fatal("expected cumulative_sales value != nil")
}
if got, want := *d.Value, float64(4696.0); got != want {
t.Fatalf("expected cumulative_sales.value=%v; got: %v", want, got)
}
}
func TestAggsIntegrationBucketScript(t *testing.T) {
// client := setupTestClientAndCreateIndexAndAddDocs(t, SetTraceLog(log.New(os.Stdout, "", log.LstdFlags)))
client := setupTestClientAndCreateIndexAndAddDocs(t)
// Match all should return all documents
builder := client.Search().
Index(testOrderIndex).
Query(NewMatchAllQuery()).
Pretty(true)
h := NewDateHistogramAggregation().Field("time").CalendarInterval("month")
h = h.SubAggregation("total_sales", NewSumAggregation().Field("price"))
appleFilter := NewFilterAggregation().Filter(NewTermQuery("manufacturer", "Apple"))
appleFilter = appleFilter.SubAggregation("sales", NewSumAggregation().Field("price"))
h = h.SubAggregation("apple_sales", appleFilter)
h = h.SubAggregation("apple_percentage",
NewBucketScriptAggregation().
GapPolicy("insert_zeros").
AddBucketsPath("appleSales", "apple_sales>sales").
AddBucketsPath("totalSales", "total_sales").
Script(NewScript("params.appleSales / params.totalSales * 100")))
builder = builder.Aggregation("sales_per_month", h)
res, err := builder.Pretty(true).Do(context.TODO())
if err != nil {
t.Fatalf("%v (maybe scripting is disabled?)", err)
}
if res.Hits == nil {
t.Errorf("expected Hits != nil; got: nil")
}
aggs := res.Aggregations
if aggs == nil {
t.Fatal("expected aggregations != nil; got: nil")
}
agg, found := aggs.DateHistogram("sales_per_month")
if !found {
t.Fatal("expected sales_per_month aggregation")
}
if agg == nil {
t.Fatal("expected sales_per_month aggregation")
}
if got, want := len(agg.Buckets), 6; got != want {
t.Fatalf("expected %d buckets; got: %d", want, got)
}
if got, want := agg.Buckets[0].DocCount, int64(1); got != want {
t.Fatalf("expected DocCount=%d; got: %d", want, got)
}
if got, want := agg.Buckets[1].DocCount, int64(0); got != want {
t.Fatalf("expected DocCount=%d; got: %d", want, got)
}
if got, want := agg.Buckets[2].DocCount, int64(1); got != want {
t.Fatalf("expected DocCount=%d; got: %d", want, got)
}
if got, want := agg.Buckets[3].DocCount, int64(3); got != want {
t.Fatalf("expected DocCount=%d; got: %d", want, got)
}
if got, want := agg.Buckets[4].DocCount, int64(1); got != want {
t.Fatalf("expected DocCount=%d; got: %d", want, got)
}
if got, want := agg.Buckets[5].DocCount, int64(2); got != want {
t.Fatalf("expected DocCount=%d; got: %d", want, got)
}
d, found := agg.Buckets[0].BucketScript("apple_percentage")
if !found {
t.Fatal("expected apple_percentage aggregation")
}
if d == nil {
t.Fatal("expected apple_percentage aggregation")
}
if d.Value == nil {
t.Fatal("expected apple_percentage value != nil")
}
if got, want := *d.Value, float64(100.0); got != want {
t.Fatalf("expected apple_percentage.value=%v; got: %v", want, got)
}
d, found = agg.Buckets[1].BucketScript("apple_percentage")
if !found {
t.Fatal("expected apple_percentage aggregation")
}
if d == nil {
t.Fatal("expected apple_percentage aggregation")
}
if d.Value != nil {
t.Fatal("expected apple_percentage value == nil")
}
d, found = agg.Buckets[2].BucketScript("apple_percentage")
if !found {
t.Fatal("expected apple_percentage aggregation")
}
if d == nil {
t.Fatal("expected apple_percentage aggregation")
}
if d.Value == nil {
t.Fatal("expected apple_percentage value != nil")
}
if got, want := *d.Value, float64(0.0); got != want {
t.Fatalf("expected apple_percentage.value=%v; got: %v", want, got)
}
d, found = agg.Buckets[3].BucketScript("apple_percentage")
if !found {
t.Fatal("expected apple_percentage aggregation")
}
if d == nil {
t.Fatal("expected apple_percentage aggregation")
}
if d.Value == nil {
t.Fatal("expected apple_percentage value != nil")
}
if got, want := *d.Value, float64(34.64052287581699); got != want {
t.Fatalf("expected apple_percentage.value=%v; got: %v", want, got)
}
d, found = agg.Buckets[4].BucketScript("apple_percentage")
if !found {
t.Fatal("expected apple_percentage aggregation")
}
if d == nil {
t.Fatal("expected apple_percentage aggregation")
}
if d.Value == nil {
t.Fatal("expected apple_percentage value != nil")
}
if got, want := *d.Value, float64(0.0); got != want {
t.Fatalf("expected apple_percentage.value=%v; got: %v", want, got)
}
d, found = agg.Buckets[5].BucketScript("apple_percentage")
if !found {
t.Fatal("expected apple_percentage aggregation")
}
if d == nil {
t.Fatal("expected apple_percentage aggregation")
}
if d.Value == nil {
t.Fatal("expected apple_percentage value != nil")
}
if got, want := *d.Value, float64(0.0); got != want {
t.Fatalf("expected apple_percentage.value=%v; got: %v", want, got)
}
}
func TestAggsIntegrationBucketSelector(t *testing.T) {
//client := setupTestClientAndCreateIndexAndAddDocs(t, SetTraceLog(log.New(os.Stdout, "", log.LstdFlags)))
client := setupTestClientAndCreateIndexAndAddDocs(t)
// Match all should return all documents
builder := client.Search().
Index(testOrderIndex).
Query(NewMatchAllQuery()).
Pretty(true)
h := NewDateHistogramAggregation().Field("time").CalendarInterval("month")
h = h.SubAggregation("total_sales", NewSumAggregation().Field("price"))
h = h.SubAggregation("sales_bucket_filter",
NewBucketSelectorAggregation().
AddBucketsPath("totalSales", "total_sales").
Script(NewScript("params.totalSales <= 100")))
builder = builder.Aggregation("sales_per_month", h)
res, err := builder.Do(context.TODO())
if err != nil {
t.Fatalf("%v (maybe scripting is disabled?)", err)
}
if res.Hits == nil {
t.Errorf("expected Hits != nil; got: nil")
}
aggs := res.Aggregations
if aggs == nil {
t.Fatal("expected aggregations != nil; got: nil")
}
agg, found := aggs.DateHistogram("sales_per_month")
if !found {
t.Fatal("expected sales_per_month aggregation")
}
if agg == nil {
t.Fatal("expected sales_per_month aggregation")
}
if got, want := len(agg.Buckets), 2; got != want {
t.Fatalf("expected %d buckets; got: %d", want, got)
}
if got, want := agg.Buckets[0].DocCount, int64(1); got != want {
t.Fatalf("expected DocCount=%d; got: %d", want, got)
}
if got, want := agg.Buckets[1].DocCount, int64(2); got != want {
t.Fatalf("expected DocCount=%d; got: %d", want, got)
}
}
func TestAggsIntegrationSerialDiff(t *testing.T) {
//client := setupTestClientAndCreateIndexAndAddDocs(t, SetTraceLog(log.New(os.Stdout, "", log.LstdFlags)))
client := setupTestClientAndCreateIndexAndAddDocs(t)
// Match all should return all documents
builder := client.Search().
Index(testOrderIndex).
Query(NewMatchAllQuery()).
Pretty(true)
h := NewDateHistogramAggregation().Field("time").CalendarInterval("month")
h = h.SubAggregation("sales", NewSumAggregation().Field("price"))
h = h.SubAggregation("the_diff", NewSerialDiffAggregation().BucketsPath("sales").Lag(1))
builder = builder.Aggregation("sales_per_month", h)
res, err := builder.Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if res.Hits == nil {
t.Errorf("expected Hits != nil; got: nil")
}
aggs := res.Aggregations
if aggs == nil {
t.Fatal("expected aggregations != nil; got: nil")
}
agg, found := aggs.DateHistogram("sales_per_month")
if !found {
t.Fatal("expected sales_per_month aggregation")
}
if agg == nil {
t.Fatal("expected sales_per_month aggregation")
}
if got, want := len(agg.Buckets), 6; got != want {
t.Fatalf("expected %d buckets; got: %d", want, got)
}
if got, want := agg.Buckets[0].DocCount, int64(1); got != want {
t.Fatalf("expected DocCount=%d; got: %d", want, got)
}
if got, want := agg.Buckets[1].DocCount, int64(0); got != want {
t.Fatalf("expected DocCount=%d; got: %d", want, got)
}
if got, want := agg.Buckets[2].DocCount, int64(1); got != want {
t.Fatalf("expected DocCount=%d; got: %d", want, got)
}
if got, want := agg.Buckets[3].DocCount, int64(3); got != want {
t.Fatalf("expected DocCount=%d; got: %d", want, got)
}
if got, want := agg.Buckets[4].DocCount, int64(1); got != want {
t.Fatalf("expected DocCount=%d; got: %d", want, got)
}
if got, want := agg.Buckets[5].DocCount, int64(2); got != want {
t.Fatalf("expected DocCount=%d; got: %d", want, got)
}
d, found := agg.Buckets[0].SerialDiff("the_diff")
if found {
t.Fatal("expected no the_diff aggregation")
}
if d != nil {
t.Fatal("expected no the_diff aggregation")
}
d, found = agg.Buckets[1].SerialDiff("the_diff")
if found {
t.Fatal("expected no the_diff aggregation")
}
if d != nil {
t.Fatal("expected no the_diff aggregation")
}
d, found = agg.Buckets[2].SerialDiff("the_diff")
if found {
t.Fatal("expected no the_diff aggregation")
}
if d != nil {
t.Fatal("expected no the_diff aggregation")
}
d, found = agg.Buckets[3].SerialDiff("the_diff")
if !found {
t.Fatal("expected the_diff aggregation")
}
if d == nil {
t.Fatal("expected the_diff aggregation")
}
if d.Value == nil {
t.Fatal("expected the_diff value != nil")
}
if got, want := *d.Value, float64(2348.0); got != want {
t.Fatalf("expected the_diff.value=%v; got: %v", want, got)
}
d, found = agg.Buckets[4].SerialDiff("the_diff")
if !found {
t.Fatal("expected the_diff aggregation")
}
if d == nil {
t.Fatal("expected the_diff aggregation")
}
if d.Value == nil {
t.Fatal("expected the_diff value != nil")
}
if got, want := *d.Value, float64(-1658.0); got != want {
t.Fatalf("expected the_diff.value=%v; got: %v", want, got)
}
d, found = agg.Buckets[5].SerialDiff("the_diff")
if !found {
t.Fatal("expected the_diff aggregation")
}
if d == nil {
t.Fatal("expected the_diff aggregation")
}
if d.Value == nil {
t.Fatal("expected the_diff value != nil")
}
if got, want := *d.Value, float64(-722.0); got != want {
t.Fatalf("expected the_diff.value=%v; got: %v", want, got)
}
}
================================================
FILE: search_aggs_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"reflect"
"strings"
"testing"
"time"
)
// TestAggs is an integration test for most aggregation types.
func TestAggs(t *testing.T) {
client := setupTestClientAndCreateIndex(t) //, SetTraceLog(log.New(os.Stdout, "", log.LstdFlags)))
tweet1 := tweet{
User: "olivere",
Retweets: 108,
Message: "Welcome to Golang and Elasticsearch.",
Image: "http://golang.org/doc/gopher/gophercolor.png",
Tags: []string{"golang", "elasticsearch"},
Location: "48.1333,11.5667", // lat,lon
Created: time.Date(2012, 12, 12, 17, 38, 34, 0, time.UTC),
}
tweet2 := tweet{
User: "olivere",
Retweets: 0,
Message: "Another unrelated topic.",
Tags: []string{"golang"},
Location: "48.1189,11.4289", // lat,lon
Created: time.Date(2012, 10, 10, 8, 12, 03, 0, time.UTC),
}
tweet3 := tweet{
User: "sandrae",
Retweets: 12,
Message: "Cycling is fun.",
Tags: []string{"sports", "cycling"},
Location: "47.7167,11.7167", // lat,lon
Created: time.Date(2011, 11, 11, 10, 58, 12, 0, time.UTC),
}
// Add all documents
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("3").BodyJson(&tweet3).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
count, err := client.Count(testIndexName).Do(context.Background())
if err != nil {
t.Fatal(err)
}
if want, have := int64(3), count; want != have {
t.Fatalf("expected %d documents, got %d", want, have)
}
// Match all should return all documents
all := NewMatchAllQuery()
// Terms Aggregate by user name
globalAgg := NewGlobalAggregation()
usersAgg := NewTermsAggregation().Field("user").Size(10).OrderByCountDesc()
retweetsAgg := NewTermsAggregation().Field("retweets").Size(10)
multiTermsAgg := NewMultiTermsAggregation().Terms("user").MultiTerms(MultiTerm{Field: "tags", Missing: "unclassified"}).Size(10)
avgRetweetsAgg := NewAvgAggregation().Field("retweets")
avgRetweetsWithMetaAgg := NewAvgAggregation().Field("retweetsMeta").Meta(map[string]interface{}{"meta": true})
weightedAvgRetweetsAgg := NewWeightedAvgAggregation().
Value(&MultiValuesSourceFieldConfig{FieldName: "retweets"}).
Weight(&MultiValuesSourceFieldConfig{FieldName: "weight", Missing: 1.0})
minRetweetsAgg := NewMinAggregation().Field("retweets")
maxRetweetsAgg := NewMaxAggregation().Field("retweets")
medianAbsDevRetweetsAgg := NewMedianAbsoluteDeviationAggregation().Field("retweets")
sumRetweetsAgg := NewSumAggregation().Field("retweets")
statsRetweetsAgg := NewStatsAggregation().Field("retweets")
extstatsRetweetsAgg := NewExtendedStatsAggregation().Field("retweets")
valueCountRetweetsAgg := NewValueCountAggregation().Field("retweets")
percentilesRetweetsAgg := NewPercentilesAggregation().Field("retweets")
percentileRanksRetweetsAgg := NewPercentileRanksAggregation().Field("retweets").Values(25, 50, 75)
cardinalityAgg := NewCardinalityAggregation().Field("user")
significantTermsAgg := NewSignificantTermsAggregation().Field("message")
rareTermsAgg := NewRareTermsAggregation().Field("message")
samplerAgg := NewSamplerAggregation().SubAggregation("tagged_with", NewTermsAggregation().Field("tags"))
diversifiedSamplerAgg := NewDiversifiedSamplerAggregation().Field("user").SubAggregation("tagged_with", NewSignificantTermsAggregation().Field("tags"))
retweetsRangeAgg := NewRangeAggregation().Field("retweets").Lt(10).Between(10, 100).Gt(100)
retweetsKeyedRangeAgg := NewRangeAggregation().Field("retweets").Keyed(true).Lt(10).Between(10, 100).Gt(100)
dateRangeAgg := NewDateRangeAggregation().Field("created").Lt("2012-01-01").Between("2012-01-01", "2013-01-01").Gt("2013-01-01")
missingTagsAgg := NewMissingAggregation().Field("tags")
retweetsHistoAgg := NewHistogramAggregation().Field("retweets").Interval(100)
autoDateHistoAgg := NewAutoDateHistogramAggregation().Field("created").Buckets(2).Missing("1900-01-01").Format("yyyy-MM-dd")
dateHistoAgg := NewDateHistogramAggregation().Field("created").CalendarInterval("year")
dateHistoKeyedAgg := NewDateHistogramAggregation().Field("created").CalendarInterval("year").Keyed(true)
retweetsFilterAgg := NewFilterAggregation().Filter(
NewRangeQuery("created").Gte("2012-01-01").Lte("2012-12-31")).
SubAggregation("avgRetweetsSub", NewAvgAggregation().Field("retweets"))
queryFilterAgg := NewFilterAggregation().Filter(NewTermQuery("tags", "golang"))
topTagsHitsAgg := NewTopHitsAggregation().Sort("created", false).Size(5).FetchSource(true)
topTagsAgg := NewTermsAggregation().Field("tags").Size(3).SubAggregation("top_tag_hits", topTagsHitsAgg)
geoBoundsAgg := NewGeoBoundsAggregation().Field("location")
geoHashAgg := NewGeoHashGridAggregation().Field("location").Precision(5)
geoCentroidAgg := NewGeoCentroidAggregation().Field("location")
geoTileAgg := NewGeoTileGridAggregation().Field("location")
topMetricsAgg := NewTopMetricsAggregation().
Field("user").
Field("retweets").
Sort("retweets", false).
Size(2)
// Run query
builder := client.Search().Index(testIndexName).Query(all).Pretty(true)
builder = builder.Aggregation("global", globalAgg)
builder = builder.Aggregation("users", usersAgg)
builder = builder.Aggregation("retweets", retweetsAgg)
builder = builder.Aggregation("multiterms", multiTermsAgg)
builder = builder.Aggregation("avgRetweets", avgRetweetsAgg)
builder = builder.Aggregation("avgRetweetsWithMeta", avgRetweetsWithMetaAgg)
builder = builder.Aggregation("weightedAvgRetweets", weightedAvgRetweetsAgg)
builder = builder.Aggregation("minRetweets", minRetweetsAgg)
builder = builder.Aggregation("maxRetweets", maxRetweetsAgg)
builder = builder.Aggregation("medianAbsDevRetweets", medianAbsDevRetweetsAgg)
builder = builder.Aggregation("sumRetweets", sumRetweetsAgg)
builder = builder.Aggregation("statsRetweets", statsRetweetsAgg)
builder = builder.Aggregation("extstatsRetweets", extstatsRetweetsAgg)
builder = builder.Aggregation("valueCountRetweets", valueCountRetweetsAgg)
builder = builder.Aggregation("percentilesRetweets", percentilesRetweetsAgg)
builder = builder.Aggregation("percentileRanksRetweets", percentileRanksRetweetsAgg)
builder = builder.Aggregation("usersCardinality", cardinalityAgg)
builder = builder.Aggregation("significantTerms", significantTermsAgg)
builder = builder.Aggregation("rareTerms", rareTermsAgg)
builder = builder.Aggregation("sample", samplerAgg)
builder = builder.Aggregation("diversified_sampler", diversifiedSamplerAgg)
builder = builder.Aggregation("retweetsRange", retweetsRangeAgg)
builder = builder.Aggregation("retweetsKeyedRange", retweetsKeyedRangeAgg)
builder = builder.Aggregation("dateRange", dateRangeAgg)
builder = builder.Aggregation("missingTags", missingTagsAgg)
builder = builder.Aggregation("retweetsHisto", retweetsHistoAgg)
builder = builder.Aggregation("autoDateHisto", autoDateHistoAgg)
builder = builder.Aggregation("dateHisto", dateHistoAgg)
builder = builder.Aggregation("dateHistoKeyed", dateHistoKeyedAgg)
builder = builder.Aggregation("retweetsFilter", retweetsFilterAgg)
builder = builder.Aggregation("queryFilter", queryFilterAgg)
builder = builder.Aggregation("top-tags", topTagsAgg)
builder = builder.Aggregation("viewport", geoBoundsAgg)
builder = builder.Aggregation("geohashed", geoHashAgg)
builder = builder.Aggregation("centroid", geoCentroidAgg)
builder = builder.Aggregation("geotile-grid", geoTileAgg)
builder = builder.Aggregation("top-metrics", topMetricsAgg)
// Unnamed filters
countByUserAgg := NewFiltersAggregation().
Filters(NewTermQuery("user", "olivere"), NewTermQuery("user", "sandrae")).
OtherBucket(true).OtherBucketKey("other")
builder = builder.Aggregation("countByUser", countByUserAgg)
// Named filters
countByUserAgg2 := NewFiltersAggregation().
FilterWithName("olivere", NewTermQuery("user", "olivere")).
FilterWithName("sandrae", NewTermQuery("user", "sandrae"))
builder = builder.Aggregation("countByUser2", countByUserAgg2)
// AdjacencyMatrix
adjacencyMatrixAgg := NewAdjacencyMatrixAggregation().
Filters("groupA", NewTermQuery("user", "olivere")).
Filters("groupB", NewTermQuery("user", "sandrae"))
builder = builder.Aggregation("interactions", adjacencyMatrixAgg)
// AvgBucket
dateHisto := NewDateHistogramAggregation().Field("created").CalendarInterval("year")
dateHisto = dateHisto.SubAggregation("sumOfRetweets", NewSumAggregation().Field("retweets"))
builder = builder.Aggregation("avgBucketDateHisto", dateHisto)
builder = builder.Aggregation("avgSumOfRetweets", NewAvgBucketAggregation().BucketsPath("avgBucketDateHisto>sumOfRetweets"))
// MinBucket
dateHisto = NewDateHistogramAggregation().Field("created").CalendarInterval("year")
dateHisto = dateHisto.SubAggregation("sumOfRetweets", NewSumAggregation().Field("retweets"))
builder = builder.Aggregation("minBucketDateHisto", dateHisto)
builder = builder.Aggregation("minBucketSumOfRetweets", NewMinBucketAggregation().BucketsPath("minBucketDateHisto>sumOfRetweets"))
// MaxBucket
dateHisto = NewDateHistogramAggregation().Field("created").CalendarInterval("year")
dateHisto = dateHisto.SubAggregation("sumOfRetweets", NewSumAggregation().Field("retweets"))
builder = builder.Aggregation("maxBucketDateHisto", dateHisto)
builder = builder.Aggregation("maxBucketSumOfRetweets", NewMaxBucketAggregation().BucketsPath("maxBucketDateHisto>sumOfRetweets"))
// SumBucket
dateHisto = NewDateHistogramAggregation().Field("created").CalendarInterval("year")
dateHisto = dateHisto.SubAggregation("sumOfRetweets", NewSumAggregation().Field("retweets"))
builder = builder.Aggregation("sumBucketDateHisto", dateHisto)
builder = builder.Aggregation("sumBucketSumOfRetweets", NewSumBucketAggregation().BucketsPath("sumBucketDateHisto>sumOfRetweets"))
// MovAvg
dateHisto = NewDateHistogramAggregation().Field("created").CalendarInterval("year")
dateHisto = dateHisto.SubAggregation("sumOfRetweets", NewSumAggregation().Field("retweets"))
dateHisto = dateHisto.SubAggregation("movingAvg", NewMovAvgAggregation().BucketsPath("sumOfRetweets"))
dateHisto = dateHisto.SubAggregation("movingFn", NewMovFnAggregation("sumOfRetweets", NewScript("MovingFunctions.sum(values)"), 10))
builder = builder.Aggregation("movingAvgDateHisto", dateHisto)
searchResult, err := builder.Pretty(true).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.Hits == nil {
t.Errorf("expected Hits != nil; got: nil")
}
if searchResult.TotalHits() != 3 {
t.Errorf("expected TotalHits() = %d; got: %d", 3, searchResult.TotalHits())
}
if len(searchResult.Hits.Hits) != 3 {
t.Errorf("expected len(Hits.Hits) = %d; got: %d", 3, len(searchResult.Hits.Hits))
}
agg := searchResult.Aggregations
if agg == nil {
t.Fatalf("expected Aggregations != nil; got: nil")
}
// Search for non-existent aggregate should return (nil, false)
{
agg, found := agg.Terms("no-such-aggregate")
if found {
t.Errorf("expected unknown aggregation to not be found; got: %v", found)
}
if agg != nil {
t.Errorf("expected unknown aggregation to return %v; got %v", nil, agg)
}
}
// Global
{
agg, found := agg.Global("global")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if agg == nil {
t.Fatalf("expected != nil; got: nil")
}
if agg.DocCount != 3 {
t.Errorf("expected DocCount = %d; got: %d", 3, agg.DocCount)
}
}
// Search for existent aggregate (by name) should return (aggregate, true)
{
agg, found := agg.Terms("users")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if agg == nil {
t.Fatalf("expected != nil; got: nil")
}
if len(agg.Buckets) != 2 {
t.Fatalf("expected %d; got: %d", 2, len(agg.Buckets))
}
if agg.Buckets[0].Key != "olivere" {
t.Errorf("expected %q; got: %q", "olivere", agg.Buckets[0].Key)
}
if agg.Buckets[0].DocCount != 2 {
t.Errorf("expected %d; got: %d", 2, agg.Buckets[0].DocCount)
}
if agg.Buckets[1].Key != "sandrae" {
t.Errorf("expected %q; got: %q", "sandrae", agg.Buckets[1].Key)
}
if agg.Buckets[1].DocCount != 1 {
t.Errorf("expected %d; got: %d", 1, agg.Buckets[1].DocCount)
}
}
// A terms aggregate with keys that are not strings
{
agg, found := agg.Terms("retweets")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if agg == nil {
t.Fatalf("expected != nil; got: nil")
}
if len(agg.Buckets) != 3 {
t.Fatalf("expected %d; got: %d", 3, len(agg.Buckets))
}
if agg.Buckets[0].Key != float64(0) {
t.Errorf("expected %v; got: %v", float64(0), agg.Buckets[0].Key)
}
if got, err := agg.Buckets[0].KeyNumber.Int64(); err != nil {
t.Errorf("expected %d; got: %v", 0, agg.Buckets[0].Key)
} else if got != 0 {
t.Errorf("expected %d; got: %d", 0, got)
}
if agg.Buckets[0].KeyNumber != "0" {
t.Errorf("expected %q; got: %q", "0", agg.Buckets[0].KeyNumber)
}
if agg.Buckets[0].KeyNumber.String() != "0" {
t.Errorf("expected %q; got: %q", "0", agg.Buckets[0].KeyNumber.String())
}
if agg.Buckets[0].DocCount != 1 {
t.Errorf("expected %d; got: %d", 1, agg.Buckets[0].DocCount)
}
if agg.Buckets[1].Key != float64(12) {
t.Errorf("expected %v; got: %v", float64(12), agg.Buckets[1].Key)
}
if got, err := agg.Buckets[1].KeyNumber.Int64(); err != nil {
t.Errorf("expected %d; got: %v", 0, agg.Buckets[1].KeyNumber)
} else if got != 12 {
t.Errorf("expected %d; got: %d", 12, got)
}
if agg.Buckets[1].KeyNumber != "12" {
t.Errorf("expected %q; got: %q", "12", agg.Buckets[1].KeyNumber)
}
if agg.Buckets[1].KeyNumber.String() != "12" {
t.Errorf("expected %q; got: %q", "12", agg.Buckets[1].KeyNumber.String())
}
if agg.Buckets[1].DocCount != 1 {
t.Errorf("expected %d; got: %d", 1, agg.Buckets[1].DocCount)
}
if agg.Buckets[2].Key != float64(108) {
t.Errorf("expected %v; got: %v", float64(108), agg.Buckets[2].Key)
}
if got, err := agg.Buckets[2].KeyNumber.Int64(); err != nil {
t.Errorf("expected %d; got: %v", 108, agg.Buckets[2].KeyNumber)
} else if got != 108 {
t.Errorf("expected %d; got: %d", 108, got)
}
if agg.Buckets[2].KeyNumber != "108" {
t.Errorf("expected %q; got: %q", "108", agg.Buckets[2].KeyNumber)
}
if agg.Buckets[2].KeyNumber.String() != "108" {
t.Errorf("expected %q; got: %q", "108", agg.Buckets[2].KeyNumber.String())
}
if agg.Buckets[2].DocCount != 1 {
t.Errorf("expected %d; got: %d", 1, agg.Buckets[2].DocCount)
}
}
// A multi terms aggregate
{
agg, found := agg.MultiTerms("multiterms")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if agg == nil {
t.Fatalf("expected != nil; got: nil")
}
if len(agg.Buckets) != 4 {
t.Fatalf("expected %d; got: %d", 4, len(agg.Buckets))
}
if want, have := 2, len(agg.Buckets[0].Key); want != have {
t.Errorf("expected %v; got: %v", want, have)
}
if want, have := "olivere", agg.Buckets[0].Key[0]; want != have {
t.Errorf("expected %v; got: %v", want, have)
}
if want, have := "golang", agg.Buckets[0].Key[1]; want != have {
t.Errorf("expected %v; got: %v", want, have)
}
if agg.Buckets[0].KeyAsString == nil {
t.Fatal("expected string not nil")
}
if want, have := "olivere|golang", *agg.Buckets[0].KeyAsString; want != have {
t.Errorf("expected %v; got: %v", want, have)
}
if agg.Buckets[0].DocCount != 2 {
t.Errorf("expected %d; got: %d", 2, agg.Buckets[0].DocCount)
}
}
// avgRetweets
{
agg, found := agg.Avg("avgRetweets")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if agg == nil {
t.Fatalf("expected != nil; got: nil")
}
if agg.Value == nil {
t.Fatalf("expected != nil; got: %v", *agg.Value)
}
if *agg.Value != 40.0 {
t.Errorf("expected %v; got: %v", 40.0, *agg.Value)
}
}
// avgRetweetsWithMeta
{
agg, found := agg.Avg("avgRetweetsWithMeta")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if agg == nil {
t.Fatalf("expected != nil; got: nil")
}
if agg.Meta == nil {
t.Fatalf("expected != nil; got: %v", agg.Meta)
}
metaDataValue, found := agg.Meta["meta"]
if !found {
t.Fatalf("expected to return meta data key %q; got: %v", "meta", found)
}
if flag, ok := metaDataValue.(bool); !ok {
t.Fatalf("expected to return meta data key type %T; got: %T", true, metaDataValue)
} else if flag != true {
t.Fatalf("expected to return meta data key value %v; got: %v", true, flag)
}
}
// weightedAvgRetweets
{
agg, found := agg.Avg("weightedAvgRetweets")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if agg == nil {
t.Fatalf("expected != nil; got: nil")
}
if agg.Value == nil {
t.Fatalf("expected != nil; got: %v", *agg.Value)
}
if *agg.Value != 40.0 {
t.Errorf("expected %v; got: %v", 40.0, *agg.Value)
}
}
// minRetweets
{
agg, found := agg.Min("minRetweets")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if agg == nil {
t.Fatalf("expected != nil; got: nil")
}
if agg.Value == nil {
t.Fatalf("expected != nil; got: %v", *agg.Value)
}
if *agg.Value != 0.0 {
t.Errorf("expected %v; got: %v", 0.0, *agg.Value)
}
}
// maxRetweets
{
agg, found := agg.Max("maxRetweets")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if agg == nil {
t.Fatalf("expected != nil; got: nil")
}
if agg.Value == nil {
t.Fatalf("expected != nil; got: %v", *agg.Value)
}
if *agg.Value != 108.0 {
t.Errorf("expected %v; got: %v", 108.0, *agg.Value)
}
}
// medianAbsDevRetweets
{
agg, found := agg.MedianAbsoluteDeviation("medianAbsDevRetweets")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if agg == nil {
t.Fatalf("expected != nil; got: nil")
}
if agg.Value == nil {
t.Fatalf("expected != nil; got: %v", *agg.Value)
}
if *agg.Value != 12.0 {
t.Errorf("expected %v; got: %v", 12.0, *agg.Value)
}
}
// sumRetweets
{
agg, found := agg.Sum("sumRetweets")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if agg == nil {
t.Fatalf("expected != nil; got: nil")
}
if agg.Value == nil {
t.Fatalf("expected != nil; got: %v", *agg.Value)
}
if *agg.Value != 120.0 {
t.Errorf("expected %v; got: %v", 120.0, *agg.Value)
}
}
// statsRetweets
{
agg, found := agg.Stats("statsRetweets")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if agg == nil {
t.Fatalf("expected != nil; got: nil")
}
if agg.Count != 3 {
t.Errorf("expected %d; got: %d", 3, agg.Count)
}
if agg.Min == nil {
t.Fatalf("expected != nil; got: %v", *agg.Min)
}
if *agg.Min != 0.0 {
t.Errorf("expected %v; got: %v", 0.0, *agg.Min)
}
if agg.Max == nil {
t.Fatalf("expected != nil; got: %v", *agg.Max)
}
if *agg.Max != 108.0 {
t.Errorf("expected %v; got: %v", 108.0, *agg.Max)
}
if agg.Avg == nil {
t.Fatalf("expected != nil; got: %v", *agg.Avg)
}
if *agg.Avg != 40.0 {
t.Errorf("expected %v; got: %v", 40.0, *agg.Avg)
}
if agg.Sum == nil {
t.Fatalf("expected != nil; got: %v", *agg.Sum)
}
if *agg.Sum != 120.0 {
t.Errorf("expected %v; got: %v", 120.0, *agg.Sum)
}
}
// extstatsRetweets
{
agg, found := agg.ExtendedStats("extstatsRetweets")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if agg == nil {
t.Fatalf("expected != nil; got: nil")
}
if agg.Count != 3 {
t.Errorf("expected %d; got: %d", 3, agg.Count)
}
if agg.Min == nil {
t.Fatalf("expected != nil; got: %v", *agg.Min)
}
if *agg.Min != 0.0 {
t.Errorf("expected %v; got: %v", 0.0, *agg.Min)
}
if agg.Max == nil {
t.Fatalf("expected != nil; got: %v", *agg.Max)
}
if *agg.Max != 108.0 {
t.Errorf("expected %v; got: %v", 108.0, *agg.Max)
}
if agg.Avg == nil {
t.Fatalf("expected != nil; got: %v", *agg.Avg)
}
if *agg.Avg != 40.0 {
t.Errorf("expected %v; got: %v", 40.0, *agg.Avg)
}
if agg.Sum == nil {
t.Fatalf("expected != nil; got: %v", *agg.Sum)
}
if *agg.Sum != 120.0 {
t.Errorf("expected %v; got: %v", 120.0, *agg.Sum)
}
if agg.SumOfSquares == nil {
t.Fatalf("expected != nil; got: %v", *agg.SumOfSquares)
}
if *agg.SumOfSquares != 11808.0 {
t.Errorf("expected %v; got: %v", 11808.0, *agg.SumOfSquares)
}
if agg.Variance == nil {
t.Fatalf("expected != nil; got: %v", *agg.Variance)
}
if *agg.Variance != 2336.0 {
t.Errorf("expected %v; got: %v", 2336.0, *agg.Variance)
}
if agg.StdDeviation == nil {
t.Fatalf("expected != nil; got: %v", *agg.StdDeviation)
}
if *agg.StdDeviation != 48.33218389437829 {
t.Errorf("expected %v; got: %v", 48.33218389437829, *agg.StdDeviation)
}
}
// valueCountRetweets
{
agg, found := agg.ValueCount("valueCountRetweets")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if agg == nil {
t.Fatalf("expected != nil; got: nil")
}
if agg.Value == nil {
t.Fatalf("expected != nil; got: %v", *agg.Value)
}
if *agg.Value != 3.0 {
t.Errorf("expected %v; got: %v", 3.0, *agg.Value)
}
}
// percentilesRetweets
{
agg, found := agg.Percentiles("percentilesRetweets")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if agg == nil {
t.Fatalf("expected != nil; got: nil")
}
// ES 1.4.x returns 7: {"1.0":...}
// ES 1.5.0 returns 14: {"1.0":..., "1.0_as_string":...}
// So we're relaxing the test here.
if len(agg.Values) == 0 {
t.Errorf("expected at least %d value; got: %d\nValues are: %#v", 1, len(agg.Values), agg.Values)
}
if _, found := agg.Values["0.0"]; found {
t.Errorf("expected %v; got: %v", false, found)
}
if agg.Values["1.0"] != 0.0 {
t.Errorf("expected %v; got: %v", 0.0, agg.Values["1.0"])
}
if agg.Values["5.0"] != 0.0 {
t.Errorf("expected %v; got: %v", 0.0, agg.Values["1.0"])
}
if agg.Values["25.0"] != 3.0 {
t.Errorf("expected %v; got: %v", 3.0, agg.Values["25.0"])
}
if agg.Values["50.0"] != 12.0 {
t.Errorf("expected %v; got: %v", 12.0, agg.Values["50.0"])
}
if agg.Values["75.0"] != 84.0 {
t.Errorf("expected %v; got: %v", 84.0, agg.Values["75.0"])
}
if agg.Values["95.0"] != 108.0 {
t.Errorf("expected %v; got: %v", 108.0, agg.Values["95.0"])
}
if agg.Values["99.0"] != 108.0 {
t.Errorf("expected %v; got: %v", 108.0, agg.Values["99.0"])
}
}
// percentileRanksRetweets
{
agg, found := agg.PercentileRanks("percentileRanksRetweets")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if agg == nil {
t.Fatalf("expected != nil; got: nil")
}
if len(agg.Values) == 0 {
t.Errorf("expected at least %d value; got %d\nValues are: %#v", 1, len(agg.Values), agg.Values)
}
if _, found := agg.Values["0.0"]; found {
t.Errorf("expected %v; got: %v", true, found)
}
if agg.Values["25.0"] != 45.06172839506173 {
t.Errorf("expected %v; got: %v", 45.06172839506173, agg.Values["25.0"])
}
if agg.Values["50.0"] != 60.49382716049383 {
t.Errorf("expected %v; got: %v", 60.49382716049383, agg.Values["50.0"])
}
if agg.Values["75.0"] != 100.0 {
t.Errorf("expected %v; got: %v", 100.0, agg.Values["75.0"])
}
}
// usersCardinality
{
agg, found := agg.Cardinality("usersCardinality")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if agg == nil {
t.Fatalf("expected != nil; got: nil")
}
if agg.Value == nil {
t.Fatalf("expected != nil; got: %v", *agg.Value)
}
if *agg.Value != 2 {
t.Errorf("expected %v; got: %v", 2, *agg.Value)
}
}
// retweetsFilter
{
agg, found := agg.Filter("retweetsFilter")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if agg == nil {
t.Fatalf("expected != nil; got: nil")
}
if agg.DocCount != 2 {
t.Fatalf("expected %v; got: %v", 2, agg.DocCount)
}
// Retrieve sub-aggregation
sub, found := agg.Avg("avgRetweetsSub")
if !found {
t.Error("expected sub-aggregation \"avgRetweets\" to be found; got false")
}
if sub == nil {
t.Fatal("expected sub-aggregation \"avgRetweets\"; got nil")
}
if sub.Value == nil {
t.Fatalf("expected != nil; got: %v", sub.Value)
}
if *sub.Value != 54.0 {
t.Errorf("expected %v; got: %v", 54.0, *sub.Value)
}
}
// queryFilter
{
agg, found := agg.Filter("queryFilter")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if agg == nil {
t.Fatalf("expected != nil; got: nil")
}
if agg.DocCount != 2 {
t.Fatalf("expected %v; got: %v", 2, agg.DocCount)
}
}
// significantTerms
{
agg, found := agg.SignificantTerms("significantTerms")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if agg == nil {
t.Fatalf("expected != nil; got: nil")
}
if agg.DocCount != 3 {
t.Errorf("expected %v; got: %v", 3, agg.DocCount)
}
if len(agg.Buckets) != 0 {
t.Errorf("expected %v; got: %v", 0, len(agg.Buckets))
}
}
// rareTerms
{
agg, found := agg.SignificantTerms("rareTerms")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if agg == nil {
t.Fatalf("expected != nil; got: nil")
}
if agg.DocCount != 0 {
t.Errorf("expected %v; got: %v", 0, agg.DocCount)
}
if len(agg.Buckets) != 11 {
t.Errorf("expected %v; got: %v", 11, len(agg.Buckets))
}
}
// sampler
{
agg, found := agg.Sampler("sample")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if agg == nil {
t.Fatalf("expected != nil; got: nil")
}
if agg.DocCount != 3 {
t.Errorf("expected %v; got: %v", 3, agg.DocCount)
}
sub, found := agg.Aggregations["tagged_with"]
if !found {
t.Fatalf("expected sub aggregation %q", "tagged_with")
}
if sub == nil {
t.Fatalf("expected sub aggregation %q; got: %v", "tagged_with", sub)
}
}
// diversified_sampler
{
agg, found := agg.DiversifiedSampler("diversified_sampler")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if agg == nil {
t.Fatalf("expected != nil; got: nil")
}
if agg.DocCount != 2 {
t.Errorf("expected %v; got: %v", 2, agg.DocCount)
}
}
// retweetsRange
{
agg, found := agg.Range("retweetsRange")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if agg == nil {
t.Fatal("expected != nil; got: nil")
}
if len(agg.Buckets) != 3 {
t.Fatalf("expected %d; got: %d", 3, len(agg.Buckets))
}
if agg.Buckets[0].DocCount != 1 {
t.Errorf("expected %d; got: %d", 1, agg.Buckets[0].DocCount)
}
if agg.Buckets[1].DocCount != 1 {
t.Errorf("expected %d; got: %d", 1, agg.Buckets[1].DocCount)
}
if agg.Buckets[2].DocCount != 1 {
t.Errorf("expected %d; got: %d", 1, agg.Buckets[2].DocCount)
}
}
// retweetsKeyedRange
{
agg, found := agg.KeyedRange("retweetsKeyedRange")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if agg == nil {
t.Fatal("expected != nil; got: nil")
}
if len(agg.Buckets) != 3 {
t.Fatalf("expected %d; got: %d", 3, len(agg.Buckets))
}
_, found = agg.Buckets["no-such-key"]
if found {
t.Fatalf("expected bucket to not be found; got: %v", found)
}
bucket, found := agg.Buckets["*-10.0"]
if !found {
t.Fatalf("expected bucket to be found; got: %v", found)
}
if bucket.DocCount != 1 {
t.Errorf("expected %d; got: %d", 1, bucket.DocCount)
}
bucket, found = agg.Buckets["10.0-100.0"]
if !found {
t.Fatalf("expected bucket to be found; got: %v", found)
}
if bucket.DocCount != 1 {
t.Errorf("expected %d; got: %d", 1, bucket.DocCount)
}
bucket, found = agg.Buckets["100.0-*"]
if !found {
t.Fatalf("expected bucket to be found; got: %v", found)
}
if bucket.DocCount != 1 {
t.Errorf("expected %d; got: %d", 1, bucket.DocCount)
}
}
// dateRange
{
agg, found := agg.DateRange("dateRange")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if agg == nil {
t.Fatal("expected != nil; got: nil")
}
if agg.Buckets[0].DocCount != 1 {
t.Errorf("expected %d; got: %d", 1, agg.Buckets[0].DocCount)
}
if agg.Buckets[0].From != nil {
t.Fatal("expected From to be nil")
}
if agg.Buckets[0].To == nil {
t.Fatal("expected To to be != nil")
}
if *agg.Buckets[0].To != 1.325376e+12 {
t.Errorf("expected %v; got: %v", 1.325376e+12, *agg.Buckets[0].To)
}
if agg.Buckets[0].ToAsString != "2012-01-01T00:00:00.000Z" {
t.Errorf("expected %q; got: %q", "2012-01-01T00:00:00.000Z", agg.Buckets[0].ToAsString)
}
if agg.Buckets[1].DocCount != 2 {
t.Errorf("expected %d; got: %d", 2, agg.Buckets[1].DocCount)
}
if agg.Buckets[1].From == nil {
t.Fatal("expected From to be != nil")
}
if *agg.Buckets[1].From != 1.325376e+12 {
t.Errorf("expected From = %v; got: %v", 1.325376e+12, *agg.Buckets[1].From)
}
if agg.Buckets[1].FromAsString != "2012-01-01T00:00:00.000Z" {
t.Errorf("expected FromAsString = %q; got: %q", "2012-01-01T00:00:00.000Z", agg.Buckets[1].FromAsString)
}
if agg.Buckets[1].To == nil {
t.Fatal("expected To to be != nil")
}
if *agg.Buckets[1].To != 1.3569984e+12 {
t.Errorf("expected To = %v; got: %v", 1.3569984e+12, *agg.Buckets[1].To)
}
if agg.Buckets[1].ToAsString != "2013-01-01T00:00:00.000Z" {
t.Errorf("expected ToAsString = %q; got: %q", "2013-01-01T00:00:00.000Z", agg.Buckets[1].ToAsString)
}
if agg.Buckets[2].DocCount != 0 {
t.Errorf("expected %d; got: %d", 0, agg.Buckets[2].DocCount)
}
if agg.Buckets[2].To != nil {
t.Fatal("expected To to be nil")
}
if agg.Buckets[2].From == nil {
t.Fatal("expected From to be != nil")
}
if *agg.Buckets[2].From != 1.3569984e+12 {
t.Errorf("expected %v; got: %v", 1.3569984e+12, *agg.Buckets[2].From)
}
if agg.Buckets[2].FromAsString != "2013-01-01T00:00:00.000Z" {
t.Errorf("expected %q; got: %q", "2013-01-01T00:00:00.000Z", agg.Buckets[2].FromAsString)
}
}
// missingTags
{
agg, found := agg.Missing("missingTags")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if agg == nil {
t.Fatalf("expected != nil; got: nil")
}
if agg.DocCount != 0 {
t.Errorf("expected searchResult.Aggregations[\"missingTags\"].DocCount = %v; got %v", 0, agg.DocCount)
}
}
// retweetsHisto
{
agg, found := agg.Histogram("retweetsHisto")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if agg == nil {
t.Fatalf("expected != nil; got: nil")
}
if len(agg.Buckets) != 2 {
t.Fatalf("expected %d; got: %d", 2, len(agg.Buckets))
}
if agg.Buckets[0].DocCount != 2 {
t.Errorf("expected %d; got: %d", 2, agg.Buckets[0].DocCount)
}
if agg.Buckets[0].Key != 0.0 {
t.Errorf("expected %v; got: %v", 0.0, agg.Buckets[0].Key)
}
if agg.Buckets[1].DocCount != 1 {
t.Errorf("expected %d; got: %d", 1, agg.Buckets[1].DocCount)
}
if agg.Buckets[1].Key != 100.0 {
t.Errorf("expected %v; got: %+v", 100.0, agg.Buckets[1].Key)
}
}
// autoDateHisto
{
agg, found := agg.DateHistogram("autoDateHisto")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if agg == nil {
t.Fatal("expected != nil; got: nil")
}
if len(agg.Buckets) != 2 {
t.Fatalf("expected %d; got: %d", 2, len(agg.Buckets))
}
}
// dateHisto
{
agg, found := agg.DateHistogram("dateHisto")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if agg == nil {
t.Fatal("expected != nil; got: nil")
}
if len(agg.Buckets) != 2 {
t.Fatalf("expected %d; got: %d", 2, len(agg.Buckets))
}
if agg.Buckets[0].DocCount != 1 {
t.Errorf("expected %d; got: %d", 1, agg.Buckets[0].DocCount)
}
if agg.Buckets[0].Key != 1.29384e+12 {
t.Errorf("expected %v; got: %v", 1.29384e+12, agg.Buckets[0].Key)
}
if agg.Buckets[0].KeyAsString == nil {
t.Fatalf("expected != nil; got: %v", agg.Buckets[0].KeyAsString)
}
if *agg.Buckets[0].KeyAsString != "2011-01-01T00:00:00.000Z" {
t.Errorf("expected %q; got: %q", "2011-01-01T00:00:00.000Z", *agg.Buckets[0].KeyAsString)
}
if agg.Buckets[1].DocCount != 2 {
t.Errorf("expected %d; got: %d", 2, agg.Buckets[1].DocCount)
}
if agg.Buckets[1].Key != 1.325376e+12 {
t.Errorf("expected %v; got: %v", 1.325376e+12, agg.Buckets[1].Key)
}
if agg.Buckets[1].KeyAsString == nil {
t.Fatalf("expected != nil; got: %v", agg.Buckets[1].KeyAsString)
}
if *agg.Buckets[1].KeyAsString != "2012-01-01T00:00:00.000Z" {
t.Errorf("expected %q; got: %q", "2012-01-01T00:00:00.000Z", *agg.Buckets[1].KeyAsString)
}
}
// dateHistoKeyed
{
res, found := agg.KeyedDateHistogram("dateHistoKeyed")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if res == nil {
t.Fatalf("expected != nil; got: nil")
}
if len(res.Buckets) != 2 {
t.Fatalf("expected %d; got: %d", 2, len(res.Buckets))
}
bucket, ok := res.Buckets["2011-01-01T00:00:00.000Z"]
if !ok || bucket == nil {
t.Fatalf("expected to have bucket with key %q", "2011-01-01T00:00:00.000Z")
}
if bucket.DocCount != 1 {
t.Errorf("expected %d; got: %d", 1, bucket.DocCount)
}
if bucket.Key != 1.29384e+12 {
t.Errorf("expected %v; got: %v", 1.29384e+12, bucket.Key)
}
if bucket.KeyAsString == nil {
t.Fatalf("expected != nil; got: %v", bucket.KeyAsString)
}
if *bucket.KeyAsString != "2011-01-01T00:00:00.000Z" {
t.Errorf("expected %q; got: %q", "2011-01-01T00:00:00.000Z", *bucket.KeyAsString)
}
bucket, ok = res.Buckets["2012-01-01T00:00:00.000Z"]
if !ok || bucket == nil {
t.Fatalf("expected to have bucket with key %q", "2012-01-01T00:00:00.000Z")
}
if bucket.DocCount != 2 {
t.Errorf("expected %d; got: %d", 2, bucket.DocCount)
}
if bucket.Key != 1.325376e+12 {
t.Errorf("expected %v; got: %v", 1.325376e+12, bucket.Key)
}
if bucket.KeyAsString == nil {
t.Fatalf("expected != nil; got: %v", bucket.KeyAsString)
}
if *bucket.KeyAsString != "2012-01-01T00:00:00.000Z" {
t.Errorf("expected %q; got: %q", "2012-01-01T00:00:00.000Z", *bucket.KeyAsString)
}
}
// topHits
{
topTags, found := agg.Terms("top-tags")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if topTags == nil {
t.Fatalf("expected != nil; got: nil")
}
if topTags.DocCountErrorUpperBound != 0 {
t.Errorf("expected %v; got: %v", 0, topTags.DocCountErrorUpperBound)
}
if topTags.SumOfOtherDocCount != 1 {
t.Errorf("expected %v; got: %v", 1, topTags.SumOfOtherDocCount)
}
if len(topTags.Buckets) != 3 {
t.Fatalf("expected %d; got: %d", 3, len(topTags.Buckets))
}
if topTags.Buckets[0].DocCount != 2 {
t.Errorf("expected %d; got: %d", 2, topTags.Buckets[0].DocCount)
}
if topTags.Buckets[0].Key != "golang" {
t.Errorf("expected %v; got: %v", "golang", topTags.Buckets[0].Key)
}
topHits, found := topTags.Buckets[0].TopHits("top_tag_hits")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if topHits == nil {
t.Fatal("expected != nil; got: nil")
}
if topHits.Hits == nil {
t.Fatalf("expected != nil; got: nil")
}
if topHits.Hits.TotalHits == nil {
t.Fatalf("expected != nil; got: nil")
}
if topHits.Hits.TotalHits.Value != 2 {
t.Errorf("expected %d; got: %d", 2, topHits.Hits.TotalHits.Value)
}
if topHits.Hits.Hits == nil {
t.Fatalf("expected != nil; got: nil")
}
if len(topHits.Hits.Hits) != 2 {
t.Fatalf("expected %d; got: %d", 2, len(topHits.Hits.Hits))
}
hit := topHits.Hits.Hits[0]
if !found {
t.Fatalf("expected %v; got: %v", true, found)
}
if hit == nil {
t.Fatal("expected != nil; got: nil")
}
var tw tweet
if err := json.Unmarshal(hit.Source, &tw); err != nil {
t.Fatalf("expected no error; got: %v", err)
}
if tw.Message != "Welcome to Golang and Elasticsearch." {
t.Errorf("expected %q; got: %q", "Welcome to Golang and Elasticsearch.", tw.Message)
}
if topTags.Buckets[1].DocCount != 1 {
t.Errorf("expected %d; got: %d", 1, topTags.Buckets[1].DocCount)
}
if topTags.Buckets[1].Key != "cycling" {
t.Errorf("expected %v; got: %v", "cycling", topTags.Buckets[1].Key)
}
topHits, found = topTags.Buckets[1].TopHits("top_tag_hits")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if topHits == nil {
t.Fatal("expected != nil; got: nil")
}
if topHits.Hits == nil {
t.Fatal("expected != nil; got nil")
}
if topHits.Hits.TotalHits == nil {
t.Fatal("expected != nil; got nil")
}
if topHits.Hits.TotalHits.Value != 1 {
t.Errorf("expected %d; got: %d", 1, topHits.Hits.TotalHits.Value)
}
if topTags.Buckets[2].DocCount != 1 {
t.Errorf("expected %d; got: %d", 1, topTags.Buckets[2].DocCount)
}
if topTags.Buckets[2].Key != "elasticsearch" {
t.Errorf("expected %v; got: %v", "elasticsearch", topTags.Buckets[2].Key)
}
topHits, found = topTags.Buckets[2].TopHits("top_tag_hits")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if topHits == nil {
t.Fatal("expected != nil; got: nil")
}
if topHits.Hits == nil {
t.Fatal("expected != nil; got: nil")
}
if topHits.Hits.TotalHits == nil {
t.Fatal("expected != nil; got: nil")
}
if topHits.Hits.TotalHits.Value != 1 {
t.Errorf("expected %d; got: %d", 1, topHits.Hits.TotalHits.Value)
}
}
// viewport via geo_bounds (1.3.0 has an error in that it doesn't output the aggregation name)
{
agg, found := agg.GeoBounds("viewport")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if agg == nil {
t.Fatalf("expected != nil; got: nil")
}
}
// geohashed via geohash
{
agg, found := agg.GeoHash("geohashed")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if agg == nil {
t.Fatalf("expected != nil; got: nil")
}
}
// geo_centroid
{
agg, found := agg.GeoCentroid("centroid")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if agg == nil {
t.Fatalf("expected != nil; got: nil")
}
}
// geotile grid
{
agg, found := agg.GeoTile("geotile-grid")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if agg == nil {
t.Fatalf("expected != nil; got: nil")
}
}
// Filters agg "countByUser" (unnamed)
{
agg, found := agg.Filters("countByUser")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if agg == nil {
t.Fatalf("expected != nil; got: nil")
}
if len(agg.Buckets) != 3 {
t.Fatalf("expected %d; got: %d", 3, len(agg.Buckets))
}
if len(agg.NamedBuckets) != 0 {
t.Fatalf("expected %d; got: %d", 0, len(agg.NamedBuckets))
}
if agg.Buckets[0].DocCount != 2 {
t.Errorf("expected %d; got: %d", 2, agg.Buckets[0].DocCount)
}
if agg.Buckets[1].DocCount != 1 {
t.Errorf("expected %d; got: %d", 1, agg.Buckets[1].DocCount)
}
if agg.Buckets[2].DocCount != 0 {
t.Errorf("expected %d; got: %d", 0, agg.Buckets[2].DocCount)
}
}
// Filters agg "countByUser2" (named)
{
agg, found := agg.Filters("countByUser2")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if agg == nil {
t.Fatalf("expected != nil; got: nil")
}
if len(agg.Buckets) != 0 {
t.Fatalf("expected %d; got: %d", 0, len(agg.Buckets))
}
if len(agg.NamedBuckets) != 2 {
t.Fatalf("expected %d; got: %d", 2, len(agg.NamedBuckets))
}
b, found := agg.NamedBuckets["olivere"]
if !found {
t.Fatalf("expected bucket %q; got: %v", "olivere", found)
}
if b == nil {
t.Fatalf("expected bucket %q; got: %v", "olivere", b)
}
if b.DocCount != 2 {
t.Errorf("expected %d; got: %d", 2, b.DocCount)
}
b, found = agg.NamedBuckets["sandrae"]
if !found {
t.Fatalf("expected bucket %q; got: %v", "sandrae", found)
}
if b == nil {
t.Fatalf("expected bucket %q; got: %v", "sandrae", b)
}
if b.DocCount != 1 {
t.Errorf("expected %d; got: %d", 1, b.DocCount)
}
}
// AdjacencyMatrix agg "adjacencyMatrixAgg" (named)
{
agg, found := agg.AdjacencyMatrix("interactions")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if agg == nil {
t.Fatalf("expected != nil; got: nil")
}
if len(agg.Buckets) != 2 {
t.Fatalf("expected %d; got: %d", 2, len(agg.Buckets))
}
if agg.Buckets[0].DocCount != 2 {
t.Errorf("expected %d; got: %d", 2, agg.Buckets[0].DocCount)
}
if agg.Buckets[1].DocCount != 1 {
t.Errorf("expected %d; got: %d", 1, agg.Buckets[1].DocCount)
}
}
// movingAvgDateHisto
{
agg, found := agg.DateHistogram("movingAvgDateHisto")
if !found {
t.Fatalf("expected %v; got: %v", true, false)
}
if agg == nil {
t.Fatal("expected != nil; got: nil")
}
if want, have := 2, len(agg.Buckets); want != have {
t.Fatalf("expected %d buckets, have %d", want, have)
}
// movingAvgDateHisto.Buckets[0]
if want, have := int64(1), agg.Buckets[0].DocCount; want != have {
t.Fatalf("expected %d docs in bucket 0, have %d", want, have)
}
if want, have := 1293840000000.0, agg.Buckets[0].Key; want != have {
t.Fatalf("expected key of %v in bucket 0, have %v", want, have)
}
if have := agg.Buckets[0].KeyAsString; have == nil {
t.Fatalf("expected key_as_string != nil in bucket 0, have %v", have)
}
if want, have := "2011-01-01T00:00:00.000Z", *agg.Buckets[0].KeyAsString; want != have {
t.Fatalf("expected key_as_string of %q in bucket 0, have %q", want, have)
}
sumOfRetweetsAgg, found := agg.Buckets[0].SumBucket("sumOfRetweets")
if !found {
t.Fatalf("expected sub-aggregation %q", "sumOfRetweets")
}
if have := sumOfRetweetsAgg.Value; have == nil {
t.Fatalf("expected sumOfRetweets != nil, have %v", have)
}
if want, have := 12.0, *sumOfRetweetsAgg.Value; want != have {
t.Fatalf("expected sumOfRetweets = %v, have %v", want, have)
}
movingAvgAgg, found := agg.Buckets[0].MovAvg("movingAvg")
if found {
t.Fatalf("expected no sub-aggregation %q", "movingAvg")
}
if movingAvgAgg != nil {
t.Fatalf("expected no sub-aggregation %q", "movingAvg")
}
movingFnAgg, found := agg.Buckets[0].MovFn("movingFn")
if !found {
t.Fatalf("expected sub-aggregation %q", "movingFn")
}
if have := movingFnAgg.Value; have == nil {
t.Fatalf("expected movingFn != nil, have %v", have)
}
if want, have := 0.0, *movingFnAgg.Value; want != have {
t.Fatalf("expected movingFn = %v, have %v", want, have)
}
// movingAvgDateHisto.Buckets[1]
if want, have := int64(2), agg.Buckets[1].DocCount; want != have {
t.Fatalf("expected %d docs in bucket 1, have %d", want, have)
}
if want, have := 1325376000000.0, agg.Buckets[1].Key; want != have {
t.Fatalf("expected key of %v in bucket 1, have %v", want, have)
}
if have := agg.Buckets[1].KeyAsString; have == nil {
t.Fatalf("expected key_as_string != nil in bucket 1, have %v", have)
}
if want, have := "2012-01-01T00:00:00.000Z", *agg.Buckets[1].KeyAsString; want != have {
t.Fatalf("expected key_as_string of %q in bucket 1, have %q", want, have)
}
sumOfRetweetsAgg, found = agg.Buckets[1].SumBucket("sumOfRetweets")
if !found {
t.Fatalf("expected sub-aggregation %q", "sumOfRetweets")
}
if have := sumOfRetweetsAgg.Value; have == nil {
t.Fatalf("expected sumOfRetweets != nil, have %v", have)
}
if want, have := 108.0, *sumOfRetweetsAgg.Value; want != have {
t.Fatalf("expected sumOfRetweets = %v, have %v", want, have)
}
movingAvgAgg, found = agg.Buckets[1].MovAvg("movingAvg")
if !found {
t.Fatalf("expected sub-aggregation %q", "movingAvg")
}
if have := movingAvgAgg.Value; have == nil {
t.Fatalf("expected movingAgg != nil, have %v", have)
}
if want, have := 12.0, *movingAvgAgg.Value; want != have {
t.Fatalf("expected movingAvg = %v, have %v", want, have)
}
movingFnAgg, found = agg.Buckets[1].MovFn("movingFn")
if !found {
t.Fatalf("expected sub-aggregation %q", "movingFn")
}
if have := movingFnAgg.Value; have == nil {
t.Fatalf("expected movingFn != nil, have %v", have)
}
if want, have := 12.0, *movingFnAgg.Value; want != have {
t.Fatalf("expected movingFn = %v, have %v", want, have)
}
}
// top metrics aggregation
{
agg, found := agg.TopMetrics("top-metrics")
if !found {
t.Fatalf("expected %v; got: %v", true, false)
}
if agg == nil {
t.Fatal("expected != nil; got: nil")
}
if want, have := 2, len(agg.Top); want != have {
t.Fatalf("expected %d top results, have %d", want, have)
}
if want, have := "olivere", agg.Top[0].Metrics["user"]; want != have {
t.Fatalf("expected %v top user, have %v", want, have)
}
if want, have := float64(108), agg.Top[0].Metrics["retweets"]; want != have {
t.Fatalf("expected %v top user, have %v", want, have)
}
if want, have := float64(108), agg.Top[0].Sort[0]; want != have {
t.Fatalf("expected %v sort value, have %v", want, have)
}
if want, have := "sandrae", agg.Top[1].Metrics["user"]; want != have {
t.Fatalf("expected %v top user, have %v", want, have)
}
if want, have := float64(12), agg.Top[1].Metrics["retweets"]; want != have {
t.Fatalf("expected %v top user, have %v", want, have)
}
if want, have := float64(12), agg.Top[1].Sort[0]; want != have {
t.Fatalf("expected %v sort value, have %v", want, have)
}
}
}
// TestAggsCompositeIntegration is an integration test for the Composite aggregation.
func TestAggsCompositeIntegration(t *testing.T) {
// client := setupTestClientAndCreateIndex(t, SetTraceLog(log.New(os.Stdout, "", log.LstdFlags)))
client := setupTestClientAndCreateIndex(t)
tweet1 := tweet{
User: "olivere",
Retweets: 108,
Message: "Welcome to Golang and Elasticsearch.",
Image: "http://golang.org/doc/gopher/gophercolor.png",
Tags: []string{"golang", "elasticsearch"},
Location: "48.1333,11.5667", // lat,lon
Created: time.Date(2012, 12, 12, 17, 38, 34, 0, time.UTC),
}
tweet2 := tweet{
User: "olivere",
Retweets: 0,
Message: "Another unrelated topic.",
Tags: []string{"golang"},
Location: "48.1189,11.4289", // lat,lon
Created: time.Date(2012, 10, 10, 8, 12, 03, 0, time.UTC),
}
tweet3 := tweet{
User: "sandrae",
Retweets: 12,
Message: "Cycling is fun.",
Tags: []string{"sports", "cycling"},
Location: "47.7167,11.7167", // lat,lon
Created: time.Date(2011, 11, 11, 10, 58, 12, 0, time.UTC),
}
// Add all documents
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("3").BodyJson(&tweet3).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
count, err := client.Count(testIndexName).Do(context.Background())
if err != nil {
t.Fatal(err)
}
if want, have := int64(3), count; want != have {
t.Fatalf("expected %d documents, got %d", want, have)
}
// Match all should return all documents
all := NewMatchAllQuery()
// Terms Aggregate by user name
builder := client.Search().Index(testIndexName).Query(all).Pretty(true)
composite := NewCompositeAggregation().Sources(
NewCompositeAggregationTermsValuesSource("composite_users").Field("user"),
NewCompositeAggregationHistogramValuesSource("composite_retweets", 1).MissingBucket(true).Field("retweets"),
NewCompositeAggregationDateHistogramValuesSource("composite_created").CalendarInterval("1m").Field("created"),
).Size(2)
builder = builder.Aggregation("composite", composite)
// Run the query
searchResult, err := builder.Pretty(true).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.Hits == nil {
t.Errorf("expected Hits != nil; got: nil")
}
if searchResult.Hits.TotalHits == nil {
t.Errorf("expected Hits.TotalHits != nil; got: nil")
}
if searchResult.Hits.TotalHits.Value != 3 {
t.Errorf("expected Hits.TotalHits.Value = %d; got: %d", 3, searchResult.Hits.TotalHits.Value)
}
if len(searchResult.Hits.Hits) != 3 {
t.Errorf("expected len(Hits.Hits) = %d; got: %d", 3, len(searchResult.Hits.Hits))
}
agg := searchResult.Aggregations
if agg == nil {
t.Fatalf("expected Aggregations != nil; got: nil")
}
// Check outcome of 1st call (without "after_key" settings)
var afterKey map[string]interface{}
{
compositeAggRes, found := agg.Composite("composite")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if compositeAggRes == nil {
t.Fatalf("expected != nil; got: nil")
}
if want, have := 2, len(compositeAggRes.Buckets); want != have {
t.Fatalf("expected %d; got: %d", want, have)
}
afterKey = compositeAggRes.AfterKey
if len(afterKey) == 0 {
t.Fatalf("expected after_key; got: %v", afterKey)
}
if v, found := afterKey["composite_users"]; !found {
t.Fatalf("expected after_key.composite_users; got: %v", afterKey)
} else if want, have := "olivere", v; want != have {
t.Fatalf("expected after_key.composite_users = %q; got: %q", want, have)
}
if v, found := afterKey["composite_retweets"]; !found {
t.Fatalf("expected after_key.composite_retweets; got: %v", afterKey)
} else if want, have := 108.0, v; want != have {
t.Fatalf("expected after_key.composite_retweets = %v; got: %v", want, have)
}
if v, found := afterKey["composite_created"]; !found {
t.Fatalf("expected after_key.composite_created; got: %v", afterKey)
} else if want, have := 1355333880000.0, v; want != have {
t.Fatalf("expected after_key.composite_created = %v; got: %v", want, have)
}
}
// Now paginate to the 2nd call via "after_key"
builder = client.Search().Index(testIndexName).Query(all).Pretty(true)
composite = NewCompositeAggregation().Sources(
NewCompositeAggregationTermsValuesSource("composite_users").Field("user"),
NewCompositeAggregationHistogramValuesSource("composite_retweets", 1).Field("retweets"),
NewCompositeAggregationDateHistogramValuesSource("composite_created").CalendarInterval("1m").Field("created"),
).Size(2).AggregateAfter(afterKey)
builder = builder.Aggregation("composite", composite)
searchResult, err = builder.Pretty(true).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.Hits == nil {
t.Errorf("expected Hits != nil; got: nil")
}
agg = searchResult.Aggregations
if agg == nil {
t.Fatalf("expected Aggregations != nil; got: nil")
}
// Check outcome of 2nd call (with "after_key" settings)
{
compositeAggRes, found := agg.Composite("composite")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if compositeAggRes == nil {
t.Fatalf("expected != nil; got: nil")
}
if want, have := 1, len(compositeAggRes.Buckets); want != have {
t.Fatalf("expected %d; got: %d", want, have)
}
afterKey = compositeAggRes.AfterKey
if len(afterKey) == 0 {
t.Fatalf("expected after_key; got: %v", afterKey)
}
if v, found := afterKey["composite_users"]; !found {
t.Fatalf("expected after_key.composite_users; got: %v", afterKey)
} else if want, have := "sandrae", v; want != have {
t.Fatalf("expected after_key.composite_users = %q; got: %q", want, have)
}
if v, found := afterKey["composite_retweets"]; !found {
t.Fatalf("expected after_key.composite_retweets; got: %v", afterKey)
} else if want, have := 12.0, v; want != have {
t.Fatalf("expected after_key.composite_retweets = %v; got: %v", want, have)
}
if v, found := afterKey["composite_created"]; !found {
t.Fatalf("expected after_key.composite_created; got: %v", afterKey)
} else if want, have := 1321009080000.0, v; want != have {
t.Fatalf("expected after_key.composite_created = %v; got: %v", want, have)
}
}
// Now paginate to the 3rd call via "after_key"
builder = client.Search().Index(testIndexName).Query(all).Pretty(true)
composite = NewCompositeAggregation().Sources(
NewCompositeAggregationTermsValuesSource("composite_users").Field("user"),
NewCompositeAggregationHistogramValuesSource("composite_retweets", 1).Field("retweets"),
NewCompositeAggregationDateHistogramValuesSource("composite_created").CalendarInterval("1m").Field("created"),
).Size(2).AggregateAfter(afterKey)
builder = builder.Aggregation("composite", composite)
searchResult, err = builder.Pretty(true).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.Hits == nil {
t.Errorf("expected Hits != nil; got: nil")
}
agg = searchResult.Aggregations
if agg == nil {
t.Fatalf("expected Aggregations != nil; got: nil")
}
// Check outcome of 3rd call (with "after_key" settings)
{
compositeAggRes, found := agg.Composite("composite")
if !found {
t.Errorf("expected %v; got: %v", true, found)
}
if compositeAggRes == nil {
t.Fatalf("expected != nil; got: nil")
}
if want, have := 0, len(compositeAggRes.Buckets); want != have {
t.Fatalf("expected %d; got: %d", want, have)
}
afterKey = compositeAggRes.AfterKey
if afterKey != nil {
t.Fatalf("expected no after_key; got: %v", afterKey)
}
}
}
// TestAggsMarshal ensures that marshaling aggregations back into a string
// does not yield base64 encoded data. See https://github.com/olivere/elastic/issues/51
// and https://groups.google.com/forum/#!topic/Golang-Nuts/38ShOlhxAYY for details.
func TestAggsMarshal(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tweet1 := tweet{
User: "olivere",
Retweets: 108,
Message: "Welcome to Golang and Elasticsearch.",
Image: "http://golang.org/doc/gopher/gophercolor.png",
Tags: []string{"golang", "elasticsearch"},
Location: "48.1333,11.5667", // lat,lon
Created: time.Date(2012, 12, 12, 17, 38, 34, 0, time.UTC),
}
// Add all documents
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Match all should return all documents
all := NewMatchAllQuery()
dhagg := NewDateHistogramAggregation().Field("created").CalendarInterval("year")
// Run query
builder := client.Search().Index(testIndexName).Query(all)
builder = builder.Aggregation("dhagg", dhagg)
searchResult, err := builder.Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.TotalHits() != 1 {
t.Errorf("expected Hits.TotalHits = %d; got: %d", 1, searchResult.TotalHits())
}
if _, found := searchResult.Aggregations["dhagg"]; !found {
t.Fatalf("expected aggregation %q", "dhagg")
}
buf, err := json.Marshal(searchResult)
if err != nil {
t.Fatal(err)
}
s := string(buf)
if i := strings.Index(s, `{"dhagg":{"buckets":[{"key_as_string":"2012-01-01`); i < 0 {
t.Errorf("expected to serialize aggregation into string; got: %v", s)
}
}
func TestAggsMetricsMin(t *testing.T) {
s := `{
"min_price": {
"value": 10
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.Min("min_price")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.Value == nil {
t.Fatalf("expected aggregation value != nil; got: %v", agg.Value)
}
if *agg.Value != float64(10) {
t.Fatalf("expected aggregation value = %v; got: %v", float64(10), *agg.Value)
}
}
func TestAggsMetricsMax(t *testing.T) {
s := `{
"max_price": {
"value": 35
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.Max("max_price")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.Value == nil {
t.Fatalf("expected aggregation value != nil; got: %v", agg.Value)
}
if *agg.Value != float64(35) {
t.Fatalf("expected aggregation value = %v; got: %v", float64(35), *agg.Value)
}
}
func TestAggsMetricsSum(t *testing.T) {
s := `{
"intraday_return": {
"value": 2.18
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.Sum("intraday_return")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.Value == nil {
t.Fatalf("expected aggregation value != nil; got: %v", agg.Value)
}
if *agg.Value != float64(2.18) {
t.Fatalf("expected aggregation value = %v; got: %v", float64(2.18), *agg.Value)
}
}
func TestAggsMetricsAvg(t *testing.T) {
s := `{
"avg_grade": {
"value": 75
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.Avg("avg_grade")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.Value == nil {
t.Fatalf("expected aggregation value != nil; got: %v", agg.Value)
}
if *agg.Value != float64(75) {
t.Fatalf("expected aggregation value = %v; got: %v", float64(75), *agg.Value)
}
}
func TestAggsMetricsValueCount(t *testing.T) {
s := `{
"grades_count": {
"value": 10
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.ValueCount("grades_count")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.Value == nil {
t.Fatalf("expected aggregation value != nil; got: %v", agg.Value)
}
if *agg.Value != float64(10) {
t.Fatalf("expected aggregation value = %v; got: %v", float64(10), *agg.Value)
}
}
func TestAggsMetricsCardinality(t *testing.T) {
s := `{
"author_count": {
"value": 12
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.Cardinality("author_count")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.Value == nil {
t.Fatalf("expected aggregation value != nil; got: %v", agg.Value)
}
if *agg.Value != float64(12) {
t.Fatalf("expected aggregation value = %v; got: %v", float64(12), *agg.Value)
}
}
func TestAggsMetricsStats(t *testing.T) {
s := `{
"grades_stats": {
"count": 6,
"min": 60,
"max": 98,
"avg": 78.5,
"sum": 471
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.Stats("grades_stats")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.Count != int64(6) {
t.Fatalf("expected aggregation Count = %v; got: %v", int64(6), agg.Count)
}
if agg.Min == nil {
t.Fatalf("expected aggregation Min != nil; got: %v", agg.Min)
}
if *agg.Min != float64(60) {
t.Fatalf("expected aggregation Min = %v; got: %v", float64(60), *agg.Min)
}
if agg.Max == nil {
t.Fatalf("expected aggregation Max != nil; got: %v", agg.Max)
}
if *agg.Max != float64(98) {
t.Fatalf("expected aggregation Max = %v; got: %v", float64(98), *agg.Max)
}
if agg.Avg == nil {
t.Fatalf("expected aggregation Avg != nil; got: %v", agg.Avg)
}
if *agg.Avg != float64(78.5) {
t.Fatalf("expected aggregation Avg = %v; got: %v", float64(78.5), *agg.Avg)
}
if agg.Sum == nil {
t.Fatalf("expected aggregation Sum != nil; got: %v", agg.Sum)
}
if *agg.Sum != float64(471) {
t.Fatalf("expected aggregation Sum = %v; got: %v", float64(471), *agg.Sum)
}
}
func TestAggsMetricsExtendedStats(t *testing.T) {
s := `{
"grades_stats": {
"count": 6,
"min": 72,
"max": 117.6,
"avg": 94.2,
"sum": 565.2,
"sum_of_squares": 54551.51999999999,
"variance": 218.2799999999976,
"std_deviation": 14.774302013969987
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.ExtendedStats("grades_stats")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.Count != int64(6) {
t.Fatalf("expected aggregation Count = %v; got: %v", int64(6), agg.Count)
}
if agg.Min == nil {
t.Fatalf("expected aggregation Min != nil; got: %v", agg.Min)
}
if *agg.Min != float64(72) {
t.Fatalf("expected aggregation Min = %v; got: %v", float64(72), *agg.Min)
}
if agg.Max == nil {
t.Fatalf("expected aggregation Max != nil; got: %v", agg.Max)
}
if *agg.Max != float64(117.6) {
t.Fatalf("expected aggregation Max = %v; got: %v", float64(117.6), *agg.Max)
}
if agg.Avg == nil {
t.Fatalf("expected aggregation Avg != nil; got: %v", agg.Avg)
}
if *agg.Avg != float64(94.2) {
t.Fatalf("expected aggregation Avg = %v; got: %v", float64(94.2), *agg.Avg)
}
if agg.Sum == nil {
t.Fatalf("expected aggregation Sum != nil; got: %v", agg.Sum)
}
if *agg.Sum != float64(565.2) {
t.Fatalf("expected aggregation Sum = %v; got: %v", float64(565.2), *agg.Sum)
}
if agg.SumOfSquares == nil {
t.Fatalf("expected aggregation sum_of_squares != nil; got: %v", agg.SumOfSquares)
}
if *agg.SumOfSquares != float64(54551.51999999999) {
t.Fatalf("expected aggregation sum_of_squares = %v; got: %v", float64(54551.51999999999), *agg.SumOfSquares)
}
if agg.Variance == nil {
t.Fatalf("expected aggregation Variance != nil; got: %v", agg.Variance)
}
if *agg.Variance != float64(218.2799999999976) {
t.Fatalf("expected aggregation Variance = %v; got: %v", float64(218.2799999999976), *agg.Variance)
}
if agg.StdDeviation == nil {
t.Fatalf("expected aggregation StdDeviation != nil; got: %v", agg.StdDeviation)
}
if *agg.StdDeviation != float64(14.774302013969987) {
t.Fatalf("expected aggregation StdDeviation = %v; got: %v", float64(14.774302013969987), *agg.StdDeviation)
}
}
func TestAggsMatrixStats(t *testing.T) {
s := `{
"matrixstats": {
"fields": [{
"name": "income",
"count": 50,
"mean": 51985.1,
"variance": 7.383377037755103E7,
"skewness": 0.5595114003506483,
"kurtosis": 2.5692365287787124,
"covariance": {
"income": 7.383377037755103E7,
"poverty": -21093.65836734694
},
"correlation": {
"income": 1.0,
"poverty": -0.8352655256272504
}
}, {
"name": "poverty",
"count": 51,
"mean": 12.732000000000001,
"variance": 8.637730612244896,
"skewness": 0.4516049811903419,
"kurtosis": 2.8615929677997767,
"covariance": {
"income": -21093.65836734694,
"poverty": 8.637730612244896
},
"correlation": {
"income": -0.8352655256272504,
"poverty": 1.0
}
}]
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.MatrixStats("matrixstats")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if want, got := 2, len(agg.Fields); want != got {
t.Fatalf("expected aggregaton len(Fields) = %v; got: %v", want, got)
}
field := agg.Fields[0]
if want, got := "income", field.Name; want != got {
t.Fatalf("expected aggregation field name == %q; got: %q", want, got)
}
if want, got := int64(50), field.Count; want != got {
t.Fatalf("expected aggregation field count == %v; got: %v", want, got)
}
if want, got := 51985.1, field.Mean; want != got {
t.Fatalf("expected aggregation field mean == %v; got: %v", want, got)
}
if want, got := 7.383377037755103e7, field.Variance; want != got {
t.Fatalf("expected aggregation field variance == %v; got: %v", want, got)
}
if want, got := 0.5595114003506483, field.Skewness; want != got {
t.Fatalf("expected aggregation field skewness == %v; got: %v", want, got)
}
if want, got := 2.5692365287787124, field.Kurtosis; want != got {
t.Fatalf("expected aggregation field kurtosis == %v; got: %v", want, got)
}
if field.Covariance == nil {
t.Fatalf("expected aggregation field covariance != nil; got: %v", nil)
}
if want, got := 7.383377037755103e7, field.Covariance["income"]; want != got {
t.Fatalf("expected aggregation field covariance == %v; got: %v", want, got)
}
if want, got := -21093.65836734694, field.Covariance["poverty"]; want != got {
t.Fatalf("expected aggregation field covariance == %v; got: %v", want, got)
}
if field.Correlation == nil {
t.Fatalf("expected aggregation field correlation != nil; got: %v", nil)
}
if want, got := 1.0, field.Correlation["income"]; want != got {
t.Fatalf("expected aggregation field correlation == %v; got: %v", want, got)
}
if want, got := -0.8352655256272504, field.Correlation["poverty"]; want != got {
t.Fatalf("expected aggregation field correlation == %v; got: %v", want, got)
}
field = agg.Fields[1]
if want, got := "poverty", field.Name; want != got {
t.Fatalf("expected aggregation field name == %q; got: %q", want, got)
}
if want, got := int64(51), field.Count; want != got {
t.Fatalf("expected aggregation field count == %v; got: %v", want, got)
}
}
func TestAggsMetricsPercentiles(t *testing.T) {
s := `{
"load_time_outlier": {
"values" : {
"1.0": 15,
"5.0": 20,
"25.0": 23,
"50.0": 25,
"75.0": 29,
"95.0": 60,
"99.0": 150
}
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.Percentiles("load_time_outlier")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.Values == nil {
t.Fatalf("expected aggregation Values != nil; got: %v", agg.Values)
}
if len(agg.Values) != 7 {
t.Fatalf("expected %d aggregation Values; got: %d", 7, len(agg.Values))
}
if agg.Values["1.0"] != float64(15) {
t.Errorf("expected aggregation value for \"1.0\" = %v; got: %v", float64(15), agg.Values["1.0"])
}
if agg.Values["5.0"] != float64(20) {
t.Errorf("expected aggregation value for \"5.0\" = %v; got: %v", float64(20), agg.Values["5.0"])
}
if agg.Values["25.0"] != float64(23) {
t.Errorf("expected aggregation value for \"25.0\" = %v; got: %v", float64(23), agg.Values["25.0"])
}
if agg.Values["50.0"] != float64(25) {
t.Errorf("expected aggregation value for \"50.0\" = %v; got: %v", float64(25), agg.Values["50.0"])
}
if agg.Values["75.0"] != float64(29) {
t.Errorf("expected aggregation value for \"75.0\" = %v; got: %v", float64(29), agg.Values["75.0"])
}
if agg.Values["95.0"] != float64(60) {
t.Errorf("expected aggregation value for \"95.0\" = %v; got: %v", float64(60), agg.Values["95.0"])
}
if agg.Values["99.0"] != float64(150) {
t.Errorf("expected aggregation value for \"99.0\" = %v; got: %v", float64(150), agg.Values["99.0"])
}
}
func TestAggsMetricsPercentileRanks(t *testing.T) {
s := `{
"load_time_outlier": {
"values" : {
"15": 92,
"30": 100
}
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.PercentileRanks("load_time_outlier")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.Values == nil {
t.Fatalf("expected aggregation Values != nil; got: %v", agg.Values)
}
if len(agg.Values) != 2 {
t.Fatalf("expected %d aggregation Values; got: %d", 7, len(agg.Values))
}
if agg.Values["15"] != float64(92) {
t.Errorf("expected aggregation value for \"15\" = %v; got: %v", float64(92), agg.Values["15"])
}
if agg.Values["30"] != float64(100) {
t.Errorf("expected aggregation value for \"30\" = %v; got: %v", float64(100), agg.Values["30"])
}
}
func TestAggsMetricsTopHits(t *testing.T) {
s := `{
"top-tags": {
"buckets": [
{
"key": "windows-7",
"doc_count": 25365,
"top_tags_hits": {
"hits": {
"total": {
"value": 25365,
"relation": "eq"
},
"max_score": 1,
"hits": [
{
"_index": "stack",
"_type": "question",
"_id": "602679",
"_score": 1,
"_source": {
"title": "Windows port opening"
},
"sort": [
1370143231177
]
}
]
}
}
},
{
"key": "linux",
"doc_count": 18342,
"top_tags_hits": {
"hits": {
"total": {
"value": 18342,
"relation": "eq"
},
"max_score": 1,
"hits": [
{
"_index": "stack",
"_type": "question",
"_id": "602672",
"_score": 1,
"_source": {
"title": "Ubuntu RFID Screensaver lock-unlock"
},
"sort": [
1370143379747
]
}
]
}
}
},
{
"key": "windows",
"doc_count": 18119,
"top_tags_hits": {
"hits": {
"total": {
"value": 18119,
"relation": "eq"
},
"max_score": 1,
"hits": [
{
"_index": "stack",
"_type": "question",
"_id": "602678",
"_score": 1,
"_source": {
"title": "If I change my computers date / time, what could be affected?"
},
"sort": [
1370142868283
]
}
]
}
}
}
]
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.Terms("top-tags")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.Buckets == nil {
t.Fatalf("expected aggregation buckets != nil; got: %v", agg.Buckets)
}
if len(agg.Buckets) != 3 {
t.Errorf("expected %d bucket entries; got: %d", 3, len(agg.Buckets))
}
if agg.Buckets[0].Key != "windows-7" {
t.Errorf("expected bucket key = %q; got: %q", "windows-7", agg.Buckets[0].Key)
}
if agg.Buckets[1].Key != "linux" {
t.Errorf("expected bucket key = %q; got: %q", "linux", agg.Buckets[1].Key)
}
if agg.Buckets[2].Key != "windows" {
t.Errorf("expected bucket key = %q; got: %q", "windows", agg.Buckets[2].Key)
}
// Sub-aggregation of top-hits
subAgg, found := agg.Buckets[0].TopHits("top_tags_hits")
if !found {
t.Fatalf("expected sub aggregation to be found; got: %v", found)
}
if subAgg == nil {
t.Fatalf("expected sub aggregation != nil; got: %v", subAgg)
}
if subAgg.Hits == nil {
t.Fatalf("expected sub aggregation Hits != nil; got: %v", subAgg.Hits)
}
if subAgg.Hits.TotalHits == nil {
t.Fatalf("expected sub aggregation Hits.TotalHits != nil; got: %v", subAgg.Hits.TotalHits)
}
if subAgg.Hits.TotalHits.Value != 25365 {
t.Fatalf("expected sub aggregation Hits.TotalHits.Value = %d; got: %d", 25365, subAgg.Hits.TotalHits.Value)
}
if subAgg.Hits.TotalHits.Relation != "eq" {
t.Fatalf("expected sub aggregation Hits.TotalHits.Relation = %v; got: %v", "eq", subAgg.Hits.TotalHits.Relation)
}
if subAgg.Hits.MaxScore == nil {
t.Fatalf("expected sub aggregation Hits.MaxScore != %v; got: %v", nil, *subAgg.Hits.MaxScore)
}
if *subAgg.Hits.MaxScore != float64(1.0) {
t.Fatalf("expected sub aggregation Hits.MaxScore = %v; got: %v", float64(1.0), *subAgg.Hits.MaxScore)
}
subAgg, found = agg.Buckets[1].TopHits("top_tags_hits")
if !found {
t.Fatalf("expected sub aggregation to be found; got: %v", found)
}
if subAgg == nil {
t.Fatalf("expected sub aggregation != nil; got: %v", subAgg)
}
if subAgg.Hits == nil {
t.Fatalf("expected sub aggregation Hits != nil; got: %v", subAgg.Hits)
}
if subAgg.Hits.TotalHits == nil {
t.Fatalf("expected sub aggregation Hits.TotalHits != nil; got: %v", subAgg.Hits.TotalHits)
}
if subAgg.Hits.TotalHits.Value != 18342 {
t.Fatalf("expected sub aggregation Hits.TotalHits.Value = %d; got: %d", 18342, subAgg.Hits.TotalHits.Value)
}
if subAgg.Hits.TotalHits.Relation != "eq" {
t.Fatalf("expected sub aggregation Hits.TotalHits.Relation = %v; got: %v", "eq", subAgg.Hits.TotalHits.Relation)
}
if subAgg.Hits.MaxScore == nil {
t.Fatalf("expected sub aggregation Hits.MaxScore != %v; got: %v", nil, *subAgg.Hits.MaxScore)
}
if *subAgg.Hits.MaxScore != float64(1.0) {
t.Fatalf("expected sub aggregation Hits.MaxScore = %v; got: %v", float64(1.0), *subAgg.Hits.MaxScore)
}
subAgg, found = agg.Buckets[2].TopHits("top_tags_hits")
if !found {
t.Fatalf("expected sub aggregation to be found; got: %v", found)
}
if subAgg == nil {
t.Fatalf("expected sub aggregation != nil; got: %v", subAgg)
}
if subAgg.Hits == nil {
t.Fatalf("expected sub aggregation Hits != nil; got: %v", subAgg.Hits)
}
if subAgg.Hits.TotalHits == nil {
t.Fatalf("expected sub aggregation Hits.TotalHits != nil; got: %v", subAgg.Hits.TotalHits)
}
if subAgg.Hits.TotalHits.Value != 18119 {
t.Fatalf("expected sub aggregation Hits.TotalHits.Value = %d; got: %d", 18119, subAgg.Hits.TotalHits.Value)
}
if subAgg.Hits.TotalHits.Relation != "eq" {
t.Fatalf("expected sub aggregation Hits.TotalHits.Relation = %v; got: %v", "eq", subAgg.Hits.TotalHits.Relation)
}
if subAgg.Hits.MaxScore == nil {
t.Fatalf("expected sub aggregation Hits.MaxScore != %v; got: %v", nil, *subAgg.Hits.MaxScore)
}
if *subAgg.Hits.MaxScore != float64(1.0) {
t.Fatalf("expected sub aggregation Hits.MaxScore = %v; got: %v", float64(1.0), *subAgg.Hits.MaxScore)
}
}
func TestAggsBucketGlobal(t *testing.T) {
s := `{
"all_products" : {
"doc_count" : 100,
"avg_price" : {
"value" : 56.3
}
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.Global("all_products")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.DocCount != 100 {
t.Fatalf("expected aggregation DocCount = %d; got: %d", 100, agg.DocCount)
}
// Sub-aggregation
subAgg, found := agg.Avg("avg_price")
if !found {
t.Fatalf("expected sub-aggregation to be found; got: %v", found)
}
if subAgg == nil {
t.Fatalf("expected sub-aggregation != nil; got: %v", subAgg)
}
if subAgg.Value == nil {
t.Fatalf("expected sub-aggregation value != nil; got: %v", subAgg.Value)
}
if *subAgg.Value != float64(56.3) {
t.Fatalf("expected sub-aggregation value = %v; got: %v", float64(56.3), *subAgg.Value)
}
}
func TestAggsBucketFilter(t *testing.T) {
s := `{
"in_stock_products" : {
"doc_count" : 100,
"avg_price" : { "value" : 56.3 }
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.Filter("in_stock_products")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.DocCount != 100 {
t.Fatalf("expected aggregation DocCount = %d; got: %d", 100, agg.DocCount)
}
// Sub-aggregation
subAgg, found := agg.Avg("avg_price")
if !found {
t.Fatalf("expected sub-aggregation to be found; got: %v", found)
}
if subAgg == nil {
t.Fatalf("expected sub-aggregation != nil; got: %v", subAgg)
}
if subAgg.Value == nil {
t.Fatalf("expected sub-aggregation value != nil; got: %v", subAgg.Value)
}
if *subAgg.Value != float64(56.3) {
t.Fatalf("expected sub-aggregation value = %v; got: %v", float64(56.3), *subAgg.Value)
}
}
func TestAggsBucketFiltersWithBuckets(t *testing.T) {
s := `{
"messages" : {
"buckets" : [
{
"doc_count" : 34,
"monthly" : {
"buckets" : []
}
},
{
"doc_count" : 439,
"monthly" : {
"buckets" : []
}
}
]
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.Filters("messages")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.Buckets == nil {
t.Fatalf("expected aggregation buckets != %v; got: %v", nil, agg.Buckets)
}
if len(agg.Buckets) != 2 {
t.Fatalf("expected %d buckets; got: %d", 2, len(agg.Buckets))
}
if agg.Buckets[0].DocCount != 34 {
t.Fatalf("expected DocCount = %d; got: %d", 34, agg.Buckets[0].DocCount)
}
subAgg, found := agg.Buckets[0].Histogram("monthly")
if !found {
t.Fatalf("expected sub aggregation to be found; got: %v", found)
}
if subAgg == nil {
t.Fatalf("expected sub aggregation != %v; got: %v", nil, subAgg)
}
if agg.Buckets[1].DocCount != 439 {
t.Fatalf("expected DocCount = %d; got: %d", 439, agg.Buckets[1].DocCount)
}
subAgg, found = agg.Buckets[1].Histogram("monthly")
if !found {
t.Fatalf("expected sub aggregation to be found; got: %v", found)
}
if subAgg == nil {
t.Fatalf("expected sub aggregation != %v; got: %v", nil, subAgg)
}
}
func TestAggsBucketFiltersWithNamedBuckets(t *testing.T) {
s := `{
"messages" : {
"buckets" : {
"errors" : {
"doc_count" : 34,
"monthly" : {
"buckets" : []
}
},
"warnings" : {
"doc_count" : 439,
"monthly" : {
"buckets" : []
}
}
}
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.Filters("messages")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.NamedBuckets == nil {
t.Fatalf("expected aggregation buckets != %v; got: %v", nil, agg.NamedBuckets)
}
if len(agg.NamedBuckets) != 2 {
t.Fatalf("expected %d buckets; got: %d", 2, len(agg.NamedBuckets))
}
if agg.NamedBuckets["errors"].DocCount != 34 {
t.Fatalf("expected DocCount = %d; got: %d", 34, agg.NamedBuckets["errors"].DocCount)
}
subAgg, found := agg.NamedBuckets["errors"].Histogram("monthly")
if !found {
t.Fatalf("expected sub aggregation to be found; got: %v", found)
}
if subAgg == nil {
t.Fatalf("expected sub aggregation != %v; got: %v", nil, subAgg)
}
if agg.NamedBuckets["warnings"].DocCount != 439 {
t.Fatalf("expected DocCount = %d; got: %d", 439, agg.NamedBuckets["warnings"].DocCount)
}
subAgg, found = agg.NamedBuckets["warnings"].Histogram("monthly")
if !found {
t.Fatalf("expected sub aggregation to be found; got: %v", found)
}
if subAgg == nil {
t.Fatalf("expected sub aggregation != %v; got: %v", nil, subAgg)
}
}
func TestAggsBucketAdjacencyMatrix(t *testing.T) {
s := `{
"interactions": {
"buckets": [
{
"key": "grpA",
"doc_count": 2,
"monthly": {
"buckets": []
}
},
{
"key": "grpA&grpB",
"doc_count": 1,
"monthly": {
"buckets": []
}
}
]
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.AdjacencyMatrix("interactions")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.Buckets == nil {
t.Fatalf("expected aggregation buckets != %v; got: %v", nil, agg.Buckets)
}
if len(agg.Buckets) != 2 {
t.Fatalf("expected %d buckets; got: %d", 2, len(agg.Buckets))
}
if agg.Buckets[0].DocCount != 2 {
t.Fatalf("expected DocCount = %d; got: %d", 2, agg.Buckets[0].DocCount)
}
subAgg, found := agg.Buckets[0].Histogram("monthly")
if !found {
t.Fatalf("expected sub aggregation to be found; got: %v", found)
}
if subAgg == nil {
t.Fatalf("expected sub aggregation != %v; got: %v", nil, subAgg)
}
if agg.Buckets[1].DocCount != 1 {
t.Fatalf("expected DocCount = %d; got: %d", 1, agg.Buckets[1].DocCount)
}
subAgg, found = agg.Buckets[1].Histogram("monthly")
if !found {
t.Fatalf("expected sub aggregation to be found; got: %v", found)
}
if subAgg == nil {
t.Fatalf("expected sub aggregation != %v; got: %v", nil, subAgg)
}
}
func TestAggsBucketMissing(t *testing.T) {
s := `{
"products_without_a_price" : {
"doc_count" : 10
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.Missing("products_without_a_price")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.DocCount != 10 {
t.Fatalf("expected aggregation DocCount = %d; got: %d", 10, agg.DocCount)
}
}
func TestAggsBucketNested(t *testing.T) {
s := `{
"resellers": {
"min_price": {
"value" : 350
}
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.Nested("resellers")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.DocCount != 0 {
t.Fatalf("expected aggregation DocCount = %d; got: %d", 0, agg.DocCount)
}
// Sub-aggregation
subAgg, found := agg.Avg("min_price")
if !found {
t.Fatalf("expected sub-aggregation to be found; got: %v", found)
}
if subAgg == nil {
t.Fatalf("expected sub-aggregation != nil; got: %v", subAgg)
}
if subAgg.Value == nil {
t.Fatalf("expected sub-aggregation value != nil; got: %v", subAgg.Value)
}
if *subAgg.Value != float64(350) {
t.Fatalf("expected sub-aggregation value = %v; got: %v", float64(350), *subAgg.Value)
}
}
func TestAggsBucketReverseNested(t *testing.T) {
s := `{
"comment_to_issue": {
"doc_count" : 10
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.ReverseNested("comment_to_issue")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.DocCount != 10 {
t.Fatalf("expected aggregation DocCount = %d; got: %d", 10, agg.DocCount)
}
}
func TestAggsBucketChildren(t *testing.T) {
s := `{
"to-answers": {
"doc_count" : 10
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.Children("to-answers")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.DocCount != 10 {
t.Fatalf("expected aggregation DocCount = %d; got: %d", 10, agg.DocCount)
}
}
func TestAggsBucketTerms(t *testing.T) {
s := `{
"users" : {
"doc_count_error_upper_bound" : 1,
"sum_other_doc_count" : 2,
"buckets" : [ {
"key" : "olivere",
"doc_count" : 2
}, {
"key" : "sandrae",
"doc_count" : 1
} ]
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.Terms("users")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.Buckets == nil {
t.Fatalf("expected aggregation buckets != nil; got: %v", agg.Buckets)
}
if len(agg.Buckets) != 2 {
t.Errorf("expected %d bucket entries; got: %d", 2, len(agg.Buckets))
}
if agg.Buckets[0].Key != "olivere" {
t.Errorf("expected key %q; got: %q", "olivere", agg.Buckets[0].Key)
}
if agg.Buckets[0].DocCount != 2 {
t.Errorf("expected doc count %d; got: %d", 2, agg.Buckets[0].DocCount)
}
if agg.Buckets[1].Key != "sandrae" {
t.Errorf("expected key %q; got: %q", "sandrae", agg.Buckets[1].Key)
}
if agg.Buckets[1].DocCount != 1 {
t.Errorf("expected doc count %d; got: %d", 1, agg.Buckets[1].DocCount)
}
}
func TestAggsBucketTermsWithNumericKeys(t *testing.T) {
s := `{
"users" : {
"doc_count_error_upper_bound" : 1,
"sum_other_doc_count" : 2,
"buckets" : [ {
"key" : 17,
"doc_count" : 2
}, {
"key" : 21,
"doc_count" : 1
} ]
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.Terms("users")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.Buckets == nil {
t.Fatalf("expected aggregation buckets != nil; got: %v", agg.Buckets)
}
if len(agg.Buckets) != 2 {
t.Errorf("expected %d bucket entries; got: %d", 2, len(agg.Buckets))
}
if agg.Buckets[0].Key != float64(17) {
t.Errorf("expected key %v; got: %v", 17, agg.Buckets[0].Key)
}
if got, err := agg.Buckets[0].KeyNumber.Int64(); err != nil {
t.Errorf("expected to convert key to int64; got: %v", err)
} else if got != 17 {
t.Errorf("expected key %v; got: %v", 17, agg.Buckets[0].Key)
}
if agg.Buckets[0].DocCount != 2 {
t.Errorf("expected doc count %d; got: %d", 2, agg.Buckets[0].DocCount)
}
if agg.Buckets[1].Key != float64(21) {
t.Errorf("expected key %v; got: %v", 21, agg.Buckets[1].Key)
}
if got, err := agg.Buckets[1].KeyNumber.Int64(); err != nil {
t.Errorf("expected to convert key to int64; got: %v", err)
} else if got != 21 {
t.Errorf("expected key %v; got: %v", 21, agg.Buckets[1].Key)
}
if agg.Buckets[1].DocCount != 1 {
t.Errorf("expected doc count %d; got: %d", 1, agg.Buckets[1].DocCount)
}
}
func TestAggsBucketTermsWithBoolKeys(t *testing.T) {
s := `{
"users" : {
"doc_count_error_upper_bound" : 1,
"sum_other_doc_count" : 2,
"buckets" : [ {
"key" : true,
"doc_count" : 2
}, {
"key" : false,
"doc_count" : 1
} ]
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.Terms("users")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.Buckets == nil {
t.Fatalf("expected aggregation buckets != nil; got: %v", agg.Buckets)
}
if len(agg.Buckets) != 2 {
t.Errorf("expected %d bucket entries; got: %d", 2, len(agg.Buckets))
}
if agg.Buckets[0].Key != true {
t.Errorf("expected key %v; got: %v", true, agg.Buckets[0].Key)
}
if agg.Buckets[0].DocCount != 2 {
t.Errorf("expected doc count %d; got: %d", 2, agg.Buckets[0].DocCount)
}
if agg.Buckets[1].Key != false {
t.Errorf("expected key %v; got: %v", false, agg.Buckets[1].Key)
}
if agg.Buckets[1].DocCount != 1 {
t.Errorf("expected doc count %d; got: %d", 1, agg.Buckets[1].DocCount)
}
}
func TestAggsBucketSignificantTerms(t *testing.T) {
s := `{
"significantCrimeTypes" : {
"doc_count": 47347,
"buckets" : [
{
"key": "Bicycle theft",
"doc_count": 3640,
"score": 0.371235374214817,
"bg_count": 66799
}
]
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.SignificantTerms("significantCrimeTypes")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.DocCount != 47347 {
t.Fatalf("expected aggregation DocCount != %d; got: %d", 47347, agg.DocCount)
}
if agg.Buckets == nil {
t.Fatalf("expected aggregation buckets != nil; got: %v", agg.Buckets)
}
if len(agg.Buckets) != 1 {
t.Errorf("expected %d bucket entries; got: %d", 1, len(agg.Buckets))
}
if agg.Buckets[0].Key != "Bicycle theft" {
t.Errorf("expected key = %q; got: %q", "Bicycle theft", agg.Buckets[0].Key)
}
if agg.Buckets[0].DocCount != 3640 {
t.Errorf("expected doc count = %d; got: %d", 3640, agg.Buckets[0].DocCount)
}
if agg.Buckets[0].Score != float64(0.371235374214817) {
t.Errorf("expected score = %v; got: %v", float64(0.371235374214817), agg.Buckets[0].Score)
}
if agg.Buckets[0].BgCount != 66799 {
t.Errorf("expected BgCount = %d; got: %d", 66799, agg.Buckets[0].BgCount)
}
}
func TestAggsBucketSampler(t *testing.T) {
s := `{
"sample" : {
"doc_count": 1000,
"keywords": {
"doc_count": 1000,
"buckets" : [
{
"key": "bend",
"doc_count": 58,
"score": 37.982536582524276,
"bg_count": 103
}
]
}
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.Sampler("sample")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.DocCount != 1000 {
t.Fatalf("expected aggregation DocCount != %d; got: %d", 1000, agg.DocCount)
}
sub, found := agg.Aggregations["keywords"]
if !found {
t.Fatalf("expected sub aggregation %q", "keywords")
}
if sub == nil {
t.Fatalf("expected sub aggregation %q; got: %v", "keywords", sub)
}
}
func TestAggsBucketDiversifiedSampler(t *testing.T) {
s := `{
"diversified_sampler" : {
"doc_count": 1000,
"keywords": {
"doc_count": 1000,
"buckets" : [
{
"key": "bend",
"doc_count": 58,
"score": 37.982536582524276,
"bg_count": 103
}
]
}
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.DiversifiedSampler("diversified_sampler")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.DocCount != 1000 {
t.Fatalf("expected aggregation DocCount != %d; got: %d", 1000, agg.DocCount)
}
sub, found := agg.Aggregations["keywords"]
if !found {
t.Fatalf("expected sub aggregation %q", "keywords")
}
if sub == nil {
t.Fatalf("expected sub aggregation %q; got: %v", "keywords", sub)
}
}
func TestAggsBucketRange(t *testing.T) {
s := `{
"price_ranges" : {
"buckets": [
{
"to": 50,
"doc_count": 2
},
{
"from": 50,
"to": 100,
"doc_count": 4
},
{
"from": 100,
"doc_count": 4
}
]
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.Range("price_ranges")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.Buckets == nil {
t.Fatalf("expected aggregation buckets != nil; got: %v", agg.Buckets)
}
if len(agg.Buckets) != 3 {
t.Errorf("expected %d bucket entries; got: %d", 3, len(agg.Buckets))
}
if agg.Buckets[0].From != nil {
t.Errorf("expected From = %v; got: %v", nil, agg.Buckets[0].From)
}
if agg.Buckets[0].To == nil {
t.Errorf("expected To != %v; got: %v", nil, agg.Buckets[0].To)
}
if *agg.Buckets[0].To != float64(50) {
t.Errorf("expected To = %v; got: %v", float64(50), *agg.Buckets[0].To)
}
if agg.Buckets[0].DocCount != 2 {
t.Errorf("expected DocCount = %d; got: %d", 2, agg.Buckets[0].DocCount)
}
if agg.Buckets[1].From == nil {
t.Errorf("expected From != %v; got: %v", nil, agg.Buckets[1].From)
}
if *agg.Buckets[1].From != float64(50) {
t.Errorf("expected From = %v; got: %v", float64(50), *agg.Buckets[1].From)
}
if agg.Buckets[1].To == nil {
t.Errorf("expected To != %v; got: %v", nil, agg.Buckets[1].To)
}
if *agg.Buckets[1].To != float64(100) {
t.Errorf("expected To = %v; got: %v", float64(100), *agg.Buckets[1].To)
}
if agg.Buckets[1].DocCount != 4 {
t.Errorf("expected DocCount = %d; got: %d", 4, agg.Buckets[1].DocCount)
}
if agg.Buckets[2].From == nil {
t.Errorf("expected From != %v; got: %v", nil, agg.Buckets[2].From)
}
if *agg.Buckets[2].From != float64(100) {
t.Errorf("expected From = %v; got: %v", float64(100), *agg.Buckets[2].From)
}
if agg.Buckets[2].To != nil {
t.Errorf("expected To = %v; got: %v", nil, agg.Buckets[2].To)
}
if agg.Buckets[2].DocCount != 4 {
t.Errorf("expected DocCount = %d; got: %d", 4, agg.Buckets[2].DocCount)
}
}
func TestAggsBucketDateRange(t *testing.T) {
s := `{
"range": {
"buckets": [
{
"to": 1.3437792E+12,
"to_as_string": "08-2012",
"doc_count": 7
},
{
"from": 1.3437792E+12,
"from_as_string": "08-2012",
"doc_count": 2
}
]
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.DateRange("range")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.Buckets == nil {
t.Fatalf("expected aggregation buckets != nil; got: %v", agg.Buckets)
}
if len(agg.Buckets) != 2 {
t.Errorf("expected %d bucket entries; got: %d", 2, len(agg.Buckets))
}
if agg.Buckets[0].From != nil {
t.Errorf("expected From = %v; got: %v", nil, agg.Buckets[0].From)
}
if agg.Buckets[0].To == nil {
t.Errorf("expected To != %v; got: %v", nil, agg.Buckets[0].To)
}
if *agg.Buckets[0].To != float64(1.3437792e+12) {
t.Errorf("expected To = %v; got: %v", float64(1.3437792e+12), *agg.Buckets[0].To)
}
if agg.Buckets[0].ToAsString != "08-2012" {
t.Errorf("expected ToAsString = %q; got: %q", "08-2012", agg.Buckets[0].ToAsString)
}
if agg.Buckets[0].DocCount != 7 {
t.Errorf("expected DocCount = %d; got: %d", 7, agg.Buckets[0].DocCount)
}
if agg.Buckets[1].From == nil {
t.Errorf("expected From != %v; got: %v", nil, agg.Buckets[1].From)
}
if *agg.Buckets[1].From != float64(1.3437792e+12) {
t.Errorf("expected From = %v; got: %v", float64(1.3437792e+12), *agg.Buckets[1].From)
}
if agg.Buckets[1].FromAsString != "08-2012" {
t.Errorf("expected FromAsString = %q; got: %q", "08-2012", agg.Buckets[1].FromAsString)
}
if agg.Buckets[1].To != nil {
t.Errorf("expected To = %v; got: %v", nil, agg.Buckets[1].To)
}
if agg.Buckets[1].DocCount != 2 {
t.Errorf("expected DocCount = %d; got: %d", 2, agg.Buckets[1].DocCount)
}
}
func TestAggsBucketIPRange(t *testing.T) {
s := `{
"ip_ranges": {
"buckets" : [
{
"to": 167772165,
"to_as_string": "10.0.0.5",
"doc_count": 4
},
{
"from": 167772165,
"from_as_string": "10.0.0.5",
"doc_count": 6
}
]
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.IPRange("ip_ranges")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.Buckets == nil {
t.Fatalf("expected aggregation buckets != nil; got: %v", agg.Buckets)
}
if len(agg.Buckets) != 2 {
t.Errorf("expected %d bucket entries; got: %d", 2, len(agg.Buckets))
}
if agg.Buckets[0].From != nil {
t.Errorf("expected From = %v; got: %v", nil, agg.Buckets[0].From)
}
if agg.Buckets[0].To == nil {
t.Errorf("expected To != %v; got: %v", nil, agg.Buckets[0].To)
}
if *agg.Buckets[0].To != float64(167772165) {
t.Errorf("expected To = %v; got: %v", float64(167772165), *agg.Buckets[0].To)
}
if agg.Buckets[0].ToAsString != "10.0.0.5" {
t.Errorf("expected ToAsString = %q; got: %q", "10.0.0.5", agg.Buckets[0].ToAsString)
}
if agg.Buckets[0].DocCount != 4 {
t.Errorf("expected DocCount = %d; got: %d", 4, agg.Buckets[0].DocCount)
}
if agg.Buckets[1].From == nil {
t.Errorf("expected From != %v; got: %v", nil, agg.Buckets[1].From)
}
if *agg.Buckets[1].From != float64(167772165) {
t.Errorf("expected From = %v; got: %v", float64(167772165), *agg.Buckets[1].From)
}
if agg.Buckets[1].FromAsString != "10.0.0.5" {
t.Errorf("expected FromAsString = %q; got: %q", "10.0.0.5", agg.Buckets[1].FromAsString)
}
if agg.Buckets[1].To != nil {
t.Errorf("expected To = %v; got: %v", nil, agg.Buckets[1].To)
}
if agg.Buckets[1].DocCount != 6 {
t.Errorf("expected DocCount = %d; got: %d", 6, agg.Buckets[1].DocCount)
}
}
func TestAggsBucketHistogram(t *testing.T) {
s := `{
"prices" : {
"buckets": [
{
"key": 0,
"doc_count": 2
},
{
"key": 50,
"doc_count": 4
},
{
"key": 150,
"doc_count": 3
}
]
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.Histogram("prices")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.Buckets == nil {
t.Fatalf("expected aggregation buckets != nil; got: %v", agg.Buckets)
}
if len(agg.Buckets) != 3 {
t.Errorf("expected %d buckets; got: %d", 3, len(agg.Buckets))
}
if agg.Buckets[0].Key != 0 {
t.Errorf("expected key = %v; got: %v", 0, agg.Buckets[0].Key)
}
if agg.Buckets[0].KeyAsString != nil {
t.Fatalf("expected key_as_string = %v; got: %q", nil, *agg.Buckets[0].KeyAsString)
}
if agg.Buckets[0].DocCount != 2 {
t.Errorf("expected doc count = %d; got: %d", 2, agg.Buckets[0].DocCount)
}
if agg.Buckets[1].Key != 50 {
t.Errorf("expected key = %v; got: %v", 50, agg.Buckets[1].Key)
}
if agg.Buckets[1].KeyAsString != nil {
t.Fatalf("expected key_as_string = %v; got: %q", nil, *agg.Buckets[1].KeyAsString)
}
if agg.Buckets[1].DocCount != 4 {
t.Errorf("expected doc count = %d; got: %d", 4, agg.Buckets[1].DocCount)
}
if agg.Buckets[2].Key != 150 {
t.Errorf("expected key = %v; got: %v", 150, agg.Buckets[2].Key)
}
if agg.Buckets[2].KeyAsString != nil {
t.Fatalf("expected key_as_string = %v; got: %q", nil, *agg.Buckets[2].KeyAsString)
}
if agg.Buckets[2].DocCount != 3 {
t.Errorf("expected doc count = %d; got: %d", 3, agg.Buckets[2].DocCount)
}
}
func TestAggsBucketDateHistogram(t *testing.T) {
s := `{
"articles_over_time": {
"buckets": [
{
"key_as_string": "2013-02-02",
"key": 1328140800000,
"doc_count": 1
},
{
"key_as_string": "2013-03-02",
"key": 1330646400000,
"doc_count": 2
}
]
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.DateHistogram("articles_over_time")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.Buckets == nil {
t.Fatalf("expected aggregation buckets != nil; got: %v", agg.Buckets)
}
if len(agg.Buckets) != 2 {
t.Errorf("expected %d bucket entries; got: %d", 2, len(agg.Buckets))
}
if agg.Buckets[0].Key != 1328140800000 {
t.Errorf("expected key %v; got: %v", 1328140800000, agg.Buckets[0].Key)
}
if agg.Buckets[0].KeyAsString == nil {
t.Fatalf("expected key_as_string != nil; got: %v", agg.Buckets[0].KeyAsString)
}
if *agg.Buckets[0].KeyAsString != "2013-02-02" {
t.Errorf("expected key_as_string %q; got: %q", "2013-02-02", *agg.Buckets[0].KeyAsString)
}
if agg.Buckets[0].DocCount != 1 {
t.Errorf("expected doc count %d; got: %d", 1, agg.Buckets[0].DocCount)
}
if agg.Buckets[1].Key != 1330646400000 {
t.Errorf("expected key %v; got: %v", 1330646400000, agg.Buckets[1].Key)
}
if agg.Buckets[1].KeyAsString == nil {
t.Fatalf("expected key_as_string != nil; got: %v", agg.Buckets[1].KeyAsString)
}
if *agg.Buckets[1].KeyAsString != "2013-03-02" {
t.Errorf("expected key_as_string %q; got: %q", "2013-03-02", *agg.Buckets[1].KeyAsString)
}
if agg.Buckets[1].DocCount != 2 {
t.Errorf("expected doc count %d; got: %d", 2, agg.Buckets[1].DocCount)
}
}
func TestAggsMetricsGeoBounds(t *testing.T) {
s := `{
"viewport": {
"bounds": {
"top_left": {
"lat": 80.45,
"lon": -160.22
},
"bottom_right": {
"lat": 40.65,
"lon": 42.57
}
}
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.GeoBounds("viewport")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.Bounds.TopLeft.Latitude != float64(80.45) {
t.Fatalf("expected Bounds.TopLeft.Latitude != %v; got: %v", float64(80.45), agg.Bounds.TopLeft.Latitude)
}
if agg.Bounds.TopLeft.Longitude != float64(-160.22) {
t.Fatalf("expected Bounds.TopLeft.Longitude != %v; got: %v", float64(-160.22), agg.Bounds.TopLeft.Longitude)
}
if agg.Bounds.BottomRight.Latitude != float64(40.65) {
t.Fatalf("expected Bounds.BottomRight.Latitude != %v; got: %v", float64(40.65), agg.Bounds.BottomRight.Latitude)
}
if agg.Bounds.BottomRight.Longitude != float64(42.57) {
t.Fatalf("expected Bounds.BottomRight.Longitude != %v; got: %v", float64(42.57), agg.Bounds.BottomRight.Longitude)
}
}
func TestAggsBucketGeoHash(t *testing.T) {
s := `{
"myLarge-GrainGeoHashGrid": {
"buckets": [
{
"key": "svz",
"doc_count": 10964
},
{
"key": "sv8",
"doc_count": 3198
}
]
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.GeoHash("myLarge-GrainGeoHashGrid")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.Buckets == nil {
t.Fatalf("expected aggregation buckets != nil; got: %v", agg.Buckets)
}
if len(agg.Buckets) != 2 {
t.Errorf("expected %d bucket entries; got: %d", 2, len(agg.Buckets))
}
if agg.Buckets[0].Key != "svz" {
t.Errorf("expected key %q; got: %q", "svz", agg.Buckets[0].Key)
}
if agg.Buckets[0].DocCount != 10964 {
t.Errorf("expected doc count %d; got: %d", 10964, agg.Buckets[0].DocCount)
}
if agg.Buckets[1].Key != "sv8" {
t.Errorf("expected key %q; got: %q", "sv8", agg.Buckets[1].Key)
}
if agg.Buckets[1].DocCount != 3198 {
t.Errorf("expected doc count %d; got: %d", 3198, agg.Buckets[1].DocCount)
}
}
func TestAggsBucketGeoTileGrid(t *testing.T) {
s := `{
"geotile-grid-aggregation":{
"buckets":[
{
"key": "6/38/20",
"doc_count": 36914
},
{
"key": "6/38/19",
"doc_count": 22182
}
]
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.GeoTile("geotile-grid-aggregation")
if !found {
t.Fatal("expected aggregation to be found")
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.Buckets == nil {
t.Fatalf("expected aggregation buckets != nil; got: %v", agg.Buckets)
}
if len(agg.Buckets) != 2 {
t.Errorf("expected %d bucket entries; got: %d", 2, len(agg.Buckets))
}
if agg.Buckets[0].Key != "6/38/20" {
t.Errorf("expected key %q; got: %q", "6/38/20", agg.Buckets[0].Key)
}
if agg.Buckets[0].DocCount != 36914 {
t.Errorf("expected doc count %d; got: %d", 36914, agg.Buckets[0].DocCount)
}
if agg.Buckets[1].Key != "6/38/19" {
t.Errorf("expected key %q; got: %q", "6/38/19", agg.Buckets[1].Key)
}
if agg.Buckets[1].DocCount != 22182 {
t.Errorf("expected doc count %d; got: %d", 22182, agg.Buckets[1].DocCount)
}
}
func TestAggsMetricsGeoCentroid(t *testing.T) {
s := `{
"centroid": {
"location": {
"lat": 80.45,
"lon": -160.22
},
"count": 6
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.GeoCentroid("centroid")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.Location.Latitude != float64(80.45) {
t.Fatalf("expected Location.Latitude != %v; got: %v", float64(80.45), agg.Location.Latitude)
}
if agg.Location.Longitude != float64(-160.22) {
t.Fatalf("expected Location.Longitude != %v; got: %v", float64(-160.22), agg.Location.Longitude)
}
if agg.Count != int(6) {
t.Fatalf("expected Count != %v; got: %v", int(6), agg.Count)
}
}
func TestAggsBucketGeoDistance(t *testing.T) {
s := `{
"rings" : {
"buckets": [
{
"unit": "km",
"to": 100.0,
"doc_count": 3
},
{
"unit": "km",
"from": 100.0,
"to": 300.0,
"doc_count": 1
},
{
"unit": "km",
"from": 300.0,
"doc_count": 7
}
]
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.GeoDistance("rings")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.Buckets == nil {
t.Fatalf("expected aggregation buckets != nil; got: %v", agg.Buckets)
}
if len(agg.Buckets) != 3 {
t.Errorf("expected %d bucket entries; got: %d", 3, len(agg.Buckets))
}
if agg.Buckets[0].From != nil {
t.Errorf("expected From = %v; got: %v", nil, agg.Buckets[0].From)
}
if agg.Buckets[0].To == nil {
t.Errorf("expected To != %v; got: %v", nil, agg.Buckets[0].To)
}
if *agg.Buckets[0].To != float64(100.0) {
t.Errorf("expected To = %v; got: %v", float64(100.0), *agg.Buckets[0].To)
}
if agg.Buckets[0].DocCount != 3 {
t.Errorf("expected DocCount = %d; got: %d", 4, agg.Buckets[0].DocCount)
}
if agg.Buckets[1].From == nil {
t.Errorf("expected From != %v; got: %v", nil, agg.Buckets[1].From)
}
if *agg.Buckets[1].From != float64(100.0) {
t.Errorf("expected From = %v; got: %v", float64(100.0), *agg.Buckets[1].From)
}
if agg.Buckets[1].To == nil {
t.Errorf("expected To != %v; got: %v", nil, agg.Buckets[1].To)
}
if *agg.Buckets[1].To != float64(300.0) {
t.Errorf("expected From = %v; got: %v", float64(300.0), *agg.Buckets[1].To)
}
if agg.Buckets[1].DocCount != 1 {
t.Errorf("expected DocCount = %d; got: %d", 1, agg.Buckets[1].DocCount)
}
if agg.Buckets[2].From == nil {
t.Errorf("expected From != %v; got: %v", nil, agg.Buckets[2].From)
}
if *agg.Buckets[2].From != float64(300.0) {
t.Errorf("expected From = %v; got: %v", float64(300.0), *agg.Buckets[2].From)
}
if agg.Buckets[2].To != nil {
t.Errorf("expected To = %v; got: %v", nil, agg.Buckets[2].To)
}
if agg.Buckets[2].DocCount != 7 {
t.Errorf("expected DocCount = %d; got: %d", 7, agg.Buckets[2].DocCount)
}
}
func TestAggsSubAggregates(t *testing.T) {
rs := `{
"users" : {
"doc_count_error_upper_bound" : 1,
"sum_other_doc_count" : 2,
"buckets" : [ {
"key" : "olivere",
"doc_count" : 2,
"ts" : {
"buckets" : [ {
"key_as_string" : "2012-01-01T00:00:00.000Z",
"key" : 1325376000000,
"doc_count" : 2
} ]
}
}, {
"key" : "sandrae",
"doc_count" : 1,
"ts" : {
"buckets" : [ {
"key_as_string" : "2011-01-01T00:00:00.000Z",
"key" : 1293840000000,
"doc_count" : 1
} ]
}
} ]
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(rs), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
// Access top-level aggregation
users, found := aggs.Terms("users")
if !found {
t.Fatalf("expected users aggregation to be found; got: %v", found)
}
if users == nil {
t.Fatalf("expected users aggregation; got: %v", users)
}
if users.Buckets == nil {
t.Fatalf("expected users buckets; got: %v", users.Buckets)
}
if len(users.Buckets) != 2 {
t.Errorf("expected %d bucket entries; got: %d", 2, len(users.Buckets))
}
if users.Buckets[0].Key != "olivere" {
t.Errorf("expected key %q; got: %q", "olivere", users.Buckets[0].Key)
}
if users.Buckets[0].DocCount != 2 {
t.Errorf("expected doc count %d; got: %d", 2, users.Buckets[0].DocCount)
}
if users.Buckets[1].Key != "sandrae" {
t.Errorf("expected key %q; got: %q", "sandrae", users.Buckets[1].Key)
}
if users.Buckets[1].DocCount != 1 {
t.Errorf("expected doc count %d; got: %d", 1, users.Buckets[1].DocCount)
}
// Access sub-aggregation
ts, found := users.Buckets[0].DateHistogram("ts")
if !found {
t.Fatalf("expected ts aggregation to be found; got: %v", found)
}
if ts == nil {
t.Fatalf("expected ts aggregation; got: %v", ts)
}
if ts.Buckets == nil {
t.Fatalf("expected ts buckets; got: %v", ts.Buckets)
}
if len(ts.Buckets) != 1 {
t.Errorf("expected %d bucket entries; got: %d", 1, len(ts.Buckets))
}
if ts.Buckets[0].Key != 1325376000000 {
t.Errorf("expected key %v; got: %v", 1325376000000, ts.Buckets[0].Key)
}
if ts.Buckets[0].KeyAsString == nil {
t.Fatalf("expected key_as_string != %v; got: %v", nil, ts.Buckets[0].KeyAsString)
}
if *ts.Buckets[0].KeyAsString != "2012-01-01T00:00:00.000Z" {
t.Errorf("expected key_as_string %q; got: %q", "2012-01-01T00:00:00.000Z", *ts.Buckets[0].KeyAsString)
}
}
func TestAggsPipelineAvgBucket(t *testing.T) {
s := `{
"avg_monthly_sales" : {
"value" : 328.33333333333333
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.AvgBucket("avg_monthly_sales")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.Value == nil {
t.Fatalf("expected aggregation value != nil; got: %v", agg.Value)
}
if *agg.Value != float64(328.33333333333333) {
t.Fatalf("expected aggregation value = %v; got: %v", float64(328.33333333333333), *agg.Value)
}
}
func TestAggsPipelineSumBucket(t *testing.T) {
s := `{
"sum_monthly_sales" : {
"value" : 985
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.SumBucket("sum_monthly_sales")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.Value == nil {
t.Fatalf("expected aggregation value != nil; got: %v", agg.Value)
}
if *agg.Value != float64(985) {
t.Fatalf("expected aggregation value = %v; got: %v", float64(985), *agg.Value)
}
}
func TestAggsPipelineMaxBucket(t *testing.T) {
s := `{
"max_monthly_sales" : {
"keys": ["2015/01/01 00:00:00"],
"value" : 550
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.MaxBucket("max_monthly_sales")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if len(agg.Keys) != 1 {
t.Fatalf("expected 1 key; got: %d", len(agg.Keys))
}
if got, want := agg.Keys[0], "2015/01/01 00:00:00"; got != want {
t.Fatalf("expected key %q; got: %v (%T)", want, got, got)
}
if agg.Value == nil {
t.Fatalf("expected aggregation value != nil; got: %v", agg.Value)
}
if *agg.Value != float64(550) {
t.Fatalf("expected aggregation value = %v; got: %v", float64(550), *agg.Value)
}
}
func TestAggsPipelineMinBucket(t *testing.T) {
s := `{
"min_monthly_sales" : {
"keys": ["2015/02/01 00:00:00"],
"value" : 60
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.MinBucket("min_monthly_sales")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if len(agg.Keys) != 1 {
t.Fatalf("expected 1 key; got: %d", len(agg.Keys))
}
if got, want := agg.Keys[0], "2015/02/01 00:00:00"; got != want {
t.Fatalf("expected key %q; got: %v (%T)", want, got, got)
}
if agg.Value == nil {
t.Fatalf("expected aggregation value != nil; got: %v", agg.Value)
}
if *agg.Value != float64(60) {
t.Fatalf("expected aggregation value = %v; got: %v", float64(60), *agg.Value)
}
}
func TestAggsPipelineMovAvg(t *testing.T) {
s := `{
"the_movavg" : {
"value" : 12.0
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.MovAvg("the_movavg")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.Value == nil {
t.Fatalf("expected aggregation value != nil; got: %v", agg.Value)
}
if *agg.Value != float64(12.0) {
t.Fatalf("expected aggregation value = %v; got: %v", float64(12.0), *agg.Value)
}
}
func TestAggsPipelineDerivative(t *testing.T) {
s := `{
"sales_deriv" : {
"value" : 315
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.Derivative("sales_deriv")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.Value == nil {
t.Fatalf("expected aggregation value != nil; got: %v", agg.Value)
}
if *agg.Value != float64(315) {
t.Fatalf("expected aggregation value = %v; got: %v", float64(315), *agg.Value)
}
}
func TestAggsPipelinePercentilesBucket(t *testing.T) {
s := `{
"sales_percentiles": {
"values": {
"25.0": 100,
"50.0": 200,
"75.0": 300
}
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.PercentilesBucket("sales_percentiles")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if len(agg.Values) != 3 {
t.Fatalf("expected aggregation map with three entries; got: %v", agg.Values)
}
}
func TestAggsPipelineStatsBucket(t *testing.T) {
s := `{
"stats_monthly_sales": {
"count": 3,
"min": 60.0,
"max": 550.0,
"avg": 328.3333333333333,
"sum": 985.0
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.StatsBucket("stats_monthly_sales")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.Count != 3 {
t.Fatalf("expected aggregation count = %v; got: %v", 3, agg.Count)
}
if agg.Min == nil {
t.Fatalf("expected aggregation min != nil; got: %v", agg.Min)
}
if *agg.Min != float64(60.0) {
t.Fatalf("expected aggregation min = %v; got: %v", float64(60.0), *agg.Min)
}
if agg.Max == nil {
t.Fatalf("expected aggregation max != nil; got: %v", agg.Max)
}
if *agg.Max != float64(550.0) {
t.Fatalf("expected aggregation max = %v; got: %v", float64(550.0), *agg.Max)
}
if agg.Avg == nil {
t.Fatalf("expected aggregation avg != nil; got: %v", agg.Avg)
}
if *agg.Avg != float64(328.3333333333333) {
t.Fatalf("expected aggregation average = %v; got: %v", float64(328.3333333333333), *agg.Avg)
}
if agg.Sum == nil {
t.Fatalf("expected aggregation sum != nil; got: %v", agg.Sum)
}
if *agg.Sum != float64(985.0) {
t.Fatalf("expected aggregation sum = %v; got: %v", float64(985.0), *agg.Sum)
}
}
func TestAggsPipelineCumulativeSum(t *testing.T) {
s := `{
"cumulative_sales" : {
"value" : 550
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.CumulativeSum("cumulative_sales")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.Value == nil {
t.Fatalf("expected aggregation value != nil; got: %v", agg.Value)
}
if *agg.Value != float64(550) {
t.Fatalf("expected aggregation value = %v; got: %v", float64(550), *agg.Value)
}
}
func TestAggsPipelineBucketScript(t *testing.T) {
s := `{
"t-shirt-percentage" : {
"value" : 20
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.BucketScript("t-shirt-percentage")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.Value == nil {
t.Fatalf("expected aggregation value != nil; got: %v", agg.Value)
}
if *agg.Value != float64(20) {
t.Fatalf("expected aggregation value = %v; got: %v", float64(20), *agg.Value)
}
}
func TestAggsPipelineSerialDiff(t *testing.T) {
s := `{
"the_diff" : {
"value" : -722.0
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.SerialDiff("the_diff")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if agg.Value == nil {
t.Fatalf("expected aggregation value != nil; got: %v", agg.Value)
}
if *agg.Value != float64(-722.0) {
t.Fatalf("expected aggregation value = %v; got: %v", float64(20), *agg.Value)
}
}
func TestAggsComposite(t *testing.T) {
s := `{
"the_composite" : {
"buckets" : [
{
"key" : {
"composite_users" : "olivere",
"composite_retweets" : 0.0,
"composite_created" : 1349856720000
},
"doc_count" : 1
},
{
"key" : {
"composite_users" : "olivere",
"composite_retweets" : 108.0,
"composite_created" : 1355333880000
},
"doc_count" : 1
},
{
"key" : {
"composite_users" : "sandrae",
"composite_retweets" : 12.0,
"composite_created" : 1321009080000
},
"doc_count" : 1
}
]
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %v", err)
}
agg, found := aggs.Composite("the_composite")
if !found {
t.Fatalf("expected aggregation to be found; got: %v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %v", agg)
}
if want, have := 3, len(agg.Buckets); want != have {
t.Fatalf("expected aggregation buckets length = %v; got: %v", want, have)
}
// 1st bucket
bucket := agg.Buckets[0]
if want, have := int64(1), bucket.DocCount; want != have {
t.Fatalf("expected aggregation bucket doc count = %v; got: %v", want, have)
}
if want, have := 3, len(bucket.Key); want != have {
t.Fatalf("expected aggregation bucket key length = %v; got: %v", want, have)
}
v, found := bucket.Key["composite_users"]
if !found {
t.Fatalf("expected to find bucket key %q", "composite_users")
}
s, ok := v.(string)
if !ok {
t.Fatalf("expected to have bucket key of type string; got: %T", v)
}
if want, have := "olivere", s; want != have {
t.Fatalf("expected to find bucket key value %q; got: %q", want, have)
}
v, found = bucket.Key["composite_retweets"]
if !found {
t.Fatalf("expected to find bucket key %q", "composite_retweets")
}
f, ok := v.(float64)
if !ok {
t.Fatalf("expected to have bucket key of type string; got: %T", v)
}
if want, have := 0.0, f; want != have {
t.Fatalf("expected to find bucket key value %v; got: %v", want, have)
}
v, found = bucket.Key["composite_created"]
if !found {
t.Fatalf("expected to find bucket key %q", "composite_created")
}
f, ok = v.(float64)
if !ok {
t.Fatalf("expected to have bucket key of type string; got: %T", v)
}
if want, have := 1349856720000.0, f; want != have {
t.Fatalf("expected to find bucket key value %v; got: %v", want, have)
}
// 2nd bucket
bucket = agg.Buckets[1]
if want, have := int64(1), bucket.DocCount; want != have {
t.Fatalf("expected aggregation bucket doc count = %v; got: %v", want, have)
}
if want, have := 3, len(bucket.Key); want != have {
t.Fatalf("expected aggregation bucket key length = %v; got: %v", want, have)
}
v, found = bucket.Key["composite_users"]
if !found {
t.Fatalf("expected to find bucket key %q", "composite_users")
}
s, ok = v.(string)
if !ok {
t.Fatalf("expected to have bucket key of type string; got: %T", v)
}
if want, have := "olivere", s; want != have {
t.Fatalf("expected to find bucket key value %q; got: %q", want, have)
}
v, found = bucket.Key["composite_retweets"]
if !found {
t.Fatalf("expected to find bucket key %q", "composite_retweets")
}
f, ok = v.(float64)
if !ok {
t.Fatalf("expected to have bucket key of type string; got: %T", v)
}
if want, have := 108.0, f; want != have {
t.Fatalf("expected to find bucket key value %v; got: %v", want, have)
}
v, found = bucket.Key["composite_created"]
if !found {
t.Fatalf("expected to find bucket key %q", "composite_created")
}
f, ok = v.(float64)
if !ok {
t.Fatalf("expected to have bucket key of type string; got: %T", v)
}
if want, have := 1355333880000.0, f; want != have {
t.Fatalf("expected to find bucket key value %v; got: %v", want, have)
}
// 3rd bucket
bucket = agg.Buckets[2]
if want, have := int64(1), bucket.DocCount; want != have {
t.Fatalf("expected aggregation bucket doc count = %v; got: %v", want, have)
}
if want, have := 3, len(bucket.Key); want != have {
t.Fatalf("expected aggregation bucket key length = %v; got: %v", want, have)
}
v, found = bucket.Key["composite_users"]
if !found {
t.Fatalf("expected to find bucket key %q", "composite_users")
}
s, ok = v.(string)
if !ok {
t.Fatalf("expected to have bucket key of type string; got: %T", v)
}
if want, have := "sandrae", s; want != have {
t.Fatalf("expected to find bucket key value %q; got: %q", want, have)
}
v, found = bucket.Key["composite_retweets"]
if !found {
t.Fatalf("expected to find bucket key %q", "composite_retweets")
}
f, ok = v.(float64)
if !ok {
t.Fatalf("expected to have bucket key of type string; got: %T", v)
}
if want, have := 12.0, f; want != have {
t.Fatalf("expected to find bucket key value %v; got: %v", want, have)
}
v, found = bucket.Key["composite_created"]
if !found {
t.Fatalf("expected to find bucket key %q", "composite_created")
}
f, ok = v.(float64)
if !ok {
t.Fatalf("expected to have bucket key of type string; got: %T", v)
}
if want, have := 1321009080000.0, f; want != have {
t.Fatalf("expected to find bucket key value %v; got: %v", want, have)
}
}
func TestAggsScriptedMetric(t *testing.T) {
s := `{
"bool_metric": {
"value": true
},
"int_metric": {
"value": 1
},
"float_metric": {
"value": 2.5
},
"string_metric": {
"value": "test"
},
"slice_metric": {
"value": [
1,
2,
3
]
},
"map_metric": {
"value": {
"a": "1",
"b": "2"
}
}
}`
aggs := new(Aggregations)
err := json.Unmarshal([]byte(s), &aggs)
if err != nil {
t.Fatalf("expected no error decoding; got: %+v", err)
}
agg, found := aggs.ScriptedMetric("bool_metric")
if !found {
t.Fatalf("expected aggregation to be found; got: %+v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %+v", agg)
}
if v, ok := agg.Value.(bool); !ok || !v {
t.Fatalf("expected aggregation value is bool true; got: %+v", agg.Value)
}
agg, found = aggs.ScriptedMetric("int_metric")
if !found {
t.Fatalf("expected aggregation to be found; got: %+v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %+v", agg)
}
if v, ok := agg.Value.(json.Number); ok {
if iv, err := v.Int64(); err != nil || iv != 1 {
t.Fatalf("expected aggregation value is 1; got: %+v", iv)
}
} else {
t.Fatalf("expected aggregation value is json.Number; got: %+v", agg.Value)
}
agg, found = aggs.ScriptedMetric("float_metric")
if !found {
t.Fatalf("expected aggregation to be found; got: %+v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %+v", agg)
}
if v, ok := agg.Value.(json.Number); ok {
if iv, err := v.Float64(); err != nil || iv != 2.5 {
t.Fatalf("expected aggregation value is 2.5; got: %+v", iv)
}
} else {
t.Fatalf("expected aggregation value is json.Number; got: %+v", agg.Value)
}
agg, found = aggs.ScriptedMetric("string_metric")
if !found {
t.Fatalf("expected aggregation to be found; got: %+v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %+v", agg)
}
if v, ok := agg.Value.(string); !ok || v != "test" {
t.Fatalf("expected aggregation value is test; got: %+v", agg.Value)
}
agg, found = aggs.ScriptedMetric("slice_metric")
if !found {
t.Fatalf("expected aggregation to be found; got: %+v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %+v", agg)
}
if v, ok := agg.Value.([]interface{}); ok {
expected := []interface{}{json.Number("1"), json.Number("2"), json.Number("3")}
if !reflect.DeepEqual(v, expected) {
t.Fatalf("expected %+v; got: %+v", expected, v)
}
} else {
t.Fatalf("expected aggregation value is []interface{}; got: %+v", agg.Value)
}
agg, found = aggs.ScriptedMetric("map_metric")
if !found {
t.Fatalf("expected aggregation to be found; got: %+v", found)
}
if agg == nil {
t.Fatalf("expected aggregation != nil; got: %+v", agg)
}
if v, ok := agg.Value.(map[string]interface{}); ok {
expected := map[string]interface{}{
"a": "1",
"b": "2",
}
if !reflect.DeepEqual(v, expected) {
t.Fatalf("expected %+v; got: %+v", expected, v)
}
} else {
t.Fatalf("expected aggregation value is map[string]interface{}; got: %+v", agg.Value)
}
}
================================================
FILE: search_collapse_builder.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// CollapseBuilder enables field collapsing on a search request.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-request-collapse.html
// for details.
type CollapseBuilder struct {
field string
innerHits []*InnerHit
maxConcurrentGroupRequests *int
}
// NewCollapseBuilder creates a new CollapseBuilder.
func NewCollapseBuilder(field string) *CollapseBuilder {
return &CollapseBuilder{field: field}
}
// Field to collapse.
func (b *CollapseBuilder) Field(field string) *CollapseBuilder {
b.field = field
return b
}
// InnerHit option to expand the collapsed results.
func (b *CollapseBuilder) InnerHit(innerHits ...*InnerHit) *CollapseBuilder {
b.innerHits = append(b.innerHits, innerHits...)
return b
}
// MaxConcurrentGroupRequests is the maximum number of group requests that are
// allowed to be ran concurrently in the inner_hits phase.
func (b *CollapseBuilder) MaxConcurrentGroupRequests(max int) *CollapseBuilder {
b.maxConcurrentGroupRequests = &max
return b
}
// Source generates the JSON serializable fragment for the CollapseBuilder.
func (b *CollapseBuilder) Source() (interface{}, error) {
// {
// "field": "user",
// "inner_hits": [{
// "name": "last_tweets",
// "size": 5,
// "sort": [{ "date": "asc" }]
// }],
// "max_concurrent_group_searches": 4
// }
src := map[string]interface{}{
"field": b.field,
}
if len(b.innerHits) > 0 {
var innerHits []interface{}
for _, h := range b.innerHits {
hits, err := h.Source()
if err != nil {
return nil, err
}
innerHits = append(innerHits, hits)
}
src["inner_hits"] = innerHits
}
if b.maxConcurrentGroupRequests != nil {
src["max_concurrent_group_searches"] = *b.maxConcurrentGroupRequests
}
return src, nil
}
================================================
FILE: search_collapse_builder_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestCollapseBuilderSource(t *testing.T) {
b := NewCollapseBuilder("user").
InnerHit(NewInnerHit().Name("last_tweets").Size(5).Sort("date", true)).
MaxConcurrentGroupRequests(4)
src, err := b.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"field":"user","inner_hits":[{"name":"last_tweets","size":5,"sort":[{"date":{"order":"asc"}}]}],"max_concurrent_group_searches":4}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestCollapseBuilderSourceMultipleInnerHits(t *testing.T) {
b := NewCollapseBuilder("user.id").
InnerHit(NewInnerHit().Name("largest_responses").Size(3).Sort("http.response.bytes", false)).
InnerHit(NewInnerHit().Name("most_recent").Size(4).Sort("@timestamp", false)).
MaxConcurrentGroupRequests(5)
src, err := b.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"field":"user.id","inner_hits":[{"name":"largest_responses","size":3,"sort":[{"http.response.bytes":{"order":"desc"}}]},{"name":"most_recent","size":4,"sort":[{"@timestamp":{"order":"desc"}}]}],"max_concurrent_group_searches":5}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_queries_bool.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import "fmt"
// A bool query matches documents matching boolean
// combinations of other queries.
// For more details, see:
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-bool-query.html
type BoolQuery struct {
Query
mustClauses []Query
mustNotClauses []Query
filterClauses []Query
shouldClauses []Query
boost *float64
minimumShouldMatch string
adjustPureNegative *bool
queryName string
}
// Creates a new bool query.
func NewBoolQuery() *BoolQuery {
return &BoolQuery{
mustClauses: make([]Query, 0),
mustNotClauses: make([]Query, 0),
filterClauses: make([]Query, 0),
shouldClauses: make([]Query, 0),
}
}
func (q *BoolQuery) Must(queries ...Query) *BoolQuery {
q.mustClauses = append(q.mustClauses, queries...)
return q
}
func (q *BoolQuery) MustNot(queries ...Query) *BoolQuery {
q.mustNotClauses = append(q.mustNotClauses, queries...)
return q
}
func (q *BoolQuery) Filter(filters ...Query) *BoolQuery {
q.filterClauses = append(q.filterClauses, filters...)
return q
}
func (q *BoolQuery) Should(queries ...Query) *BoolQuery {
q.shouldClauses = append(q.shouldClauses, queries...)
return q
}
func (q *BoolQuery) Boost(boost float64) *BoolQuery {
q.boost = &boost
return q
}
func (q *BoolQuery) MinimumShouldMatch(minimumShouldMatch string) *BoolQuery {
q.minimumShouldMatch = minimumShouldMatch
return q
}
func (q *BoolQuery) MinimumNumberShouldMatch(minimumNumberShouldMatch int) *BoolQuery {
q.minimumShouldMatch = fmt.Sprintf("%d", minimumNumberShouldMatch)
return q
}
func (q *BoolQuery) AdjustPureNegative(adjustPureNegative bool) *BoolQuery {
q.adjustPureNegative = &adjustPureNegative
return q
}
func (q *BoolQuery) QueryName(queryName string) *BoolQuery {
q.queryName = queryName
return q
}
// Creates the query source for the bool query.
func (q *BoolQuery) Source() (interface{}, error) {
// {
// "bool" : {
// "must" : {
// "term" : { "user" : "kimchy" }
// },
// "must_not" : {
// "range" : {
// "age" : { "from" : 10, "to" : 20 }
// }
// },
// "filter" : [
// ...
// ]
// "should" : [
// {
// "term" : { "tag" : "wow" }
// },
// {
// "term" : { "tag" : "elasticsearch" }
// }
// ],
// "minimum_should_match" : 1,
// "boost" : 1.0
// }
// }
query := make(map[string]interface{})
boolClause := make(map[string]interface{})
query["bool"] = boolClause
// must
if len(q.mustClauses) == 1 {
src, err := q.mustClauses[0].Source()
if err != nil {
return nil, err
}
boolClause["must"] = src
} else if len(q.mustClauses) > 1 {
var clauses []interface{}
for _, subQuery := range q.mustClauses {
src, err := subQuery.Source()
if err != nil {
return nil, err
}
clauses = append(clauses, src)
}
boolClause["must"] = clauses
}
// must_not
if len(q.mustNotClauses) == 1 {
src, err := q.mustNotClauses[0].Source()
if err != nil {
return nil, err
}
boolClause["must_not"] = src
} else if len(q.mustNotClauses) > 1 {
var clauses []interface{}
for _, subQuery := range q.mustNotClauses {
src, err := subQuery.Source()
if err != nil {
return nil, err
}
clauses = append(clauses, src)
}
boolClause["must_not"] = clauses
}
// filter
if len(q.filterClauses) == 1 {
src, err := q.filterClauses[0].Source()
if err != nil {
return nil, err
}
boolClause["filter"] = src
} else if len(q.filterClauses) > 1 {
var clauses []interface{}
for _, subQuery := range q.filterClauses {
src, err := subQuery.Source()
if err != nil {
return nil, err
}
clauses = append(clauses, src)
}
boolClause["filter"] = clauses
}
// should
if len(q.shouldClauses) == 1 {
src, err := q.shouldClauses[0].Source()
if err != nil {
return nil, err
}
boolClause["should"] = src
} else if len(q.shouldClauses) > 1 {
var clauses []interface{}
for _, subQuery := range q.shouldClauses {
src, err := subQuery.Source()
if err != nil {
return nil, err
}
clauses = append(clauses, src)
}
boolClause["should"] = clauses
}
if q.boost != nil {
boolClause["boost"] = *q.boost
}
if q.minimumShouldMatch != "" {
boolClause["minimum_should_match"] = q.minimumShouldMatch
}
if q.adjustPureNegative != nil {
boolClause["adjust_pure_negative"] = *q.adjustPureNegative
}
if q.queryName != "" {
boolClause["_name"] = q.queryName
}
return query, nil
}
================================================
FILE: search_queries_bool_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestBoolQuery(t *testing.T) {
q := NewBoolQuery()
q = q.Must(NewTermQuery("tag", "wow"))
q = q.MustNot(NewRangeQuery("age").From(10).To(20))
q = q.Filter(NewTermQuery("account", "1"))
q = q.Should(NewTermQuery("tag", "sometag"), NewTermQuery("tag", "sometagtag"))
q = q.Boost(10)
q = q.QueryName("Test")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"bool":{"_name":"Test","boost":10,"filter":{"term":{"account":"1"}},"must":{"term":{"tag":"wow"}},"must_not":{"range":{"age":{"from":10,"include_lower":true,"include_upper":true,"to":20}}},"should":[{"term":{"tag":"sometag"}},{"term":{"tag":"sometagtag"}}]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_queries_boosting.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// A boosting query can be used to effectively
// demote results that match a given query.
// For more details, see:
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-boosting-query.html
type BoostingQuery struct {
Query
positiveClause Query
negativeClause Query
negativeBoost *float64
boost *float64
}
// Creates a new boosting query.
func NewBoostingQuery() *BoostingQuery {
return &BoostingQuery{}
}
func (q *BoostingQuery) Positive(positive Query) *BoostingQuery {
q.positiveClause = positive
return q
}
func (q *BoostingQuery) Negative(negative Query) *BoostingQuery {
q.negativeClause = negative
return q
}
func (q *BoostingQuery) NegativeBoost(negativeBoost float64) *BoostingQuery {
q.negativeBoost = &negativeBoost
return q
}
func (q *BoostingQuery) Boost(boost float64) *BoostingQuery {
q.boost = &boost
return q
}
// Creates the query source for the boosting query.
func (q *BoostingQuery) Source() (interface{}, error) {
// {
// "boosting" : {
// "positive" : {
// "term" : {
// "field1" : "value1"
// }
// },
// "negative" : {
// "term" : {
// "field2" : "value2"
// }
// },
// "negative_boost" : 0.2
// }
// }
query := make(map[string]interface{})
boostingClause := make(map[string]interface{})
query["boosting"] = boostingClause
// Negative and positive clause as well as negative boost
// are mandatory in the Java client.
// positive
if q.positiveClause != nil {
src, err := q.positiveClause.Source()
if err != nil {
return nil, err
}
boostingClause["positive"] = src
}
// negative
if q.negativeClause != nil {
src, err := q.negativeClause.Source()
if err != nil {
return nil, err
}
boostingClause["negative"] = src
}
if q.negativeBoost != nil {
boostingClause["negative_boost"] = *q.negativeBoost
}
if q.boost != nil {
boostingClause["boost"] = *q.boost
}
return query, nil
}
================================================
FILE: search_queries_boosting_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestBoostingQuery(t *testing.T) {
q := NewBoostingQuery()
q = q.Positive(NewTermQuery("tag", "wow"))
q = q.Negative(NewRangeQuery("age").From(10).To(20))
q = q.NegativeBoost(0.2)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"boosting":{"negative":{"range":{"age":{"from":10,"include_lower":true,"include_upper":true,"to":20}}},"negative_boost":0.2,"positive":{"term":{"tag":"wow"}}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_queries_combined_fields.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import "fmt"
// CombinedFieldsQuery supports searching multiple text fields as if their
// contents had been indexed into one combined field.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.13/query-dsl-combined-fields-query.html
type CombinedFieldsQuery struct {
text interface{}
fields []string
fieldBoosts map[string]*float64
autoGenerateSynonymsPhraseQuery *bool
operator string // AND or OR
minimumShouldMatch string
zeroTermsQuery string
}
// NewCombinedFieldsQuery creates and initializes a new CombinedFieldsQuery.
func NewCombinedFieldsQuery(text interface{}, fields ...string) *CombinedFieldsQuery {
q := &CombinedFieldsQuery{
text: text,
fieldBoosts: make(map[string]*float64),
}
q.fields = append(q.fields, fields...)
return q
}
// Field adds a field to run the multi match against.
func (q *CombinedFieldsQuery) Field(field string) *CombinedFieldsQuery {
q.fields = append(q.fields, field)
return q
}
// FieldWithBoost adds a field to run the multi match against with a specific boost.
func (q *CombinedFieldsQuery) FieldWithBoost(field string, boost float64) *CombinedFieldsQuery {
q.fields = append(q.fields, field)
q.fieldBoosts[field] = &boost
return q
}
// AutoGenerateSynonymsPhraseQuery indicates whether phrase queries should be
// automatically generated for multi terms synonyms. Defaults to true.
func (q *CombinedFieldsQuery) AutoGenerateSynonymsPhraseQuery(enable bool) *CombinedFieldsQuery {
q.autoGenerateSynonymsPhraseQuery = &enable
return q
}
// Operator sets the operator to use when using boolean query.
// It can be either AND or OR (default).
func (q *CombinedFieldsQuery) Operator(operator string) *CombinedFieldsQuery {
q.operator = operator
return q
}
// MinimumShouldMatch represents the minimum number of optional should clauses
// to match.
func (q *CombinedFieldsQuery) MinimumShouldMatch(minimumShouldMatch string) *CombinedFieldsQuery {
q.minimumShouldMatch = minimumShouldMatch
return q
}
// ZeroTermsQuery can be "all" or "none".
func (q *CombinedFieldsQuery) ZeroTermsQuery(zeroTermsQuery string) *CombinedFieldsQuery {
q.zeroTermsQuery = zeroTermsQuery
return q
}
// Source returns JSON for the query.
func (q *CombinedFieldsQuery) Source() (interface{}, error) {
source := make(map[string]interface{})
combinedFields := make(map[string]interface{})
source["combined_fields"] = combinedFields
combinedFields["query"] = q.text
fields := []string{}
for _, field := range q.fields {
if boost, found := q.fieldBoosts[field]; found {
if boost != nil {
fields = append(fields, fmt.Sprintf("%s^%f", field, *boost))
} else {
fields = append(fields, field)
}
} else {
fields = append(fields, field)
}
}
combinedFields["fields"] = fields
if q.autoGenerateSynonymsPhraseQuery != nil {
combinedFields["auto_generate_synonyms_phrase_query"] = q.autoGenerateSynonymsPhraseQuery
}
if q.operator != "" {
combinedFields["operator"] = q.operator
}
if q.minimumShouldMatch != "" {
combinedFields["minimum_should_match"] = q.minimumShouldMatch
}
if q.zeroTermsQuery != "" {
combinedFields["zero_terms_query"] = q.zeroTermsQuery
}
return source, nil
}
================================================
FILE: search_queries_combined_fields_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestCombinedFieldsQuery(t *testing.T) {
q := NewCombinedFieldsQuery("query text", "f1", "f2").
Field("f3").
FieldWithBoost("f4", 2.0).
AutoGenerateSynonymsPhraseQuery(false).
Operator("AND").
MinimumShouldMatch("3").
ZeroTermsQuery("all")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"combined_fields":{"auto_generate_synonyms_phrase_query":false,"fields":["f1","f2","f3","f4^2.000000"],"minimum_should_match":"3","operator":"AND","query":"query text","zero_terms_query":"all"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_queries_common_terms.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// CommonTermsQuery is a modern alternative to stopwords
// which improves the precision and recall of search results
// (by taking stopwords into account), without sacrificing performance.
// For more details, see:
// https://www.elastic.co/guide/en/elasticsearch/reference/7.4/query-dsl-common-terms-query.html
//
// Deprecated: Use Match query instead (7.3.0+), which skips blocks of
// documents efficiently, without any configuration, provided that the
// total number of hits is not tracked.
type CommonTermsQuery struct {
Query
name string
text interface{}
cutoffFreq *float64
highFreq *float64
highFreqOp string
highFreqMinimumShouldMatch string
lowFreq *float64
lowFreqOp string
lowFreqMinimumShouldMatch string
analyzer string
boost *float64
queryName string
}
// NewCommonTermsQuery creates and initializes a new common terms query.
//
// Deprecated: Common Terms Query was deprecated in >= 7.3.0. See https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-common-terms-query.html.
func NewCommonTermsQuery(name string, text interface{}) *CommonTermsQuery {
return &CommonTermsQuery{name: name, text: text}
}
func (q *CommonTermsQuery) CutoffFrequency(f float64) *CommonTermsQuery {
q.cutoffFreq = &f
return q
}
func (q *CommonTermsQuery) HighFreq(f float64) *CommonTermsQuery {
q.highFreq = &f
return q
}
func (q *CommonTermsQuery) HighFreqOperator(op string) *CommonTermsQuery {
q.highFreqOp = op
return q
}
func (q *CommonTermsQuery) HighFreqMinimumShouldMatch(minShouldMatch string) *CommonTermsQuery {
q.highFreqMinimumShouldMatch = minShouldMatch
return q
}
func (q *CommonTermsQuery) LowFreq(f float64) *CommonTermsQuery {
q.lowFreq = &f
return q
}
func (q *CommonTermsQuery) LowFreqOperator(op string) *CommonTermsQuery {
q.lowFreqOp = op
return q
}
func (q *CommonTermsQuery) LowFreqMinimumShouldMatch(minShouldMatch string) *CommonTermsQuery {
q.lowFreqMinimumShouldMatch = minShouldMatch
return q
}
func (q *CommonTermsQuery) Analyzer(analyzer string) *CommonTermsQuery {
q.analyzer = analyzer
return q
}
func (q *CommonTermsQuery) Boost(boost float64) *CommonTermsQuery {
q.boost = &boost
return q
}
func (q *CommonTermsQuery) QueryName(queryName string) *CommonTermsQuery {
q.queryName = queryName
return q
}
// Creates the query source for the common query.
func (q *CommonTermsQuery) Source() (interface{}, error) {
// {
// "common": {
// "body": {
// "query": "this is bonsai cool",
// "cutoff_frequency": 0.001
// }
// }
// }
source := make(map[string]interface{})
body := make(map[string]interface{})
query := make(map[string]interface{})
source["common"] = body
body[q.name] = query
query["query"] = q.text
if q.cutoffFreq != nil {
query["cutoff_frequency"] = *q.cutoffFreq
}
if q.highFreq != nil {
query["high_freq"] = *q.highFreq
}
if q.highFreqOp != "" {
query["high_freq_operator"] = q.highFreqOp
}
if q.lowFreq != nil {
query["low_freq"] = *q.lowFreq
}
if q.lowFreqOp != "" {
query["low_freq_operator"] = q.lowFreqOp
}
if q.lowFreqMinimumShouldMatch != "" || q.highFreqMinimumShouldMatch != "" {
mm := make(map[string]interface{})
if q.lowFreqMinimumShouldMatch != "" {
mm["low_freq"] = q.lowFreqMinimumShouldMatch
}
if q.highFreqMinimumShouldMatch != "" {
mm["high_freq"] = q.highFreqMinimumShouldMatch
}
query["minimum_should_match"] = mm
}
if q.analyzer != "" {
query["analyzer"] = q.analyzer
}
if q.boost != nil {
query["boost"] = *q.boost
}
if q.queryName != "" {
query["_name"] = q.queryName
}
return source, nil
}
================================================
FILE: search_queries_common_terms_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
_ "net/http"
"testing"
)
func TestCommonTermsQuery(t *testing.T) {
q := NewCommonTermsQuery("message", "Golang").CutoffFrequency(0.001)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"common":{"message":{"cutoff_frequency":0.001,"query":"Golang"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSearchQueriesCommonTermsQuery(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
// Deprecated in >= 7.3.0
// https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-common-terms-query.html
esversion, err := client.ElasticsearchVersion(DefaultURL)
if err != nil {
t.Fatal(err)
}
if esversion < "7.3.0" {
t.Skipf("Elasticsearch versions >= 7.3.0 deprecated Common Terms Query. "+
"See https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-common-terms-query.html. "+
"You are running Elasticsearch %v.", esversion)
}
tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
tweet2 := tweet{User: "olivere", Message: "Another unrelated topic."}
tweet3 := tweet{User: "sandrae", Message: "Cycling is fun."}
// Add all documents
_, err = client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("3").BodyJson(&tweet3).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Common terms query
q := NewCommonTermsQuery("message", "Golang")
searchResult, err := client.Search().Index(testIndexName).Query(q).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.Hits == nil {
t.Errorf("expected SearchResult.Hits != nil; got nil")
}
if searchResult.TotalHits() != 1 {
t.Errorf("expected SearchResult.TotalHits() = %d; got %d", 1, searchResult.TotalHits())
}
if len(searchResult.Hits.Hits) != 1 {
t.Errorf("expected len(SearchResult.Hits.Hits) = %d; got %d", 1, len(searchResult.Hits.Hits))
}
for _, hit := range searchResult.Hits.Hits {
if hit.Index != testIndexName {
t.Errorf("expected SearchResult.Hits.Hit.Index = %q; got %q", testIndexName, hit.Index)
}
item := make(map[string]interface{})
err := json.Unmarshal(hit.Source, &item)
if err != nil {
t.Fatal(err)
}
}
}
================================================
FILE: search_queries_constant_score.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// ConstantScoreQuery is a query that wraps a filter and simply returns
// a constant score equal to the query boost for every document in the filter.
//
// For more details, see:
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-constant-score-query.html
type ConstantScoreQuery struct {
filter Query
boost *float64
}
// ConstantScoreQuery creates and initializes a new constant score query.
func NewConstantScoreQuery(filter Query) *ConstantScoreQuery {
return &ConstantScoreQuery{
filter: filter,
}
}
// Boost sets the boost for this query. Documents matching this query
// will (in addition to the normal weightings) have their score multiplied
// by the boost provided.
func (q *ConstantScoreQuery) Boost(boost float64) *ConstantScoreQuery {
q.boost = &boost
return q
}
// Source returns the query source.
func (q *ConstantScoreQuery) Source() (interface{}, error) {
// "constant_score" : {
// "filter" : {
// ....
// },
// "boost" : 1.5
// }
query := make(map[string]interface{})
params := make(map[string]interface{})
query["constant_score"] = params
// filter
src, err := q.filter.Source()
if err != nil {
return nil, err
}
params["filter"] = src
// boost
if q.boost != nil {
params["boost"] = *q.boost
}
return query, nil
}
================================================
FILE: search_queries_constant_score_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestConstantScoreQuery(t *testing.T) {
q := NewConstantScoreQuery(NewTermQuery("user", "kimchy")).Boost(1.2)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"constant_score":{"boost":1.2,"filter":{"term":{"user":"kimchy"}}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_queries_dis_max.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// DisMaxQuery is a query that generates the union of documents produced by
// its subqueries, and that scores each document with the maximum score
// for that document as produced by any subquery, plus a tie breaking
// increment for any additional matching subqueries.
//
// For more details, see:
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-dis-max-query.html
type DisMaxQuery struct {
queries []Query
boost *float64
tieBreaker *float64
queryName string
}
// NewDisMaxQuery creates and initializes a new dis max query.
func NewDisMaxQuery() *DisMaxQuery {
return &DisMaxQuery{
queries: make([]Query, 0),
}
}
// Query adds one or more queries to the dis max query.
func (q *DisMaxQuery) Query(queries ...Query) *DisMaxQuery {
q.queries = append(q.queries, queries...)
return q
}
// Boost sets the boost for this query. Documents matching this query will
// (in addition to the normal weightings) have their score multiplied by
// the boost provided.
func (q *DisMaxQuery) Boost(boost float64) *DisMaxQuery {
q.boost = &boost
return q
}
// TieBreaker is the factor by which the score of each non-maximum disjunct
// for a document is multiplied with and added into the final score.
//
// If non-zero, the value should be small, on the order of 0.1, which says
// that 10 occurrences of word in a lower-scored field that is also in a
// higher scored field is just as good as a unique word in the lower scored
// field (i.e., one that is not in any higher scored field).
func (q *DisMaxQuery) TieBreaker(tieBreaker float64) *DisMaxQuery {
q.tieBreaker = &tieBreaker
return q
}
// QueryName sets the query name for the filter that can be used
// when searching for matched filters per hit.
func (q *DisMaxQuery) QueryName(queryName string) *DisMaxQuery {
q.queryName = queryName
return q
}
// Source returns the JSON serializable content for this query.
func (q *DisMaxQuery) Source() (interface{}, error) {
// {
// "dis_max" : {
// "tie_breaker" : 0.7,
// "boost" : 1.2,
// "queries" : {
// {
// "term" : { "age" : 34 }
// },
// {
// "term" : { "age" : 35 }
// }
// ]
// }
// }
query := make(map[string]interface{})
params := make(map[string]interface{})
query["dis_max"] = params
if q.tieBreaker != nil {
params["tie_breaker"] = *q.tieBreaker
}
if q.boost != nil {
params["boost"] = *q.boost
}
if q.queryName != "" {
params["_name"] = q.queryName
}
// queries
var clauses []interface{}
for _, subQuery := range q.queries {
src, err := subQuery.Source()
if err != nil {
return nil, err
}
clauses = append(clauses, src)
}
params["queries"] = clauses
return query, nil
}
================================================
FILE: search_queries_dis_max_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestDisMaxQuery(t *testing.T) {
q := NewDisMaxQuery()
q = q.Query(NewTermQuery("age", 34), NewTermQuery("age", 35)).Boost(1.2).TieBreaker(0.7)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"dis_max":{"boost":1.2,"queries":[{"term":{"age":34}},{"term":{"age":35}}],"tie_breaker":0.7}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_queries_distance_feature_query.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"fmt"
)
// DistanceFeatureQuery uses a script to provide a custom score for returned documents.
//
// A DistanceFeatureQuery query is useful if, for example, a scoring function is
// expensive and you only need to calculate the score of a filtered set of documents.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.4/query-dsl-distance-feature-query.html
type DistanceFeatureQuery struct {
field string
pivot string
origin interface{}
boost *float64
queryName string
}
// NewDistanceFeatureQuery creates and initializes a new script_score query.
func NewDistanceFeatureQuery(field string, origin interface{}, pivot string) *DistanceFeatureQuery {
return &DistanceFeatureQuery{
field: field,
origin: origin,
pivot: pivot,
}
}
// Field to be used in the DistanceFeatureQuery.
func (q *DistanceFeatureQuery) Field(name string) *DistanceFeatureQuery {
q.field = name
return q
}
// Origin is the date or point of origin used to calculate distances.
//
// If the field is a date or date_nanos field, the origin value must be a
// date. Date math such as "now-1h" is supported.
//
// If the field is a geo_point field, the origin must be a GeoPoint.
func (q *DistanceFeatureQuery) Origin(origin interface{}) *DistanceFeatureQuery {
q.origin = origin
return q
}
// Pivot is distance from the origin at which relevance scores
// receive half of the boost value.
//
// If field is a date or date_nanos field, the pivot value must be a time
// unit, such as "1h" or "10d".
//
// If field is a geo_point field, the pivot value must be a distance unit,
// such as "1km" or "12m". You can pass a string, or a GeoPoint.
func (q *DistanceFeatureQuery) Pivot(pivot string) *DistanceFeatureQuery {
q.pivot = pivot
return q
}
// Boost sets the boost for this query.
func (q *DistanceFeatureQuery) Boost(boost float64) *DistanceFeatureQuery {
q.boost = &boost
return q
}
// QueryName sets the query name for the filter.
func (q *DistanceFeatureQuery) QueryName(queryName string) *DistanceFeatureQuery {
q.queryName = queryName
return q
}
// Source returns JSON for the function score query.
func (q *DistanceFeatureQuery) Source() (interface{}, error) {
// {
// "distance_feature" : {
// "field" : "production_date",
// "pivot" : "7d",
// "origin" : "now"
// }
// }
// {
// "distance_feature" : {
// "field" : "location",
// "pivot" : "1000m",
// "origin" : [-71.3, 41.15]
// }
// }
source := make(map[string]interface{})
query := make(map[string]interface{})
source["distance_feature"] = query
query["field"] = q.field
query["pivot"] = q.pivot
switch v := q.origin.(type) {
default:
return nil, fmt.Errorf("DistanceFeatureQuery: unable to serialize Origin from type %T", v)
case string:
query["origin"] = v
case *GeoPoint:
query["origin"] = v.Source()
case GeoPoint:
query["origin"] = v.Source()
}
if v := q.boost; v != nil {
query["boost"] = *v
}
if q.queryName != "" {
query["_name"] = q.queryName
}
return source, nil
}
================================================
FILE: search_queries_distance_feature_query_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"testing"
)
func TestDistanceFeatureQueryForDateField(t *testing.T) {
q := NewDistanceFeatureQuery("production_date", "now", "7d")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"distance_feature":{"field":"production_date","origin":"now","pivot":"7d"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestDistanceFeatureQueryForGeoField(t *testing.T) {
q := NewDistanceFeatureQuery("location", GeoPointFromLatLon(-71.3, 41.15), "1000m")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"distance_feature":{"field":"location","origin":{"lat":-71.3,"lon":41.15},"pivot":"1000m"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestDistanceFeatureQueryIntegration(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t) //, SetTraceLog(log.New(os.Stdout, "", 0)))
res, err := client.Search().
Index(testOrderIndex).
Query(
NewDistanceFeatureQuery("time", "now", "7d"),
).
Pretty(true).
Do(context.Background())
if err != nil {
t.Fatal(err)
}
if res.Hits == nil {
t.Errorf("expected Hits != nil; got nil")
}
if want, have := int64(8), res.TotalHits(); want != have {
t.Errorf("expected TotalHits() = %d; got %d", want, have)
}
if want, have := 8, len(res.Hits.Hits); want != have {
t.Errorf("expected len(Hits.Hits) = %d; got %d", want, have)
}
}
================================================
FILE: search_queries_exists.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// ExistsQuery is a query that only matches on documents that the field
// has a value in them.
//
// For more details, see:
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-exists-query.html
type ExistsQuery struct {
name string
queryName string
}
// NewExistsQuery creates and initializes a new exists query.
func NewExistsQuery(name string) *ExistsQuery {
return &ExistsQuery{
name: name,
}
}
// QueryName sets the query name for the filter that can be used
// when searching for matched queries per hit.
func (q *ExistsQuery) QueryName(queryName string) *ExistsQuery {
q.queryName = queryName
return q
}
// Source returns the JSON serializable content for this query.
func (q *ExistsQuery) Source() (interface{}, error) {
// {
// "exists" : {
// "field" : "user"
// }
// }
query := make(map[string]interface{})
params := make(map[string]interface{})
query["exists"] = params
params["field"] = q.name
if q.queryName != "" {
params["_name"] = q.queryName
}
return query, nil
}
================================================
FILE: search_queries_exists_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestExistsQuery(t *testing.T) {
q := NewExistsQuery("user")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"exists":{"field":"user"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_queries_fsq.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// FunctionScoreQuery allows you to modify the score of documents that
// are retrieved by a query. This can be useful if, for example,
// a score function is computationally expensive and it is sufficient
// to compute the score on a filtered set of documents.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-function-score-query.html
type FunctionScoreQuery struct {
query Query
filter Query
boost *float64
maxBoost *float64
scoreMode string
boostMode string
filters []Query
scoreFuncs []ScoreFunction
minScore *float64
}
// NewFunctionScoreQuery creates and initializes a new function score query.
func NewFunctionScoreQuery() *FunctionScoreQuery {
return &FunctionScoreQuery{
filters: make([]Query, 0),
scoreFuncs: make([]ScoreFunction, 0),
}
}
// Query sets the query for the function score query.
func (q *FunctionScoreQuery) Query(query Query) *FunctionScoreQuery {
q.query = query
return q
}
// Filter sets the filter for the function score query.
func (q *FunctionScoreQuery) Filter(filter Query) *FunctionScoreQuery {
q.filter = filter
return q
}
// Add adds a score function that will execute on all the documents
// matching the filter.
func (q *FunctionScoreQuery) Add(filter Query, scoreFunc ScoreFunction) *FunctionScoreQuery {
q.filters = append(q.filters, filter)
q.scoreFuncs = append(q.scoreFuncs, scoreFunc)
return q
}
// AddScoreFunc adds a score function that will execute the function on all documents.
func (q *FunctionScoreQuery) AddScoreFunc(scoreFunc ScoreFunction) *FunctionScoreQuery {
q.filters = append(q.filters, nil)
q.scoreFuncs = append(q.scoreFuncs, scoreFunc)
return q
}
// ScoreMode defines how results of individual score functions will be aggregated.
// Can be first, avg, max, sum, min, or multiply.
func (q *FunctionScoreQuery) ScoreMode(scoreMode string) *FunctionScoreQuery {
q.scoreMode = scoreMode
return q
}
// BoostMode defines how the combined result of score functions will
// influence the final score together with the sub query score.
func (q *FunctionScoreQuery) BoostMode(boostMode string) *FunctionScoreQuery {
q.boostMode = boostMode
return q
}
// MaxBoost is the maximum boost that will be applied by function score.
func (q *FunctionScoreQuery) MaxBoost(maxBoost float64) *FunctionScoreQuery {
q.maxBoost = &maxBoost
return q
}
// Boost sets the boost for this query. Documents matching this query will
// (in addition to the normal weightings) have their score multiplied by the
// boost provided.
func (q *FunctionScoreQuery) Boost(boost float64) *FunctionScoreQuery {
q.boost = &boost
return q
}
// MinScore sets the minimum score.
func (q *FunctionScoreQuery) MinScore(minScore float64) *FunctionScoreQuery {
q.minScore = &minScore
return q
}
// Source returns JSON for the function score query.
func (q *FunctionScoreQuery) Source() (interface{}, error) {
source := make(map[string]interface{})
query := make(map[string]interface{})
source["function_score"] = query
if q.query != nil {
src, err := q.query.Source()
if err != nil {
return nil, err
}
query["query"] = src
}
if q.filter != nil {
src, err := q.filter.Source()
if err != nil {
return nil, err
}
query["filter"] = src
}
if len(q.filters) > 0 {
funcs := make([]interface{}, len(q.filters))
for i, filter := range q.filters {
hsh := make(map[string]interface{})
if filter != nil {
src, err := filter.Source()
if err != nil {
return nil, err
}
hsh["filter"] = src
}
// Weight needs to be serialized on this level.
if weight := q.scoreFuncs[i].GetWeight(); weight != nil {
hsh["weight"] = weight
}
// Serialize the score function
src, err := q.scoreFuncs[i].Source()
if err != nil {
return nil, err
}
hsh[q.scoreFuncs[i].Name()] = src
funcs[i] = hsh
}
query["functions"] = funcs
}
if q.scoreMode != "" {
query["score_mode"] = q.scoreMode
}
if q.boostMode != "" {
query["boost_mode"] = q.boostMode
}
if q.maxBoost != nil {
query["max_boost"] = *q.maxBoost
}
if q.boost != nil {
query["boost"] = *q.boost
}
if q.minScore != nil {
query["min_score"] = *q.minScore
}
return source, nil
}
================================================
FILE: search_queries_fsq_score_funcs.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"strings"
)
// ScoreFunction is used in combination with the Function Score Query.
type ScoreFunction interface {
Name() string
GetWeight() *float64 // returns the weight which must be serialized at the level of FunctionScoreQuery
Source() (interface{}, error)
}
// -- Exponential Decay --
// ExponentialDecayFunction builds an exponential decay score function.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-function-score-query.html
// for details.
type ExponentialDecayFunction struct {
fieldName string
origin interface{}
scale interface{}
decay *float64
offset interface{}
multiValueMode string
weight *float64
}
// NewExponentialDecayFunction creates a new ExponentialDecayFunction.
func NewExponentialDecayFunction() *ExponentialDecayFunction {
return &ExponentialDecayFunction{}
}
// Name represents the JSON field name under which the output of Source
// needs to be serialized by FunctionScoreQuery (see FunctionScoreQuery.Source).
func (fn *ExponentialDecayFunction) Name() string {
return "exp"
}
// FieldName specifies the name of the field to which this decay function is applied to.
func (fn *ExponentialDecayFunction) FieldName(fieldName string) *ExponentialDecayFunction {
fn.fieldName = fieldName
return fn
}
// Origin defines the "central point" by which the decay function calculates
// "distance".
func (fn *ExponentialDecayFunction) Origin(origin interface{}) *ExponentialDecayFunction {
fn.origin = origin
return fn
}
// Scale defines the scale to be used with Decay.
func (fn *ExponentialDecayFunction) Scale(scale interface{}) *ExponentialDecayFunction {
fn.scale = scale
return fn
}
// Decay defines how documents are scored at the distance given a Scale.
// If no decay is defined, documents at the distance Scale will be scored 0.5.
func (fn *ExponentialDecayFunction) Decay(decay float64) *ExponentialDecayFunction {
fn.decay = &decay
return fn
}
// Offset, if defined, computes the decay function only for a distance
// greater than the defined offset.
func (fn *ExponentialDecayFunction) Offset(offset interface{}) *ExponentialDecayFunction {
fn.offset = offset
return fn
}
// Weight adjusts the score of the score function.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-function-score-query.html#_using_function_score
// for details.
func (fn *ExponentialDecayFunction) Weight(weight float64) *ExponentialDecayFunction {
fn.weight = &weight
return fn
}
// GetWeight returns the adjusted score. It is part of the ScoreFunction interface.
// Returns nil if weight is not specified.
func (fn *ExponentialDecayFunction) GetWeight() *float64 {
return fn.weight
}
// MultiValueMode specifies how the decay function should be calculated
// on a field that has multiple values.
// Valid modes are: min, max, avg, and sum.
func (fn *ExponentialDecayFunction) MultiValueMode(mode string) *ExponentialDecayFunction {
fn.multiValueMode = mode
return fn
}
// Source returns the serializable JSON data of this score function.
func (fn *ExponentialDecayFunction) Source() (interface{}, error) {
source := make(map[string]interface{})
params := make(map[string]interface{})
source[fn.fieldName] = params
if fn.origin != nil {
params["origin"] = fn.origin
}
params["scale"] = fn.scale
if fn.decay != nil && *fn.decay > 0 {
params["decay"] = *fn.decay
}
if fn.offset != nil {
params["offset"] = fn.offset
}
if fn.multiValueMode != "" {
source["multi_value_mode"] = fn.multiValueMode
}
return source, nil
}
// -- Gauss Decay --
// GaussDecayFunction builds a gauss decay score function.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-function-score-query.html
// for details.
type GaussDecayFunction struct {
fieldName string
origin interface{}
scale interface{}
decay *float64
offset interface{}
multiValueMode string
weight *float64
}
// NewGaussDecayFunction returns a new GaussDecayFunction.
func NewGaussDecayFunction() *GaussDecayFunction {
return &GaussDecayFunction{}
}
// Name represents the JSON field name under which the output of Source
// needs to be serialized by FunctionScoreQuery (see FunctionScoreQuery.Source).
func (fn *GaussDecayFunction) Name() string {
return "gauss"
}
// FieldName specifies the name of the field to which this decay function is applied to.
func (fn *GaussDecayFunction) FieldName(fieldName string) *GaussDecayFunction {
fn.fieldName = fieldName
return fn
}
// Origin defines the "central point" by which the decay function calculates
// "distance".
func (fn *GaussDecayFunction) Origin(origin interface{}) *GaussDecayFunction {
fn.origin = origin
return fn
}
// Scale defines the scale to be used with Decay.
func (fn *GaussDecayFunction) Scale(scale interface{}) *GaussDecayFunction {
fn.scale = scale
return fn
}
// Decay defines how documents are scored at the distance given a Scale.
// If no decay is defined, documents at the distance Scale will be scored 0.5.
func (fn *GaussDecayFunction) Decay(decay float64) *GaussDecayFunction {
fn.decay = &decay
return fn
}
// Offset, if defined, computes the decay function only for a distance
// greater than the defined offset.
func (fn *GaussDecayFunction) Offset(offset interface{}) *GaussDecayFunction {
fn.offset = offset
return fn
}
// Weight adjusts the score of the score function.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-function-score-query.html#_using_function_score
// for details.
func (fn *GaussDecayFunction) Weight(weight float64) *GaussDecayFunction {
fn.weight = &weight
return fn
}
// GetWeight returns the adjusted score. It is part of the ScoreFunction interface.
// Returns nil if weight is not specified.
func (fn *GaussDecayFunction) GetWeight() *float64 {
return fn.weight
}
// MultiValueMode specifies how the decay function should be calculated
// on a field that has multiple values.
// Valid modes are: min, max, avg, and sum.
func (fn *GaussDecayFunction) MultiValueMode(mode string) *GaussDecayFunction {
fn.multiValueMode = mode
return fn
}
// Source returns the serializable JSON data of this score function.
func (fn *GaussDecayFunction) Source() (interface{}, error) {
source := make(map[string]interface{})
params := make(map[string]interface{})
source[fn.fieldName] = params
if fn.origin != nil {
params["origin"] = fn.origin
}
params["scale"] = fn.scale
if fn.decay != nil && *fn.decay > 0 {
params["decay"] = *fn.decay
}
if fn.offset != nil {
params["offset"] = fn.offset
}
if fn.multiValueMode != "" {
source["multi_value_mode"] = fn.multiValueMode
}
// Notice that the weight has to be serialized in FunctionScoreQuery.
return source, nil
}
// -- Linear Decay --
// LinearDecayFunction builds a linear decay score function.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-function-score-query.html
// for details.
type LinearDecayFunction struct {
fieldName string
origin interface{}
scale interface{}
decay *float64
offset interface{}
multiValueMode string
weight *float64
}
// NewLinearDecayFunction initializes and returns a new LinearDecayFunction.
func NewLinearDecayFunction() *LinearDecayFunction {
return &LinearDecayFunction{}
}
// Name represents the JSON field name under which the output of Source
// needs to be serialized by FunctionScoreQuery (see FunctionScoreQuery.Source).
func (fn *LinearDecayFunction) Name() string {
return "linear"
}
// FieldName specifies the name of the field to which this decay function is applied to.
func (fn *LinearDecayFunction) FieldName(fieldName string) *LinearDecayFunction {
fn.fieldName = fieldName
return fn
}
// Origin defines the "central point" by which the decay function calculates
// "distance".
func (fn *LinearDecayFunction) Origin(origin interface{}) *LinearDecayFunction {
fn.origin = origin
return fn
}
// Scale defines the scale to be used with Decay.
func (fn *LinearDecayFunction) Scale(scale interface{}) *LinearDecayFunction {
fn.scale = scale
return fn
}
// Decay defines how documents are scored at the distance given a Scale.
// If no decay is defined, documents at the distance Scale will be scored 0.5.
func (fn *LinearDecayFunction) Decay(decay float64) *LinearDecayFunction {
fn.decay = &decay
return fn
}
// Offset, if defined, computes the decay function only for a distance
// greater than the defined offset.
func (fn *LinearDecayFunction) Offset(offset interface{}) *LinearDecayFunction {
fn.offset = offset
return fn
}
// Weight adjusts the score of the score function.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-function-score-query.html#_using_function_score
// for details.
func (fn *LinearDecayFunction) Weight(weight float64) *LinearDecayFunction {
fn.weight = &weight
return fn
}
// GetWeight returns the adjusted score. It is part of the ScoreFunction interface.
// Returns nil if weight is not specified.
func (fn *LinearDecayFunction) GetWeight() *float64 {
return fn.weight
}
// MultiValueMode specifies how the decay function should be calculated
// on a field that has multiple values.
// Valid modes are: min, max, avg, and sum.
func (fn *LinearDecayFunction) MultiValueMode(mode string) *LinearDecayFunction {
fn.multiValueMode = mode
return fn
}
// GetMultiValueMode returns how the decay function should be calculated
// on a field that has multiple values.
// Valid modes are: min, max, avg, and sum.
func (fn *LinearDecayFunction) GetMultiValueMode() string {
return fn.multiValueMode
}
// Source returns the serializable JSON data of this score function.
func (fn *LinearDecayFunction) Source() (interface{}, error) {
source := make(map[string]interface{})
params := make(map[string]interface{})
source[fn.fieldName] = params
if fn.origin != nil {
params["origin"] = fn.origin
}
params["scale"] = fn.scale
if fn.decay != nil && *fn.decay > 0 {
params["decay"] = *fn.decay
}
if fn.offset != nil {
params["offset"] = fn.offset
}
if fn.multiValueMode != "" {
source["multi_value_mode"] = fn.multiValueMode
}
// Notice that the weight has to be serialized in FunctionScoreQuery.
return source, nil
}
// -- Script --
// ScriptFunction builds a script score function. It uses a script to
// compute or influence the score of documents that match with the inner
// query or filter.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-function-score-query.html#_script_score
// for details.
type ScriptFunction struct {
script *Script
weight *float64
}
// NewScriptFunction initializes and returns a new ScriptFunction.
func NewScriptFunction(script *Script) *ScriptFunction {
return &ScriptFunction{
script: script,
}
}
// Name represents the JSON field name under which the output of Source
// needs to be serialized by FunctionScoreQuery (see FunctionScoreQuery.Source).
func (fn *ScriptFunction) Name() string {
return "script_score"
}
// Script specifies the script to be executed.
func (fn *ScriptFunction) Script(script *Script) *ScriptFunction {
fn.script = script
return fn
}
// Weight adjusts the score of the score function.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-function-score-query.html#_using_function_score
// for details.
func (fn *ScriptFunction) Weight(weight float64) *ScriptFunction {
fn.weight = &weight
return fn
}
// GetWeight returns the adjusted score. It is part of the ScoreFunction interface.
// Returns nil if weight is not specified.
func (fn *ScriptFunction) GetWeight() *float64 {
return fn.weight
}
// Source returns the serializable JSON data of this score function.
func (fn *ScriptFunction) Source() (interface{}, error) {
source := make(map[string]interface{})
if fn.script != nil {
src, err := fn.script.Source()
if err != nil {
return nil, err
}
source["script"] = src
}
// Notice that the weight has to be serialized in FunctionScoreQuery.
return source, nil
}
// -- Field value factor --
// FieldValueFactorFunction is a function score function that allows you
// to use a field from a document to influence the score.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-function-score-query.html#_field_value_factor.
type FieldValueFactorFunction struct {
field string
factor *float64
missing *float64
weight *float64
modifier string
}
// NewFieldValueFactorFunction initializes and returns a new FieldValueFactorFunction.
func NewFieldValueFactorFunction() *FieldValueFactorFunction {
return &FieldValueFactorFunction{}
}
// Name represents the JSON field name under which the output of Source
// needs to be serialized by FunctionScoreQuery (see FunctionScoreQuery.Source).
func (fn *FieldValueFactorFunction) Name() string {
return "field_value_factor"
}
// Field is the field to be extracted from the document.
func (fn *FieldValueFactorFunction) Field(field string) *FieldValueFactorFunction {
fn.field = field
return fn
}
// Factor is the (optional) factor to multiply the field with. If you do not
// specify a factor, the default is 1.
func (fn *FieldValueFactorFunction) Factor(factor float64) *FieldValueFactorFunction {
fn.factor = &factor
return fn
}
// Modifier to apply to the field value. It can be one of: none, log, log1p,
// log2p, ln, ln1p, ln2p, square, sqrt, or reciprocal. Defaults to: none.
func (fn *FieldValueFactorFunction) Modifier(modifier string) *FieldValueFactorFunction {
fn.modifier = modifier
return fn
}
// Weight adjusts the score of the score function.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-function-score-query.html#_using_function_score
// for details.
func (fn *FieldValueFactorFunction) Weight(weight float64) *FieldValueFactorFunction {
fn.weight = &weight
return fn
}
// GetWeight returns the adjusted score. It is part of the ScoreFunction interface.
// Returns nil if weight is not specified.
func (fn *FieldValueFactorFunction) GetWeight() *float64 {
return fn.weight
}
// Missing is used if a document does not have that field.
func (fn *FieldValueFactorFunction) Missing(missing float64) *FieldValueFactorFunction {
fn.missing = &missing
return fn
}
// Source returns the serializable JSON data of this score function.
func (fn *FieldValueFactorFunction) Source() (interface{}, error) {
source := make(map[string]interface{})
if fn.field != "" {
source["field"] = fn.field
}
if fn.factor != nil {
source["factor"] = *fn.factor
}
if fn.missing != nil {
source["missing"] = *fn.missing
}
if fn.modifier != "" {
source["modifier"] = strings.ToLower(fn.modifier)
}
// Notice that the weight has to be serialized in FunctionScoreQuery.
return source, nil
}
// -- Weight Factor --
// WeightFactorFunction builds a weight factor function that multiplies
// the weight to the score.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-function-score-query.html#_weight
// for details.
type WeightFactorFunction struct {
weight float64
}
// NewWeightFactorFunction initializes and returns a new WeightFactorFunction.
func NewWeightFactorFunction(weight float64) *WeightFactorFunction {
return &WeightFactorFunction{weight: weight}
}
// Name represents the JSON field name under which the output of Source
// needs to be serialized by FunctionScoreQuery (see FunctionScoreQuery.Source).
func (fn *WeightFactorFunction) Name() string {
return "weight"
}
// Weight adjusts the score of the score function.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-function-score-query.html#_using_function_score
// for details.
func (fn *WeightFactorFunction) Weight(weight float64) *WeightFactorFunction {
fn.weight = weight
return fn
}
// GetWeight returns the adjusted score. It is part of the ScoreFunction interface.
// Returns nil if weight is not specified.
func (fn *WeightFactorFunction) GetWeight() *float64 {
return &fn.weight
}
// Source returns the serializable JSON data of this score function.
func (fn *WeightFactorFunction) Source() (interface{}, error) {
// Notice that the weight has to be serialized in FunctionScoreQuery.
return fn.weight, nil
}
// -- Random --
// RandomFunction builds a random score function.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-function-score-query.html#_random
// for details.
type RandomFunction struct {
field string
seed interface{}
weight *float64
}
// NewRandomFunction initializes and returns a new RandomFunction.
func NewRandomFunction() *RandomFunction {
return &RandomFunction{}
}
// Name represents the JSON field name under which the output of Source
// needs to be serialized by FunctionScoreQuery (see FunctionScoreQuery.Source).
func (fn *RandomFunction) Name() string {
return "random_score"
}
// Field is the field to be used for random number generation.
// This parameter is compulsory when a Seed is set and ignored
// otherwise. Note that documents that have the same value for a
// field will get the same score.
func (fn *RandomFunction) Field(field string) *RandomFunction {
fn.field = field
return fn
}
// Seed sets the seed based on which the random number will be generated.
// Using the same seed is guaranteed to generate the same random number for a specific doc.
// Seed must be an integer, e.g. int or int64. It is specified as an interface{}
// here for compatibility with older versions (which also accepted strings).
func (fn *RandomFunction) Seed(seed interface{}) *RandomFunction {
fn.seed = seed
return fn
}
// Weight adjusts the score of the score function.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-function-score-query.html#_using_function_score
// for details.
func (fn *RandomFunction) Weight(weight float64) *RandomFunction {
fn.weight = &weight
return fn
}
// GetWeight returns the adjusted score. It is part of the ScoreFunction interface.
// Returns nil if weight is not specified.
func (fn *RandomFunction) GetWeight() *float64 {
return fn.weight
}
// Source returns the serializable JSON data of this score function.
func (fn *RandomFunction) Source() (interface{}, error) {
source := make(map[string]interface{})
if fn.field != "" {
source["field"] = fn.field
}
if fn.seed != nil {
source["seed"] = fn.seed
}
// Notice that the weight has to be serialized in FunctionScoreQuery.
return source, nil
}
================================================
FILE: search_queries_fsq_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestFunctionScoreQuery(t *testing.T) {
q := NewFunctionScoreQuery().
Query(NewTermQuery("name.last", "banon")).
Add(NewTermQuery("name.last", "banon"), NewWeightFactorFunction(1.5)).
AddScoreFunc(NewWeightFactorFunction(3)).
AddScoreFunc(NewRandomFunction().Field("_seq_no").Seed(10)).
Boost(3).
MaxBoost(10).
ScoreMode("avg")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"function_score":{"boost":3,"functions":[{"filter":{"term":{"name.last":"banon"}},"weight":1.5},{"weight":3},{"random_score":{"field":"_seq_no","seed":10}}],"max_boost":10,"query":{"term":{"name.last":"banon"}},"score_mode":"avg"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestFunctionScoreQueryWithNilFilter(t *testing.T) {
q := NewFunctionScoreQuery().
Query(NewTermQuery("tag", "wow")).
AddScoreFunc(NewRandomFunction()).
Boost(2.0).
MaxBoost(12.0).
BoostMode("multiply").
ScoreMode("max")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"function_score":{"boost":2,"boost_mode":"multiply","functions":[{"random_score":{}}],"max_boost":12,"query":{"term":{"tag":"wow"}},"score_mode":"max"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestFieldValueFactor(t *testing.T) {
q := NewFunctionScoreQuery().
Query(NewTermQuery("name.last", "banon")).
AddScoreFunc(NewFieldValueFactorFunction().Modifier("sqrt").Factor(2).Field("income")).
Boost(2.0).
MaxBoost(12.0).
BoostMode("multiply").
ScoreMode("max")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"function_score":{"boost":2,"boost_mode":"multiply","functions":[{"field_value_factor":{"factor":2,"field":"income","modifier":"sqrt"}}],"max_boost":12,"query":{"term":{"name.last":"banon"}},"score_mode":"max"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestFieldValueFactorWithWeight(t *testing.T) {
q := NewFunctionScoreQuery().
Query(NewTermQuery("name.last", "banon")).
AddScoreFunc(NewFieldValueFactorFunction().Modifier("sqrt").Factor(2).Field("income").Weight(2.5)).
Boost(2.0).
MaxBoost(12.0).
BoostMode("multiply").
ScoreMode("max")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"function_score":{"boost":2,"boost_mode":"multiply","functions":[{"field_value_factor":{"factor":2,"field":"income","modifier":"sqrt"},"weight":2.5}],"max_boost":12,"query":{"term":{"name.last":"banon"}},"score_mode":"max"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestFieldValueFactorWithMultipleScoreFuncsAndWeights(t *testing.T) {
q := NewFunctionScoreQuery().
Query(NewTermQuery("name.last", "banon")).
AddScoreFunc(NewFieldValueFactorFunction().Modifier("sqrt").Factor(2).Field("income").Weight(2.5)).
AddScoreFunc(NewScriptFunction(NewScript("_score * doc['my_numeric_field'].value")).Weight(1.25)).
AddScoreFunc(NewWeightFactorFunction(0.5)).
Boost(2.0).
MaxBoost(12.0).
BoostMode("multiply").
ScoreMode("max")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"function_score":{"boost":2,"boost_mode":"multiply","functions":[{"field_value_factor":{"factor":2,"field":"income","modifier":"sqrt"},"weight":2.5},{"script_score":{"script":{"source":"_score * doc['my_numeric_field'].value"}},"weight":1.25},{"weight":0.5}],"max_boost":12,"query":{"term":{"name.last":"banon"}},"score_mode":"max"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestFunctionScoreQueryWithGaussScoreFunc(t *testing.T) {
q := NewFunctionScoreQuery().
Query(NewTermQuery("name.last", "banon")).
AddScoreFunc(NewGaussDecayFunction().FieldName("pin.location").Origin("11, 12").Scale("2km").Offset("0km").Decay(0.33))
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"function_score":{"functions":[{"gauss":{"pin.location":{"decay":0.33,"offset":"0km","origin":"11, 12","scale":"2km"}}}],"query":{"term":{"name.last":"banon"}}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestFunctionScoreQueryWithGaussScoreFuncAndMultiValueMode(t *testing.T) {
q := NewFunctionScoreQuery().
Query(NewTermQuery("name.last", "banon")).
AddScoreFunc(NewGaussDecayFunction().FieldName("pin.location").Origin("11, 12").Scale("2km").Offset("0km").Decay(0.33).MultiValueMode("avg"))
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"function_score":{"functions":[{"gauss":{"multi_value_mode":"avg","pin.location":{"decay":0.33,"offset":"0km","origin":"11, 12","scale":"2km"}}}],"query":{"term":{"name.last":"banon"}}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestFunctionScoreQueryWithFilters(t *testing.T) {
q := NewFunctionScoreQuery().
Add(NewTermQuery("features", "wifi"), NewWeightFactorFunction(1)).
Add(NewTermQuery("features", "garden"), NewWeightFactorFunction(1)).
Add(NewTermQuery("features", "pool"), NewWeightFactorFunction(2)).
ScoreMode("sum")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"function_score":{"functions":[{"filter":{"term":{"features":"wifi"}},"weight":1},{"filter":{"term":{"features":"garden"}},"weight":1},{"filter":{"term":{"features":"pool"}},"weight":2}],"score_mode":"sum"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestFunctionScoreQueryBug660(t *testing.T) {
q := NewFunctionScoreQuery().
Query(NewBoolQuery().Filter(NewTermsQuery("prod", 8199))).
AddScoreFunc(
NewGaussDecayFunction().
FieldName("update").
Origin(1501747403).
Scale(1209600).
Offset("259200").
Decay(0.6).
Weight(1),
).
BoostMode("sum").
ScoreMode("sum")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"function_score":{"boost_mode":"sum","functions":[{"gauss":{"update":{"decay":0.6,"offset":"259200","origin":1501747403,"scale":1209600}},"weight":1}],"query":{"bool":{"filter":{"terms":{"prod":[8199]}}}},"score_mode":"sum"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_queries_fuzzy.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// FuzzyQuery uses similarity based on Levenshtein edit distance for
// string fields, and a +/- margin on numeric and date fields.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-fuzzy-query.html
type FuzzyQuery struct {
name string
value interface{}
boost *float64
fuzziness interface{}
prefixLength *int
maxExpansions *int
transpositions *bool
rewrite string
queryName string
}
// NewFuzzyQuery creates a new fuzzy query.
func NewFuzzyQuery(name string, value interface{}) *FuzzyQuery {
q := &FuzzyQuery{
name: name,
value: value,
}
return q
}
// Boost sets the boost for this query. Documents matching this query will
// (in addition to the normal weightings) have their score multiplied by
// the boost provided.
func (q *FuzzyQuery) Boost(boost float64) *FuzzyQuery {
q.boost = &boost
return q
}
// Fuzziness can be an integer/long like 0, 1 or 2 as well as strings
// like "auto", "0..1", "1..4" or "0.0..1.0".
func (q *FuzzyQuery) Fuzziness(fuzziness interface{}) *FuzzyQuery {
q.fuzziness = fuzziness
return q
}
func (q *FuzzyQuery) PrefixLength(prefixLength int) *FuzzyQuery {
q.prefixLength = &prefixLength
return q
}
func (q *FuzzyQuery) MaxExpansions(maxExpansions int) *FuzzyQuery {
q.maxExpansions = &maxExpansions
return q
}
func (q *FuzzyQuery) Transpositions(transpositions bool) *FuzzyQuery {
q.transpositions = &transpositions
return q
}
func (q *FuzzyQuery) Rewrite(rewrite string) *FuzzyQuery {
q.rewrite = rewrite
return q
}
// QueryName sets the query name for the filter that can be used when
// searching for matched filters per hit.
func (q *FuzzyQuery) QueryName(queryName string) *FuzzyQuery {
q.queryName = queryName
return q
}
// Source returns JSON for the function score query.
func (q *FuzzyQuery) Source() (interface{}, error) {
// {
// "fuzzy" : {
// "user" : {
// "value" : "ki",
// "boost" : 1.0,
// "fuzziness" : 2,
// "prefix_length" : 0,
// "max_expansions" : 100
// }
// }
source := make(map[string]interface{})
query := make(map[string]interface{})
source["fuzzy"] = query
fq := make(map[string]interface{})
query[q.name] = fq
fq["value"] = q.value
if q.boost != nil {
fq["boost"] = *q.boost
}
if q.transpositions != nil {
fq["transpositions"] = *q.transpositions
}
if q.fuzziness != nil {
fq["fuzziness"] = q.fuzziness
}
if q.prefixLength != nil {
fq["prefix_length"] = *q.prefixLength
}
if q.maxExpansions != nil {
fq["max_expansions"] = *q.maxExpansions
}
if q.rewrite != "" {
fq["rewrite"] = q.rewrite
}
if q.queryName != "" {
fq["_name"] = q.queryName
}
return source, nil
}
================================================
FILE: search_queries_fuzzy_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestFuzzyQuery(t *testing.T) {
q := NewFuzzyQuery("user", "ki").Boost(1.5).Fuzziness(2).PrefixLength(0).MaxExpansions(100)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"fuzzy":{"user":{"boost":1.5,"fuzziness":2,"max_expansions":100,"prefix_length":0,"value":"ki"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_queries_geo_bounding_box.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// GeoBoundingBoxQuery allows to filter hits based on a point location using
// a bounding box.
//
// For more details, see:
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-geo-bounding-box-query.html
type GeoBoundingBoxQuery struct {
name string
topLeft interface{} // can be a GeoPoint, a GeoHash (string), or a lat/lon pair as float64
topRight interface{}
bottomRight interface{} // can be a GeoPoint, a GeoHash (string), or a lat/lon pair as float64
bottomLeft interface{}
wkt interface{}
typ string
validationMethod string
ignoreUnmapped *bool
queryName string
}
// NewGeoBoundingBoxQuery creates and initializes a new GeoBoundingBoxQuery.
func NewGeoBoundingBoxQuery(name string) *GeoBoundingBoxQuery {
return &GeoBoundingBoxQuery{
name: name,
}
}
// TopLeft position from longitude (left) and latitude (top).
func (q *GeoBoundingBoxQuery) TopLeft(top, left float64) *GeoBoundingBoxQuery {
q.topLeft = []float64{left, top}
return q
}
// TopLeftFromGeoPoint from a GeoPoint.
func (q *GeoBoundingBoxQuery) TopLeftFromGeoPoint(point *GeoPoint) *GeoBoundingBoxQuery {
return q.TopLeft(point.Lat, point.Lon)
}
// TopLeftFromGeoHash from a Geo hash.
func (q *GeoBoundingBoxQuery) TopLeftFromGeoHash(topLeft string) *GeoBoundingBoxQuery {
q.topLeft = topLeft
return q
}
// BottomRight position from longitude (right) and latitude (bottom).
func (q *GeoBoundingBoxQuery) BottomRight(bottom, right float64) *GeoBoundingBoxQuery {
q.bottomRight = []float64{right, bottom}
return q
}
// BottomRightFromGeoPoint from a GeoPoint.
func (q *GeoBoundingBoxQuery) BottomRightFromGeoPoint(point *GeoPoint) *GeoBoundingBoxQuery {
return q.BottomRight(point.Lat, point.Lon)
}
// BottomRightFromGeoHash from a Geo hash.
func (q *GeoBoundingBoxQuery) BottomRightFromGeoHash(bottomRight string) *GeoBoundingBoxQuery {
q.bottomRight = bottomRight
return q
}
// BottomLeft position from longitude (left) and latitude (bottom).
func (q *GeoBoundingBoxQuery) BottomLeft(bottom, left float64) *GeoBoundingBoxQuery {
q.bottomLeft = []float64{bottom, left}
return q
}
// BottomLeftFromGeoPoint from a GeoPoint.
func (q *GeoBoundingBoxQuery) BottomLeftFromGeoPoint(point *GeoPoint) *GeoBoundingBoxQuery {
return q.BottomLeft(point.Lat, point.Lon)
}
// BottomLeftFromGeoHash from a Geo hash.
func (q *GeoBoundingBoxQuery) BottomLeftFromGeoHash(bottomLeft string) *GeoBoundingBoxQuery {
q.bottomLeft = bottomLeft
return q
}
// TopRight position from longitude (right) and latitude (top).
func (q *GeoBoundingBoxQuery) TopRight(top, right float64) *GeoBoundingBoxQuery {
q.topRight = []float64{right, top}
return q
}
// TopRightFromGeoPoint from a GeoPoint.
func (q *GeoBoundingBoxQuery) TopRightFromGeoPoint(point *GeoPoint) *GeoBoundingBoxQuery {
return q.TopRight(point.Lat, point.Lon)
}
// TopRightFromGeoHash from a Geo hash.
func (q *GeoBoundingBoxQuery) TopRightFromGeoHash(topRight string) *GeoBoundingBoxQuery {
q.topRight = topRight
return q
}
// WKT initializes the bounding box from Well-Known Text (WKT),
// e.g. "BBOX (-74.1, -71.12, 40.73, 40.01)".
func (q *GeoBoundingBoxQuery) WKT(wkt interface{}) *GeoBoundingBoxQuery {
q.wkt = wkt
return q
}
// Type sets the type of executing the geo bounding box. It can be either
// memory or indexed. It defaults to memory.
func (q *GeoBoundingBoxQuery) Type(typ string) *GeoBoundingBoxQuery {
q.typ = typ
return q
}
// ValidationMethod accepts IGNORE_MALFORMED, COERCE, and STRICT (default).
// IGNORE_MALFORMED accepts geo points with invalid lat/lon.
// COERCE tries to infer the correct lat/lon.
func (q *GeoBoundingBoxQuery) ValidationMethod(method string) *GeoBoundingBoxQuery {
q.validationMethod = method
return q
}
// IgnoreUnmapped indicates whether to ignore unmapped fields (and run a
// MatchNoDocsQuery in place of this).
func (q *GeoBoundingBoxQuery) IgnoreUnmapped(ignoreUnmapped bool) *GeoBoundingBoxQuery {
q.ignoreUnmapped = &ignoreUnmapped
return q
}
// QueryName gives the query a name. It is used for caching.
func (q *GeoBoundingBoxQuery) QueryName(queryName string) *GeoBoundingBoxQuery {
q.queryName = queryName
return q
}
// Source returns JSON for the function score query.
func (q *GeoBoundingBoxQuery) Source() (interface{}, error) {
// {
// "geo_bounding_box" : {
// ...
// }
// }
source := make(map[string]interface{})
params := make(map[string]interface{})
source["geo_bounding_box"] = params
box := make(map[string]interface{})
if q.wkt != nil {
box["wkt"] = q.wkt
} else {
if q.topLeft != nil {
box["top_left"] = q.topLeft
}
if q.topRight != nil {
box["top_right"] = q.topRight
}
if q.bottomLeft != nil {
box["bottom_left"] = q.bottomLeft
}
if q.bottomRight != nil {
box["bottom_right"] = q.bottomRight
}
}
params[q.name] = box
if q.typ != "" {
params["type"] = q.typ
}
if q.validationMethod != "" {
params["validation_method"] = q.validationMethod
}
if q.ignoreUnmapped != nil {
params["ignore_unmapped"] = *q.ignoreUnmapped
}
if q.queryName != "" {
params["_name"] = q.queryName
}
return source, nil
}
================================================
FILE: search_queries_geo_bounding_box_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestGeoBoundingBoxQuery(t *testing.T) {
q := NewGeoBoundingBoxQuery("pin.location")
q = q.TopLeft(40.73, -74.1)
q = q.BottomRight(40.01, -71.12)
q = q.Type("memory")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"geo_bounding_box":{"pin.location":{"bottom_right":[-71.12,40.01],"top_left":[-74.1,40.73]},"type":"memory"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestGeoBoundingBoxQueryWithGeoPoint(t *testing.T) {
q := NewGeoBoundingBoxQuery("pin.location")
q = q.TopLeftFromGeoPoint(GeoPointFromLatLon(40.73, -74.1))
q = q.BottomRightFromGeoPoint(GeoPointFromLatLon(40.01, -71.12))
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"geo_bounding_box":{"pin.location":{"bottom_right":[-71.12,40.01],"top_left":[-74.1,40.73]}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestGeoBoundingBoxQueryWithGeoHash(t *testing.T) {
q := NewGeoBoundingBoxQuery("pin.location")
q = q.TopLeftFromGeoHash("dr5r9ydj2y73")
q = q.BottomRightFromGeoHash("drj7teegpus6")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"geo_bounding_box":{"pin.location":{"bottom_right":"drj7teegpus6","top_left":"dr5r9ydj2y73"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestGeoBoundingBoxQueryWithWKT(t *testing.T) {
q := NewGeoBoundingBoxQuery("pin.location")
q = q.WKT("BBOX (-74.1, -71.12, 40.73, 40.01)")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"geo_bounding_box":{"pin.location":{"wkt":"BBOX (-74.1, -71.12, 40.73, 40.01)"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestGeoBoundingBoxQueryWithMixed(t *testing.T) {
q := NewGeoBoundingBoxQuery("pin.location")
q = q.TopLeftFromGeoPoint(GeoPointFromLatLon(40.73, -74.1))
q = q.BottomRightFromGeoHash("drj7teegpus6")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"geo_bounding_box":{"pin.location":{"bottom_right":"drj7teegpus6","top_left":[-74.1,40.73]}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestGeoBoundingBoxQueryWithParameters(t *testing.T) {
q := NewGeoBoundingBoxQuery("pin.location")
q = q.TopLeftFromGeoHash("dr5r9ydj2y73")
q = q.BottomRightFromGeoHash("drj7teegpus6")
q = q.ValidationMethod("IGNORE_MALFORMED")
q = q.IgnoreUnmapped((true))
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"geo_bounding_box":{"ignore_unmapped":true,"pin.location":{"bottom_right":"drj7teegpus6","top_left":"dr5r9ydj2y73"},"validation_method":"IGNORE_MALFORMED"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_queries_geo_distance.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// GeoDistanceQuery filters documents that include only hits that exists
// within a specific distance from a geo point.
//
// For more details, see:
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-geo-distance-query.html
type GeoDistanceQuery struct {
name string
distance string
lat float64
lon float64
geohash string
distanceType string
queryName string
}
// NewGeoDistanceQuery creates and initializes a new GeoDistanceQuery.
func NewGeoDistanceQuery(name string) *GeoDistanceQuery {
return &GeoDistanceQuery{name: name}
}
func (q *GeoDistanceQuery) GeoPoint(point *GeoPoint) *GeoDistanceQuery {
q.lat = point.Lat
q.lon = point.Lon
return q
}
func (q *GeoDistanceQuery) Point(lat, lon float64) *GeoDistanceQuery {
q.lat = lat
q.lon = lon
return q
}
func (q *GeoDistanceQuery) Lat(lat float64) *GeoDistanceQuery {
q.lat = lat
return q
}
func (q *GeoDistanceQuery) Lon(lon float64) *GeoDistanceQuery {
q.lon = lon
return q
}
func (q *GeoDistanceQuery) GeoHash(geohash string) *GeoDistanceQuery {
q.geohash = geohash
return q
}
func (q *GeoDistanceQuery) Distance(distance string) *GeoDistanceQuery {
q.distance = distance
return q
}
func (q *GeoDistanceQuery) DistanceType(distanceType string) *GeoDistanceQuery {
q.distanceType = distanceType
return q
}
func (q *GeoDistanceQuery) QueryName(queryName string) *GeoDistanceQuery {
q.queryName = queryName
return q
}
// Source returns JSON for the function score query.
func (q *GeoDistanceQuery) Source() (interface{}, error) {
// {
// "geo_distance" : {
// "distance" : "200km",
// "pin.location" : {
// "lat" : 40,
// "lon" : -70
// }
// }
// }
source := make(map[string]interface{})
params := make(map[string]interface{})
if q.geohash != "" {
params[q.name] = q.geohash
} else {
location := make(map[string]interface{})
location["lat"] = q.lat
location["lon"] = q.lon
params[q.name] = location
}
if q.distance != "" {
params["distance"] = q.distance
}
if q.distanceType != "" {
params["distance_type"] = q.distanceType
}
if q.queryName != "" {
params["_name"] = q.queryName
}
source["geo_distance"] = params
return source, nil
}
================================================
FILE: search_queries_geo_distance_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestGeoDistanceQuery(t *testing.T) {
q := NewGeoDistanceQuery("pin.location")
q = q.Lat(40)
q = q.Lon(-70)
q = q.Distance("200km")
q = q.DistanceType("plane")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"geo_distance":{"distance":"200km","distance_type":"plane","pin.location":{"lat":40,"lon":-70}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestGeoDistanceQueryWithGeoPoint(t *testing.T) {
q := NewGeoDistanceQuery("pin.location")
q = q.GeoPoint(GeoPointFromLatLon(40, -70))
q = q.Distance("200km")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"geo_distance":{"distance":"200km","pin.location":{"lat":40,"lon":-70}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestGeoDistanceQueryWithGeoHash(t *testing.T) {
q := NewGeoDistanceQuery("pin.location")
q = q.GeoHash("drm3btev3e86")
q = q.Distance("12km")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"geo_distance":{"distance":"12km","pin.location":"drm3btev3e86"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_queries_geo_polygon.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// GeoPolygonQuery allows to include hits that only fall within a polygon of points.
//
// For more details, see:
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-geo-polygon-query.html
type GeoPolygonQuery struct {
name string
points []*GeoPoint
queryName string
}
// NewGeoPolygonQuery creates and initializes a new GeoPolygonQuery.
func NewGeoPolygonQuery(name string) *GeoPolygonQuery {
return &GeoPolygonQuery{
name: name,
points: make([]*GeoPoint, 0),
}
}
// AddPoint adds a point from latitude and longitude.
func (q *GeoPolygonQuery) AddPoint(lat, lon float64) *GeoPolygonQuery {
q.points = append(q.points, GeoPointFromLatLon(lat, lon))
return q
}
// AddGeoPoint adds a GeoPoint.
func (q *GeoPolygonQuery) AddGeoPoint(point *GeoPoint) *GeoPolygonQuery {
q.points = append(q.points, point)
return q
}
func (q *GeoPolygonQuery) QueryName(queryName string) *GeoPolygonQuery {
q.queryName = queryName
return q
}
// Source returns JSON for the function score query.
func (q *GeoPolygonQuery) Source() (interface{}, error) {
// "geo_polygon" : {
// "person.location" : {
// "points" : [
// {"lat" : 40, "lon" : -70},
// {"lat" : 30, "lon" : -80},
// {"lat" : 20, "lon" : -90}
// ]
// }
// }
source := make(map[string]interface{})
params := make(map[string]interface{})
source["geo_polygon"] = params
polygon := make(map[string]interface{})
params[q.name] = polygon
var points []interface{}
for _, point := range q.points {
points = append(points, point.Source())
}
polygon["points"] = points
if q.queryName != "" {
params["_name"] = q.queryName
}
return source, nil
}
================================================
FILE: search_queries_geo_polygon_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestGeoPolygonQuery(t *testing.T) {
q := NewGeoPolygonQuery("person.location")
q = q.AddPoint(40, -70)
q = q.AddPoint(30, -80)
point, err := GeoPointFromString("20,-90")
if err != nil {
t.Fatalf("GeoPointFromString failed: %v", err)
}
q = q.AddGeoPoint(point)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"geo_polygon":{"person.location":{"points":[{"lat":40,"lon":-70},{"lat":30,"lon":-80},{"lat":20,"lon":-90}]}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestGeoPolygonQueryFromGeoPoints(t *testing.T) {
q := NewGeoPolygonQuery("person.location")
q = q.AddGeoPoint(&GeoPoint{Lat: 40, Lon: -70})
q = q.AddGeoPoint(GeoPointFromLatLon(30, -80))
point, err := GeoPointFromString("20,-90")
if err != nil {
t.Fatalf("GeoPointFromString failed: %v", err)
}
q = q.AddGeoPoint(point)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"geo_polygon":{"person.location":{"points":[{"lat":40,"lon":-70},{"lat":30,"lon":-80},{"lat":20,"lon":-90}]}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_queries_has_child.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// HasChildQuery accepts a query and the child type to run against, and results
// in parent documents that have child docs matching the query.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-has-child-query.html
type HasChildQuery struct {
query Query
childType string
boost *float64
scoreMode string
minChildren *int
maxChildren *int
shortCircuitCutoff *int
queryName string
innerHit *InnerHit
}
// NewHasChildQuery creates and initializes a new has_child query.
func NewHasChildQuery(childType string, query Query) *HasChildQuery {
return &HasChildQuery{
query: query,
childType: childType,
}
}
// Boost sets the boost for this query.
func (q *HasChildQuery) Boost(boost float64) *HasChildQuery {
q.boost = &boost
return q
}
// ScoreMode defines how the scores from the matching child documents
// are mapped into the parent document. Allowed values are: min, max,
// avg, or none.
func (q *HasChildQuery) ScoreMode(scoreMode string) *HasChildQuery {
q.scoreMode = scoreMode
return q
}
// MinChildren defines the minimum number of children that are required
// to match for the parent to be considered a match.
func (q *HasChildQuery) MinChildren(minChildren int) *HasChildQuery {
q.minChildren = &minChildren
return q
}
// MaxChildren defines the maximum number of children that are required
// to match for the parent to be considered a match.
func (q *HasChildQuery) MaxChildren(maxChildren int) *HasChildQuery {
q.maxChildren = &maxChildren
return q
}
// ShortCircuitCutoff configures what cut off point only to evaluate
// parent documents that contain the matching parent id terms instead
// of evaluating all parent docs.
func (q *HasChildQuery) ShortCircuitCutoff(shortCircuitCutoff int) *HasChildQuery {
q.shortCircuitCutoff = &shortCircuitCutoff
return q
}
// QueryName specifies the query name for the filter that can be used when
// searching for matched filters per hit.
func (q *HasChildQuery) QueryName(queryName string) *HasChildQuery {
q.queryName = queryName
return q
}
// InnerHit sets the inner hit definition in the scope of this query and
// reusing the defined type and query.
func (q *HasChildQuery) InnerHit(innerHit *InnerHit) *HasChildQuery {
q.innerHit = innerHit
return q
}
// Source returns JSON for the function score query.
func (q *HasChildQuery) Source() (interface{}, error) {
// {
// "has_child" : {
// "type" : "blog_tag",
// "score_mode" : "min",
// "query" : {
// "term" : {
// "tag" : "something"
// }
// }
// }
// }
source := make(map[string]interface{})
query := make(map[string]interface{})
source["has_child"] = query
src, err := q.query.Source()
if err != nil {
return nil, err
}
query["query"] = src
query["type"] = q.childType
if q.boost != nil {
query["boost"] = *q.boost
}
if q.scoreMode != "" {
query["score_mode"] = q.scoreMode
}
if q.minChildren != nil {
query["min_children"] = *q.minChildren
}
if q.maxChildren != nil {
query["max_children"] = *q.maxChildren
}
if q.shortCircuitCutoff != nil {
query["short_circuit_cutoff"] = *q.shortCircuitCutoff
}
if q.queryName != "" {
query["_name"] = q.queryName
}
if q.innerHit != nil {
src, err := q.innerHit.Source()
if err != nil {
return nil, err
}
query["inner_hits"] = src
}
return source, nil
}
================================================
FILE: search_queries_has_child_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestHasChildQuery(t *testing.T) {
q := NewHasChildQuery("blog_tag", NewTermQuery("tag", "something")).ScoreMode("min")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"has_child":{"query":{"term":{"tag":"something"}},"score_mode":"min","type":"blog_tag"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestHasChildQueryWithInnerHit(t *testing.T) {
q := NewHasChildQuery("blog_tag", NewTermQuery("tag", "something"))
q = q.InnerHit(NewInnerHit().Name("comments"))
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"has_child":{"inner_hits":{"name":"comments"},"query":{"term":{"tag":"something"}},"type":"blog_tag"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_queries_has_parent.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// HasParentQuery accepts a query and a parent type. The query is executed
// in the parent document space which is specified by the parent type.
// This query returns child documents which associated parents have matched.
// For the rest has_parent query has the same options and works in the
// same manner as has_child query.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-has-parent-query.html
type HasParentQuery struct {
query Query
parentType string
boost *float64
score *bool
queryName string
innerHit *InnerHit
ignoreUnmapped *bool
}
// NewHasParentQuery creates and initializes a new has_parent query.
func NewHasParentQuery(parentType string, query Query) *HasParentQuery {
return &HasParentQuery{
query: query,
parentType: parentType,
}
}
// Boost sets the boost for this query.
func (q *HasParentQuery) Boost(boost float64) *HasParentQuery {
q.boost = &boost
return q
}
// Score defines if the parent score is mapped into the child documents.
func (q *HasParentQuery) Score(score bool) *HasParentQuery {
q.score = &score
return q
}
// QueryName specifies the query name for the filter that can be used when
// searching for matched filters per hit.
func (q *HasParentQuery) QueryName(queryName string) *HasParentQuery {
q.queryName = queryName
return q
}
// InnerHit sets the inner hit definition in the scope of this query and
// reusing the defined type and query.
func (q *HasParentQuery) InnerHit(innerHit *InnerHit) *HasParentQuery {
q.innerHit = innerHit
return q
}
// IgnoreUnmapped specifies whether unmapped types should be ignored.
// If set to false, the query failes when an unmapped type is found.
func (q *HasParentQuery) IgnoreUnmapped(ignore bool) *HasParentQuery {
q.ignoreUnmapped = &ignore
return q
}
// Source returns JSON for the function score query.
func (q *HasParentQuery) Source() (interface{}, error) {
// {
// "has_parent" : {
// "parent_type" : "blog",
// "query" : {
// "term" : {
// "tag" : "something"
// }
// }
// }
// }
source := make(map[string]interface{})
query := make(map[string]interface{})
source["has_parent"] = query
src, err := q.query.Source()
if err != nil {
return nil, err
}
query["query"] = src
query["parent_type"] = q.parentType
if q.boost != nil {
query["boost"] = *q.boost
}
if q.score != nil {
query["score"] = *q.score
}
if q.queryName != "" {
query["_name"] = q.queryName
}
if q.innerHit != nil {
src, err := q.innerHit.Source()
if err != nil {
return nil, err
}
query["inner_hits"] = src
}
if q.ignoreUnmapped != nil {
query["ignore_unmapped"] = *q.ignoreUnmapped
}
return source, nil
}
================================================
FILE: search_queries_has_parent_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestHasParentQueryTest(t *testing.T) {
q := NewHasParentQuery("blog", NewTermQuery("tag", "something")).Score(true)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"has_parent":{"parent_type":"blog","query":{"term":{"tag":"something"}},"score":true}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_queries_ids.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// IdsQuery filters documents that only have the provided ids.
// Note, this query uses the _uid field.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.6/query-dsl-ids-query.html
type IdsQuery struct {
types []string
values []string
boost *float64
queryName string
}
// NewIdsQuery creates and initializes a new ids query.
//
// Notice that types are in the process of being removed.
// You should filter on a field instead.
func NewIdsQuery(types ...string) *IdsQuery {
return &IdsQuery{
types: types,
values: make([]string, 0),
}
}
// Ids adds ids to the filter.
func (q *IdsQuery) Ids(ids ...string) *IdsQuery {
q.values = append(q.values, ids...)
return q
}
// Boost sets the boost for this query.
func (q *IdsQuery) Boost(boost float64) *IdsQuery {
q.boost = &boost
return q
}
// QueryName sets the query name for the filter.
func (q *IdsQuery) QueryName(queryName string) *IdsQuery {
q.queryName = queryName
return q
}
// Source returns JSON for the function score query.
func (q *IdsQuery) Source() (interface{}, error) {
// {
// "ids" : {
// "type" : "my_type",
// "values" : ["1", "4", "100"]
// }
// }
source := make(map[string]interface{})
query := make(map[string]interface{})
source["ids"] = query
// type(s)
if len(q.types) == 1 {
query["type"] = q.types[0]
} else if len(q.types) > 1 {
query["types"] = q.types
}
// values
query["values"] = q.values
if q.boost != nil {
query["boost"] = *q.boost
}
if q.queryName != "" {
query["_name"] = q.queryName
}
return source, nil
}
================================================
FILE: search_queries_ids_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestIdsQuery(t *testing.T) {
q := NewIdsQuery("my_type").Ids("1", "4", "100").Boost(10.5).QueryName("my_query")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"ids":{"_name":"my_query","boost":10.5,"type":"my_type","values":["1","4","100"]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_queries_interval.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// IntervalQueryRule represents the generic matching interval rule interface.
// Interval Rule is actually just a Query, but may be used only inside
// IntervalQuery. An extra method is added just to shield its
// implementations (*Rule objects) from other query objects.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.5/query-dsl-intervals-query.html
// for details.
type IntervalQueryRule interface {
Query
// isIntervalQueryRule is never actually called, and is used just for Rule to
// differ from standard Query.
isIntervalQueryRule() bool
}
// IntervalQuery returns documents based on the order and proximity of matching terms.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.5/query-dsl-intervals-query.html
type IntervalQuery struct {
field string
rule IntervalQueryRule
}
// NewIntervalQuery creates and initializes a new IntervalQuery.
func NewIntervalQuery(field string, rule IntervalQueryRule) *IntervalQuery {
return &IntervalQuery{field: field, rule: rule}
}
// Source returns JSON for the function score query.
func (q *IntervalQuery) Source() (interface{}, error) {
// {
// "intervals" : { ... }
// }
source := make(map[string]interface{})
params := make(map[string]interface{})
source["intervals"] = params
src, err := q.rule.Source()
if err != nil {
return nil, err
}
params[q.field] = src
return source, nil
}
================================================
FILE: search_queries_interval_filter.go
================================================
package elastic
var (
_ IntervalQueryRule = (*IntervalQueryFilter)(nil)
)
// IntervalQueryFilter specifies filters used in some
// IntervalQueryRule implementations, e.g. IntervalQueryRuleAllOf.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.5/query-dsl-intervals-query.html#interval_filter
// for details.
type IntervalQueryFilter struct {
after IntervalQueryRule
before IntervalQueryRule
containedBy IntervalQueryRule
containing IntervalQueryRule
overlapping IntervalQueryRule
notContainedBy IntervalQueryRule
notContaining IntervalQueryRule
notOverlapping IntervalQueryRule
script *Script
}
// NewIntervalQueryFilter initializes and creates a new
// IntervalQueryFilter.
func NewIntervalQueryFilter() *IntervalQueryFilter {
return &IntervalQueryFilter{}
}
// After specifies the query to be used to return intervals that follow
// an interval from the filter rule.
func (r *IntervalQueryFilter) After(after IntervalQueryRule) *IntervalQueryFilter {
r.after = after
return r
}
// Before specifies the query to be used to return intervals that occur
// before an interval from the filter rule.
func (r *IntervalQueryFilter) Before(before IntervalQueryRule) *IntervalQueryFilter {
r.before = before
return r
}
// ContainedBy specifies the query to be used to return intervals contained
// by an interval from the filter rule.
func (r *IntervalQueryFilter) ContainedBy(containedBy IntervalQueryRule) *IntervalQueryFilter {
r.containedBy = containedBy
return r
}
// Containing specifies the query to be used to return intervals that contain an
// interval from the filter rule.
func (r *IntervalQueryFilter) Containing(containing IntervalQueryRule) *IntervalQueryFilter {
r.containing = containing
return r
}
// Overlapping specifies the query to be used to return intervals that overlap
// with an interval from the filter rule.
func (r *IntervalQueryFilter) Overlapping(overlapping IntervalQueryRule) *IntervalQueryFilter {
r.overlapping = overlapping
return r
}
// NotContainedBy specifies the query to be used to return intervals that are NOT
// contained by an interval from the filter rule.
func (r *IntervalQueryFilter) NotContainedBy(notContainedBy IntervalQueryRule) *IntervalQueryFilter {
r.notContainedBy = notContainedBy
return r
}
// NotContaining specifies the query to be used to return intervals that do NOT
// contain an interval from the filter rule.
func (r *IntervalQueryFilter) NotContaining(notContaining IntervalQueryRule) *IntervalQueryFilter {
r.notContaining = notContaining
return r
}
// NotOverlapping specifies the query to be used to return intervals that do NOT
// overlap with an interval from the filter rule.
func (r *IntervalQueryFilter) NotOverlapping(notOverlapping IntervalQueryRule) *IntervalQueryFilter {
r.notOverlapping = notOverlapping
return r
}
// Script allows a script to be used to return matching documents. The script
// must return a boolean value, true or false.
func (r *IntervalQueryFilter) Script(script *Script) *IntervalQueryFilter {
r.script = script
return r
}
// Source returns JSON for the function score query.
func (r *IntervalQueryFilter) Source() (interface{}, error) {
source := make(map[string]interface{})
if r.before != nil {
src, err := r.before.Source()
if err != nil {
return nil, err
}
source["before"] = src
}
if r.after != nil {
src, err := r.after.Source()
if err != nil {
return nil, err
}
source["after"] = src
}
if r.containedBy != nil {
src, err := r.containedBy.Source()
if err != nil {
return nil, err
}
source["contained_by"] = src
}
if r.containing != nil {
src, err := r.containing.Source()
if err != nil {
return nil, err
}
source["containing"] = src
}
if r.overlapping != nil {
src, err := r.overlapping.Source()
if err != nil {
return nil, err
}
source["overlapping"] = src
}
if r.notContainedBy != nil {
src, err := r.notContainedBy.Source()
if err != nil {
return nil, err
}
source["not_contained_by"] = src
}
if r.notContaining != nil {
src, err := r.notContaining.Source()
if err != nil {
return nil, err
}
source["not_containing"] = src
}
if r.notOverlapping != nil {
src, err := r.notOverlapping.Source()
if err != nil {
return nil, err
}
source["not_overlapping"] = src
}
if r.script != nil {
src, err := r.script.Source()
if err != nil {
return nil, err
}
source["script"] = src
}
return source, nil
}
// isIntervalQueryRule implements the marker interface.
func (r *IntervalQueryFilter) isIntervalQueryRule() bool {
return true
}
================================================
FILE: search_queries_interval_integration_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"testing"
)
func TestIntervalQuery_Integration(t *testing.T) {
// client := setupTestClientAndCreateIndexAndAddDocs(t, SetTraceLog(log.New(os.Stdout, "", log.LstdFlags)))
client := setupTestClientAndCreateIndexAndAddDocs(t)
t.Run("Match", func(t *testing.T) {
testIntervalQueryMatch(client, t)
})
t.Run("Prefix", func(t *testing.T) {
testIntervalQueryPrefix(client, t)
})
t.Run("Wildcard", func(t *testing.T) {
testIntervalQueryWildcard(client, t)
})
t.Run("Fuzzy", func(t *testing.T) {
testIntervalQueryFuzzy(client, t)
})
}
func testIntervalQueryMatch(client *Client, t *testing.T) {
q := NewIntervalQuery(
"message",
NewIntervalQueryRuleAllOf(
NewIntervalQueryRuleAnyOf(
NewIntervalQueryRuleMatch("Golang").Ordered(true),
NewIntervalQueryRuleMatch("Cycling").MaxGaps(0).Filter(
NewIntervalQueryFilter().NotContaining(
NewIntervalQueryRuleMatch("Hockey"),
),
),
),
).Ordered(true),
)
// Match all should return all documents
searchResult, err := client.Search().
Index(testIndexName).
Query(q).
Size(10).
Pretty(true).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.Hits == nil {
t.Errorf("expected SearchResult.Hits != nil; got nil")
}
if got, want := searchResult.TotalHits(), int64(2); got != want {
t.Errorf("expected SearchResult.TotalHits() = %d; got %d", want, got)
}
if got, want := len(searchResult.Hits.Hits), 2; got != want {
t.Errorf("expected len(SearchResult.Hits.Hits) = %d; got %d", want, got)
}
for _, hit := range searchResult.Hits.Hits {
if hit.Index != testIndexName {
t.Errorf("expected SearchResult.Hits.Hit.Index = %q; got %q", testIndexName, hit.Index)
}
item := make(map[string]interface{})
err := json.Unmarshal(hit.Source, &item)
if err != nil {
t.Fatal(err)
}
}
}
func testIntervalQueryPrefix(client *Client, t *testing.T) {
q := NewIntervalQuery(
"message",
NewIntervalQueryRuleAllOf(
NewIntervalQueryRuleAnyOf(
NewIntervalQueryRuleMatch("Golang").Ordered(true),
NewIntervalQueryRuleMatch("Cycling").MaxGaps(0).Filter(
NewIntervalQueryFilter().NotContaining(
NewIntervalQueryRulePrefix("Hockey"),
),
),
),
).Ordered(true),
)
// Match all should return all documents
searchResult, err := client.Search().
Index(testIndexName).
Query(q).
Size(10).
Pretty(true).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.Hits == nil {
t.Errorf("expected SearchResult.Hits != nil; got nil")
}
if got, want := searchResult.TotalHits(), int64(2); got != want {
t.Errorf("expected SearchResult.TotalHits() = %d; got %d", want, got)
}
if got, want := len(searchResult.Hits.Hits), 2; got != want {
t.Errorf("expected len(SearchResult.Hits.Hits) = %d; got %d", want, got)
}
for _, hit := range searchResult.Hits.Hits {
if hit.Index != testIndexName {
t.Errorf("expected SearchResult.Hits.Hit.Index = %q; got %q", testIndexName, hit.Index)
}
item := make(map[string]interface{})
err := json.Unmarshal(hit.Source, &item)
if err != nil {
t.Fatal(err)
}
}
}
func testIntervalQueryWildcard(client *Client, t *testing.T) {
q := NewIntervalQuery(
"message",
NewIntervalQueryRuleAllOf(
NewIntervalQueryRuleAnyOf(
NewIntervalQueryRuleMatch("Golang").Ordered(true),
NewIntervalQueryRuleMatch("Cycling").MaxGaps(0).Filter(
NewIntervalQueryFilter().NotContaining(
NewIntervalQueryRuleWildcard("Hockey*"),
),
),
),
).Ordered(true),
)
// Match all should return all documents
searchResult, err := client.Search().
Index(testIndexName).
Query(q).
Size(10).
Pretty(true).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.Hits == nil {
t.Errorf("expected SearchResult.Hits != nil; got nil")
}
if got, want := searchResult.TotalHits(), int64(2); got != want {
t.Errorf("expected SearchResult.TotalHits() = %d; got %d", want, got)
}
if got, want := len(searchResult.Hits.Hits), 2; got != want {
t.Errorf("expected len(SearchResult.Hits.Hits) = %d; got %d", want, got)
}
for _, hit := range searchResult.Hits.Hits {
if hit.Index != testIndexName {
t.Errorf("expected SearchResult.Hits.Hit.Index = %q; got %q", testIndexName, hit.Index)
}
item := make(map[string]interface{})
err := json.Unmarshal(hit.Source, &item)
if err != nil {
t.Fatal(err)
}
}
}
func testIntervalQueryFuzzy(client *Client, t *testing.T) {
q := NewIntervalQuery(
"message",
NewIntervalQueryRuleAllOf(
NewIntervalQueryRuleAnyOf(
NewIntervalQueryRuleMatch("Golang").Ordered(true),
NewIntervalQueryRuleMatch("Cycling").MaxGaps(0).Filter(
NewIntervalQueryFilter().NotContaining(
NewIntervalQueryRuleFuzzy("Hocky").Fuzziness("auto"),
),
),
),
).Ordered(true),
)
// Match all should return all documents
searchResult, err := client.Search().
Index(testIndexName).
Query(q).
Size(10).
Pretty(true).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.Hits == nil {
t.Errorf("expected SearchResult.Hits != nil; got nil")
}
if got, want := searchResult.TotalHits(), int64(2); got != want {
t.Errorf("expected SearchResult.TotalHits() = %d; got %d", want, got)
}
if got, want := len(searchResult.Hits.Hits), 2; got != want {
t.Errorf("expected len(SearchResult.Hits.Hits) = %d; got %d", want, got)
}
for _, hit := range searchResult.Hits.Hits {
if hit.Index != testIndexName {
t.Errorf("expected SearchResult.Hits.Hit.Index = %q; got %q", testIndexName, hit.Index)
}
item := make(map[string]interface{})
err := json.Unmarshal(hit.Source, &item)
if err != nil {
t.Fatal(err)
}
}
}
================================================
FILE: search_queries_interval_rules_all_of.go
================================================
package elastic
var (
_ IntervalQueryRule = (*IntervalQueryRuleAllOf)(nil)
)
// IntervalQueryRuleAllOf is an implementation of IntervalQueryRule.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.5/query-dsl-intervals-query.html#intervals-all_of
// for details.
type IntervalQueryRuleAllOf struct {
intervals []IntervalQueryRule
maxGaps *int
ordered *bool
filter *IntervalQueryFilter
}
// NewIntervalQueryRuleAllOf initializes and returns a new instance
// of IntervalQueryRuleAllOf.
func NewIntervalQueryRuleAllOf(intervals ...IntervalQueryRule) *IntervalQueryRuleAllOf {
return &IntervalQueryRuleAllOf{intervals: intervals}
}
// MaxGaps specifies the maximum number of positions between the matching
// terms. Terms further apart than this are considered matches. Defaults to -1.
func (r *IntervalQueryRuleAllOf) MaxGaps(maxGaps int) *IntervalQueryRuleAllOf {
r.maxGaps = &maxGaps
return r
}
// Ordered, if true, indicates that matching terms must appear in their specified
// order. Defaults to false.
func (r *IntervalQueryRuleAllOf) Ordered(ordered bool) *IntervalQueryRuleAllOf {
r.ordered = &ordered
return r
}
// Filter adds an additional interval filter.
func (r *IntervalQueryRuleAllOf) Filter(filter *IntervalQueryFilter) *IntervalQueryRuleAllOf {
r.filter = filter
return r
}
// Source returns JSON for the function score query.
func (r *IntervalQueryRuleAllOf) Source() (interface{}, error) {
source := make(map[string]interface{})
intervalSources := make([]interface{}, 0)
for _, interval := range r.intervals {
src, err := interval.Source()
if err != nil {
return nil, err
}
intervalSources = append(intervalSources, src)
}
source["intervals"] = intervalSources
if r.ordered != nil {
source["ordered"] = *r.ordered
}
if r.maxGaps != nil {
source["max_gaps"] = *r.maxGaps
}
if r.filter != nil {
src, err := r.filter.Source()
if err != nil {
return nil, err
}
source["filter"] = src
}
return map[string]interface{}{
"all_of": source,
}, nil
}
// isIntervalQueryRule implements the marker interface.
func (r *IntervalQueryRuleAllOf) isIntervalQueryRule() bool {
return true
}
================================================
FILE: search_queries_interval_rules_any_of.go
================================================
package elastic
var (
_ IntervalQueryRule = (*IntervalQueryRuleAnyOf)(nil)
)
// IntervalQueryRuleAnyOf is an implementation of IntervalQueryRule.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.5/query-dsl-intervals-query.html#intervals-any_of
// for details.
type IntervalQueryRuleAnyOf struct {
intervals []IntervalQueryRule
filter *IntervalQueryFilter
}
// NewIntervalQueryRuleAnyOf initializes and returns a new instance
// of IntervalQueryRuleAnyOf.
func NewIntervalQueryRuleAnyOf(intervals ...IntervalQueryRule) *IntervalQueryRuleAnyOf {
return &IntervalQueryRuleAnyOf{intervals: intervals}
}
// Filter adds an additional interval filter.
func (r *IntervalQueryRuleAnyOf) Filter(filter *IntervalQueryFilter) *IntervalQueryRuleAnyOf {
r.filter = filter
return r
}
// Source returns JSON for the function score query.
func (r *IntervalQueryRuleAnyOf) Source() (interface{}, error) {
source := make(map[string]interface{})
var intervalSources []interface{}
for _, interval := range r.intervals {
src, err := interval.Source()
if err != nil {
return nil, err
}
intervalSources = append(intervalSources, src)
}
source["intervals"] = intervalSources
if r.filter != nil {
src, err := r.filter.Source()
if err != nil {
return nil, err
}
source["filter"] = src
}
return map[string]interface{}{
"any_of": source,
}, nil
}
// isIntervalQueryRule implements the marker interface.
func (r *IntervalQueryRuleAnyOf) isIntervalQueryRule() bool {
return true
}
================================================
FILE: search_queries_interval_rules_fuzzy.go
================================================
package elastic
var (
_ IntervalQueryRule = (*IntervalQueryRuleFuzzy)(nil)
)
// IntervalQueryRuleFuzzy is an implementation of IntervalQueryRule.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.16/query-dsl-intervals-query.html#intervals-fuzzy
// for details.
type IntervalQueryRuleFuzzy struct {
term string
prefixLength *int
transpositions *bool
fuzziness interface{}
analyzer string
useField string
}
// NewIntervalQueryRuleFuzzy initializes and returns a new instance
// of IntervalQueryRuleFuzzy.
func NewIntervalQueryRuleFuzzy(term string) *IntervalQueryRuleFuzzy {
return &IntervalQueryRuleFuzzy{term: term}
}
// PrefixLength is the number of beginning characters left unchanged when
// creating expansions. Defaults to 0.
func (q *IntervalQueryRuleFuzzy) PrefixLength(prefixLength int) *IntervalQueryRuleFuzzy {
q.prefixLength = &prefixLength
return q
}
// Fuzziness is the maximum edit distance allowed for matching.
// It can be integers like 0, 1 or 2 as well as strings
// like "auto", "0..1", "1..4" or "0.0..1.0". Defaults to "auto".
func (q *IntervalQueryRuleFuzzy) Fuzziness(fuzziness interface{}) *IntervalQueryRuleFuzzy {
q.fuzziness = fuzziness
return q
}
// Transpositions indicates whether edits include transpositions of two
// adjacent characters (ab -> ba). Defaults to true.
func (q *IntervalQueryRuleFuzzy) Transpositions(transpositions bool) *IntervalQueryRuleFuzzy {
q.transpositions = &transpositions
return q
}
// Analyzer specifies the analyzer used to analyze terms in the query.
func (r *IntervalQueryRuleFuzzy) Analyzer(analyzer string) *IntervalQueryRuleFuzzy {
r.analyzer = analyzer
return r
}
// UseField, if specified, matches the intervals from this field rather than
// the top-level field.
func (r *IntervalQueryRuleFuzzy) UseField(useField string) *IntervalQueryRuleFuzzy {
r.useField = useField
return r
}
// Source returns JSON for the function score query.
func (r *IntervalQueryRuleFuzzy) Source() (interface{}, error) {
source := make(map[string]interface{})
source["term"] = r.term
if r.prefixLength != nil {
source["prefix_length"] = *r.prefixLength
}
if r.transpositions != nil {
source["transpositions"] = *r.transpositions
}
if r.fuzziness != "" {
source["fuzziness"] = r.fuzziness
}
if r.analyzer != "" {
source["analyzer"] = r.analyzer
}
if r.useField != "" {
source["use_field"] = r.useField
}
return map[string]interface{}{
"fuzzy": source,
}, nil
}
// isIntervalQueryRule implements the marker interface.
func (r *IntervalQueryRuleFuzzy) isIntervalQueryRule() bool {
return true
}
================================================
FILE: search_queries_interval_rules_match.go
================================================
package elastic
var (
_ IntervalQueryRule = (*IntervalQueryRuleMatch)(nil)
)
// IntervalQueryRuleMatch is an implementation of IntervalQueryRule.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.5/query-dsl-intervals-query.html#intervals-match
// for details.
type IntervalQueryRuleMatch struct {
query string
maxGaps *int
ordered *bool
analyzer string
useField string
filter *IntervalQueryFilter
}
// NewIntervalQueryRuleMatch initializes and returns a new instance
// of IntervalQueryRuleMatch.
func NewIntervalQueryRuleMatch(query string) *IntervalQueryRuleMatch {
return &IntervalQueryRuleMatch{query: query}
}
// MaxGaps specifies the maximum number of positions between the matching
// terms. Terms further apart than this are considered matches. Defaults to -1.
func (r *IntervalQueryRuleMatch) MaxGaps(maxGaps int) *IntervalQueryRuleMatch {
r.maxGaps = &maxGaps
return r
}
// Ordered, if true, indicates that matching terms must appear in their specified
// order. Defaults to false.
func (r *IntervalQueryRuleMatch) Ordered(ordered bool) *IntervalQueryRuleMatch {
r.ordered = &ordered
return r
}
// Analyzer specifies the analyzer used to analyze terms in the query.
func (r *IntervalQueryRuleMatch) Analyzer(analyzer string) *IntervalQueryRuleMatch {
r.analyzer = analyzer
return r
}
// UseField, if specified, matches the intervals from this field rather than
// the top-level field.
func (r *IntervalQueryRuleMatch) UseField(useField string) *IntervalQueryRuleMatch {
r.useField = useField
return r
}
// Filter adds an additional interval filter.
func (r *IntervalQueryRuleMatch) Filter(filter *IntervalQueryFilter) *IntervalQueryRuleMatch {
r.filter = filter
return r
}
// Source returns JSON for the function score query.
func (r *IntervalQueryRuleMatch) Source() (interface{}, error) {
source := make(map[string]interface{})
source["query"] = r.query
if r.ordered != nil {
source["ordered"] = *r.ordered
}
if r.maxGaps != nil {
source["max_gaps"] = *r.maxGaps
}
if r.analyzer != "" {
source["analyzer"] = r.analyzer
}
if r.useField != "" {
source["use_field"] = r.useField
}
if r.filter != nil {
filterRuleSource, err := r.filter.Source()
if err != nil {
return nil, err
}
source["filter"] = filterRuleSource
}
return map[string]interface{}{
"match": source,
}, nil
}
// isIntervalQueryRule implements the marker interface.
func (r *IntervalQueryRuleMatch) isIntervalQueryRule() bool {
return true
}
================================================
FILE: search_queries_interval_rules_prefix.go
================================================
package elastic
var (
_ IntervalQueryRule = (*IntervalQueryRulePrefix)(nil)
)
// IntervalQueryRulePrefix is an implementation of IntervalQueryRule.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.5/query-dsl-intervals-query.html#intervals-prefix
// for details.
type IntervalQueryRulePrefix struct {
prefix string
analyzer string
useField string
}
// NewIntervalQueryRulePrefix initializes and returns a new instance
// of IntervalQueryRulePrefix.
func NewIntervalQueryRulePrefix(prefix string) *IntervalQueryRulePrefix {
return &IntervalQueryRulePrefix{prefix: prefix}
}
// Analyzer specifies the analyzer used to analyze terms in the query.
func (r *IntervalQueryRulePrefix) Analyzer(analyzer string) *IntervalQueryRulePrefix {
r.analyzer = analyzer
return r
}
// UseField, if specified, matches the intervals from this field rather than
// the top-level field.
func (r *IntervalQueryRulePrefix) UseField(useField string) *IntervalQueryRulePrefix {
r.useField = useField
return r
}
// Source returns JSON for the function score query.
func (r *IntervalQueryRulePrefix) Source() (interface{}, error) {
source := make(map[string]interface{})
source["prefix"] = r.prefix
if r.analyzer != "" {
source["analyzer"] = r.analyzer
}
if r.useField != "" {
source["use_field"] = r.useField
}
return map[string]interface{}{
"prefix": source,
}, nil
}
// isIntervalQueryRule implements the marker interface.
func (r *IntervalQueryRulePrefix) isIntervalQueryRule() bool {
return true
}
================================================
FILE: search_queries_interval_rules_wildcard.go
================================================
package elastic
var (
_ IntervalQueryRule = (*IntervalQueryRuleWildcard)(nil)
)
// IntervalQueryRuleWildcard is an implementation of IntervalQueryRule.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.5/query-dsl-intervals-query.html#intervals-wildcard
// for details.
type IntervalQueryRuleWildcard struct {
pattern string
analyzer string
useField string
}
// NewIntervalQueryRuleWildcard initializes and returns a new instance
// of IntervalQueryRuleWildcard.
func NewIntervalQueryRuleWildcard(pattern string) *IntervalQueryRuleWildcard {
return &IntervalQueryRuleWildcard{pattern: pattern}
}
// Analyzer specifies the analyzer used to analyze terms in the query.
func (r *IntervalQueryRuleWildcard) Analyzer(analyzer string) *IntervalQueryRuleWildcard {
r.analyzer = analyzer
return r
}
// UseField, if specified, matches the intervals from this field rather than
// the top-level field.
func (r *IntervalQueryRuleWildcard) UseField(useField string) *IntervalQueryRuleWildcard {
r.useField = useField
return r
}
// Source returns JSON for the function score query.
func (r *IntervalQueryRuleWildcard) Source() (interface{}, error) {
source := make(map[string]interface{})
source["pattern"] = r.pattern
if r.analyzer != "" {
source["analyzer"] = r.analyzer
}
if r.useField != "" {
source["use_field"] = r.useField
}
return map[string]interface{}{
"wildcard": source,
}, nil
}
// isIntervalQueryRule implements the marker interface.
func (r *IntervalQueryRuleWildcard) isIntervalQueryRule() bool {
return true
}
================================================
FILE: search_queries_interval_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestIntervalQuery(t *testing.T) {
q := NewIntervalQuery(
"my_text",
NewIntervalQueryRuleAllOf(
NewIntervalQueryRuleMatch("my favorite food").
MaxGaps(0).
Ordered(true).
Filter(
NewIntervalQueryFilter().
NotContaining(
NewIntervalQueryRuleMatch("salty"),
),
),
NewIntervalQueryRuleAnyOf(
NewIntervalQueryRuleMatch("hot water"),
NewIntervalQueryRuleMatch("cold porridge"),
),
).Ordered(true),
)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"intervals":{"my_text":{"all_of":{"intervals":[{"match":{"filter":{"not_containing":{"match":{"query":"salty"}}},"max_gaps":0,"ordered":true,"query":"my favorite food"}},{"any_of":{"intervals":[{"match":{"query":"hot water"}},{"match":{"query":"cold porridge"}}]}}],"ordered":true}}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_queries_match.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// MatchQuery is a family of queries that accepts text/numerics/dates,
// analyzes them, and constructs a query.
//
// To create a new MatchQuery, use NewMatchQuery. To create specific types
// of queries, e.g. a match_phrase query, use NewMatchPhrQuery(...).Type("phrase"),
// or use one of the shortcuts e.g. NewMatchPhraseQuery(...).
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-match-query.html
type MatchQuery struct {
name string
text interface{}
operator string // or / and
analyzer string
boost *float64
fuzziness string
prefixLength *int
maxExpansions *int
minimumShouldMatch string
fuzzyRewrite string
lenient *bool
fuzzyTranspositions *bool
zeroTermsQuery string
cutoffFrequency *float64
queryName string
}
// NewMatchQuery creates and initializes a new MatchQuery.
func NewMatchQuery(name string, text interface{}) *MatchQuery {
return &MatchQuery{name: name, text: text}
}
// Operator sets the operator to use when using a boolean query.
// Can be "AND" or "OR" (default).
func (q *MatchQuery) Operator(operator string) *MatchQuery {
q.operator = operator
return q
}
// Analyzer explicitly sets the analyzer to use. It defaults to use explicit
// mapping config for the field, or, if not set, the default search analyzer.
func (q *MatchQuery) Analyzer(analyzer string) *MatchQuery {
q.analyzer = analyzer
return q
}
// Fuzziness sets the fuzziness when evaluated to a fuzzy query type.
// Defaults to "AUTO".
func (q *MatchQuery) Fuzziness(fuzziness string) *MatchQuery {
q.fuzziness = fuzziness
return q
}
// PrefixLength sets the length of a length of common (non-fuzzy)
// prefix for fuzzy match queries. It must be non-negative.
func (q *MatchQuery) PrefixLength(prefixLength int) *MatchQuery {
q.prefixLength = &prefixLength
return q
}
// MaxExpansions is used with fuzzy or prefix type queries. It specifies
// the number of term expansions to use. It defaults to unbounded so that
// its recommended to set it to a reasonable value for faster execution.
func (q *MatchQuery) MaxExpansions(maxExpansions int) *MatchQuery {
q.maxExpansions = &maxExpansions
return q
}
// CutoffFrequency can be a value in [0..1] (or an absolute number >=1).
// It represents the maximum treshold of a terms document frequency to be
// considered a low frequency term.
func (q *MatchQuery) CutoffFrequency(cutoff float64) *MatchQuery {
q.cutoffFrequency = &cutoff
return q
}
// MinimumShouldMatch sets the optional minimumShouldMatch value to
// apply to the query.
func (q *MatchQuery) MinimumShouldMatch(minimumShouldMatch string) *MatchQuery {
q.minimumShouldMatch = minimumShouldMatch
return q
}
// FuzzyRewrite sets the fuzzy_rewrite parameter controlling how the
// fuzzy query will get rewritten.
func (q *MatchQuery) FuzzyRewrite(fuzzyRewrite string) *MatchQuery {
q.fuzzyRewrite = fuzzyRewrite
return q
}
// FuzzyTranspositions sets whether transpositions are supported in
// fuzzy queries.
//
// The default metric used by fuzzy queries to determine a match is
// the Damerau-Levenshtein distance formula which supports transpositions.
// Setting transposition to false will
// * switch to classic Levenshtein distance.
// * If not set, Damerau-Levenshtein distance metric will be used.
func (q *MatchQuery) FuzzyTranspositions(fuzzyTranspositions bool) *MatchQuery {
q.fuzzyTranspositions = &fuzzyTranspositions
return q
}
// Lenient specifies whether format based failures will be ignored.
func (q *MatchQuery) Lenient(lenient bool) *MatchQuery {
q.lenient = &lenient
return q
}
// ZeroTermsQuery can be "all" or "none".
func (q *MatchQuery) ZeroTermsQuery(zeroTermsQuery string) *MatchQuery {
q.zeroTermsQuery = zeroTermsQuery
return q
}
// Boost sets the boost to apply to this query.
func (q *MatchQuery) Boost(boost float64) *MatchQuery {
q.boost = &boost
return q
}
// QueryName sets the query name for the filter that can be used when
// searching for matched filters per hit.
func (q *MatchQuery) QueryName(queryName string) *MatchQuery {
q.queryName = queryName
return q
}
// Source returns JSON for the function score query.
func (q *MatchQuery) Source() (interface{}, error) {
// {"match":{"name":{"query":"value","type":"boolean/phrase"}}}
source := make(map[string]interface{})
match := make(map[string]interface{})
source["match"] = match
query := make(map[string]interface{})
match[q.name] = query
query["query"] = q.text
if q.operator != "" {
query["operator"] = q.operator
}
if q.analyzer != "" {
query["analyzer"] = q.analyzer
}
if q.fuzziness != "" {
query["fuzziness"] = q.fuzziness
}
if q.prefixLength != nil {
query["prefix_length"] = *q.prefixLength
}
if q.maxExpansions != nil {
query["max_expansions"] = *q.maxExpansions
}
if q.minimumShouldMatch != "" {
query["minimum_should_match"] = q.minimumShouldMatch
}
if q.fuzzyRewrite != "" {
query["fuzzy_rewrite"] = q.fuzzyRewrite
}
if q.lenient != nil {
query["lenient"] = *q.lenient
}
if q.fuzzyTranspositions != nil {
query["fuzzy_transpositions"] = *q.fuzzyTranspositions
}
if q.zeroTermsQuery != "" {
query["zero_terms_query"] = q.zeroTermsQuery
}
if q.cutoffFrequency != nil {
query["cutoff_frequency"] = *q.cutoffFrequency
}
if q.boost != nil {
query["boost"] = *q.boost
}
if q.queryName != "" {
query["_name"] = q.queryName
}
return source, nil
}
================================================
FILE: search_queries_match_all.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import "encoding/json"
// MatchAllQuery is the most simple query, which matches all documents,
// giving them all a _score of 1.0.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-match-all-query.html
type MatchAllQuery struct {
boost *float64
queryName string
}
// NewMatchAllQuery creates and initializes a new match all query.
func NewMatchAllQuery() *MatchAllQuery {
return &MatchAllQuery{}
}
// Boost sets the boost for this query. Documents matching this query will
// (in addition to the normal weightings) have their score multiplied by the
// boost provided.
func (q *MatchAllQuery) Boost(boost float64) *MatchAllQuery {
q.boost = &boost
return q
}
// QueryName sets the query name.
func (q *MatchAllQuery) QueryName(name string) *MatchAllQuery {
q.queryName = name
return q
}
// Source returns JSON for the match all query.
func (q *MatchAllQuery) Source() (interface{}, error) {
// {
// "match_all" : { ... }
// }
source := make(map[string]interface{})
params := make(map[string]interface{})
source["match_all"] = params
if q.boost != nil {
params["boost"] = *q.boost
}
if q.queryName != "" {
params["_name"] = q.queryName
}
return source, nil
}
// MarshalJSON enables serializing the type as JSON.
func (q *MatchAllQuery) MarshalJSON() ([]byte, error) {
if q == nil {
return nilByte, nil
}
src, err := q.Source()
if err != nil {
return nil, err
}
return json.Marshal(src)
}
================================================
FILE: search_queries_match_all_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestMatchAllQuery(t *testing.T) {
q := NewMatchAllQuery()
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"match_all":{}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestMatchAllQueryWithBoost(t *testing.T) {
q := NewMatchAllQuery().Boost(3.14)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"match_all":{"boost":3.14}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestMatchAllQueryWithQueryName(t *testing.T) {
q := NewMatchAllQuery().QueryName("qname")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"match_all":{"_name":"qname"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestMatchAllMarshalJSON(t *testing.T) {
in := NewMatchAllQuery().Boost(3.14)
data, err := json.Marshal(in)
if err != nil {
t.Fatal(err)
}
got := string(data)
expected := `{"match_all":{"boost":3.14}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_queries_match_bool_prefix.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// MatchBoolPrefixQuery query analyzes its input and constructs a bool query from the terms.
// Each term except the last is used in a term query. The last term is used in a prefix query.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-match-bool-prefix-query.html
type MatchBoolPrefixQuery struct {
name string
queryText interface{}
analyzer string
minimumShouldMatch string
operator string
fuzziness string
prefixLength *int
maxExpansions *int
fuzzyTranspositions *bool
fuzzyRewrite string
boost *float64
}
// NewMatchBoolPrefixQuery creates and initializes a new MatchBoolPrefixQuery.
func NewMatchBoolPrefixQuery(name string, queryText interface{}) *MatchBoolPrefixQuery {
return &MatchBoolPrefixQuery{name: name, queryText: queryText}
}
// Analyzer explicitly sets the analyzer to use. It defaults to use explicit
// mapping config for the field, or, if not set, the default search analyzer.
func (q *MatchBoolPrefixQuery) Analyzer(analyzer string) *MatchBoolPrefixQuery {
q.analyzer = analyzer
return q
}
// MinimumShouldMatch sets the optional minimumShouldMatch value to apply to the query.
func (q *MatchBoolPrefixQuery) MinimumShouldMatch(minimumShouldMatch string) *MatchBoolPrefixQuery {
q.minimumShouldMatch = minimumShouldMatch
return q
}
// Operator sets the operator to use when using a boolean query.
// Can be "AND" or "OR" (default).
func (q *MatchBoolPrefixQuery) Operator(operator string) *MatchBoolPrefixQuery {
q.operator = operator
return q
}
// Fuzziness sets the edit distance for fuzzy queries. Default is "AUTO".
func (q *MatchBoolPrefixQuery) Fuzziness(fuzziness string) *MatchBoolPrefixQuery {
q.fuzziness = fuzziness
return q
}
// PrefixLength is the number of beginning characters left unchanged for fuzzy matching. Defaults to 0.
func (q *MatchBoolPrefixQuery) PrefixLength(prefixLength int) *MatchBoolPrefixQuery {
q.prefixLength = &prefixLength
return q
}
// MaxExpansions sets the number of term expansions to use.
func (q *MatchBoolPrefixQuery) MaxExpansions(n int) *MatchBoolPrefixQuery {
q.maxExpansions = &n
return q
}
// FuzzyTranspositions if true, edits for fuzzy matching include transpositions of two adjacent
// characters (ab → ba). Defaults to true.
func (q *MatchBoolPrefixQuery) FuzzyTranspositions(fuzzyTranspositions bool) *MatchBoolPrefixQuery {
q.fuzzyTranspositions = &fuzzyTranspositions
return q
}
// FuzzyRewrite sets the fuzzy_rewrite parameter controlling how the
// fuzzy query will get rewritten.
func (q *MatchBoolPrefixQuery) FuzzyRewrite(fuzzyRewrite string) *MatchBoolPrefixQuery {
q.fuzzyRewrite = fuzzyRewrite
return q
}
// Boost sets the boost to apply to this query.
func (q *MatchBoolPrefixQuery) Boost(boost float64) *MatchBoolPrefixQuery {
q.boost = &boost
return q
}
// Source returns JSON for the function score query.
func (q *MatchBoolPrefixQuery) Source() (interface{}, error) {
source := make(map[string]interface{})
match := make(map[string]interface{})
source["match_bool_prefix"] = match
query := make(map[string]interface{})
match[q.name] = query
query["query"] = q.queryText
if q.analyzer != "" {
query["analyzer"] = q.analyzer
}
if q.minimumShouldMatch != "" {
query["minimum_should_match"] = q.minimumShouldMatch
}
if q.operator != "" {
query["operator"] = q.operator
}
if q.fuzziness != "" {
query["fuzziness"] = q.fuzziness
}
if q.prefixLength != nil {
query["prefix_length"] = *q.prefixLength
}
if q.maxExpansions != nil {
query["max_expansions"] = *q.maxExpansions
}
if q.fuzzyTranspositions != nil {
query["fuzzy_transpositions"] = *q.fuzzyTranspositions
}
if q.fuzzyRewrite != "" {
query["fuzzy_rewrite"] = q.fuzzyRewrite
}
if q.boost != nil {
query["boost"] = *q.boost
}
return source, nil
}
================================================
FILE: search_queries_match_bool_prefix_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestMatchBoolPrefixQuery(t *testing.T) {
q := NewMatchBoolPrefixQuery("query_name", "this is a test").
Analyzer("custom_analyzer").
MinimumShouldMatch("75%").
Operator("AND").
Fuzziness("AUTO").
PrefixLength(1).
MaxExpansions(5).
FuzzyTranspositions(false).
FuzzyRewrite("constant_score").
Boost(0.3)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"match_bool_prefix":{"query_name":{"analyzer":"custom_analyzer","boost":0.3,"fuzziness":"AUTO","fuzzy_rewrite":"constant_score","fuzzy_transpositions":false,"max_expansions":5,"minimum_should_match":"75%","operator":"AND","prefix_length":1,"query":"this is a test"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_queries_match_none.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// MatchNoneQuery returns no documents. It is the inverse of
// MatchAllQuery.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-match-all-query.html
type MatchNoneQuery struct {
queryName string
}
// NewMatchNoneQuery creates and initializes a new match none query.
func NewMatchNoneQuery() *MatchNoneQuery {
return &MatchNoneQuery{}
}
// QueryName sets the query name.
func (q *MatchNoneQuery) QueryName(name string) *MatchNoneQuery {
q.queryName = name
return q
}
// Source returns JSON for the match none query.
func (q MatchNoneQuery) Source() (interface{}, error) {
// {
// "match_none" : { ... }
// }
source := make(map[string]interface{})
params := make(map[string]interface{})
source["match_none"] = params
if q.queryName != "" {
params["_name"] = q.queryName
}
return source, nil
}
================================================
FILE: search_queries_match_none_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestMatchNoneQuery(t *testing.T) {
q := NewMatchNoneQuery()
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"match_none":{}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestMatchNoneQueryWithQueryName(t *testing.T) {
q := NewMatchNoneQuery().QueryName("qname")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"match_none":{"_name":"qname"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_queries_match_phrase.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// MatchPhraseQuery analyzes the text and creates a phrase query out of
// the analyzed text.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-match-query-phrase.html
type MatchPhraseQuery struct {
name string
value interface{}
analyzer string
slop *int
boost *float64
queryName string
zeroTermsQuery string
}
// NewMatchPhraseQuery creates and initializes a new MatchPhraseQuery.
func NewMatchPhraseQuery(name string, value interface{}) *MatchPhraseQuery {
return &MatchPhraseQuery{name: name, value: value}
}
// Analyzer explicitly sets the analyzer to use. It defaults to use explicit
// mapping config for the field, or, if not set, the default search analyzer.
func (q *MatchPhraseQuery) Analyzer(analyzer string) *MatchPhraseQuery {
q.analyzer = analyzer
return q
}
// Slop sets the phrase slop if evaluated to a phrase query type.
func (q *MatchPhraseQuery) Slop(slop int) *MatchPhraseQuery {
q.slop = &slop
return q
}
// ZeroTermsQuery can be "all" or "none".
func (q *MatchPhraseQuery) ZeroTermsQuery(zeroTermsQuery string) *MatchPhraseQuery {
q.zeroTermsQuery = zeroTermsQuery
return q
}
// Boost sets the boost to apply to this query.
func (q *MatchPhraseQuery) Boost(boost float64) *MatchPhraseQuery {
q.boost = &boost
return q
}
// QueryName sets the query name for the filter that can be used when
// searching for matched filters per hit.
func (q *MatchPhraseQuery) QueryName(queryName string) *MatchPhraseQuery {
q.queryName = queryName
return q
}
// Source returns JSON for the function score query.
func (q *MatchPhraseQuery) Source() (interface{}, error) {
// {"match_phrase":{"name":{"query":"value","analyzer":"my_analyzer"}}}
source := make(map[string]interface{})
match := make(map[string]interface{})
source["match_phrase"] = match
query := make(map[string]interface{})
match[q.name] = query
query["query"] = q.value
if q.analyzer != "" {
query["analyzer"] = q.analyzer
}
if q.slop != nil {
query["slop"] = *q.slop
}
if q.zeroTermsQuery != "" {
query["zero_terms_query"] = q.zeroTermsQuery
}
if q.boost != nil {
query["boost"] = *q.boost
}
if q.queryName != "" {
query["_name"] = q.queryName
}
return source, nil
}
================================================
FILE: search_queries_match_phrase_prefix.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// MatchPhrasePrefixQuery is the same as match_phrase, except that it allows for
// prefix matches on the last term in the text.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-match-query-phrase-prefix.html
type MatchPhrasePrefixQuery struct {
name string
value interface{}
analyzer string
slop *int
maxExpansions *int
boost *float64
queryName string
}
// NewMatchPhrasePrefixQuery creates and initializes a new MatchPhrasePrefixQuery.
func NewMatchPhrasePrefixQuery(name string, value interface{}) *MatchPhrasePrefixQuery {
return &MatchPhrasePrefixQuery{name: name, value: value}
}
// Analyzer explicitly sets the analyzer to use. It defaults to use explicit
// mapping config for the field, or, if not set, the default search analyzer.
func (q *MatchPhrasePrefixQuery) Analyzer(analyzer string) *MatchPhrasePrefixQuery {
q.analyzer = analyzer
return q
}
// Slop sets the phrase slop if evaluated to a phrase query type.
func (q *MatchPhrasePrefixQuery) Slop(slop int) *MatchPhrasePrefixQuery {
q.slop = &slop
return q
}
// MaxExpansions sets the number of term expansions to use.
func (q *MatchPhrasePrefixQuery) MaxExpansions(n int) *MatchPhrasePrefixQuery {
q.maxExpansions = &n
return q
}
// Boost sets the boost to apply to this query.
func (q *MatchPhrasePrefixQuery) Boost(boost float64) *MatchPhrasePrefixQuery {
q.boost = &boost
return q
}
// QueryName sets the query name for the filter that can be used when
// searching for matched filters per hit.
func (q *MatchPhrasePrefixQuery) QueryName(queryName string) *MatchPhrasePrefixQuery {
q.queryName = queryName
return q
}
// Source returns JSON for the function score query.
func (q *MatchPhrasePrefixQuery) Source() (interface{}, error) {
// {"match_phrase_prefix":{"name":{"query":"value","max_expansions":10}}}
source := make(map[string]interface{})
match := make(map[string]interface{})
source["match_phrase_prefix"] = match
query := make(map[string]interface{})
match[q.name] = query
query["query"] = q.value
if q.analyzer != "" {
query["analyzer"] = q.analyzer
}
if q.slop != nil {
query["slop"] = *q.slop
}
if q.maxExpansions != nil {
query["max_expansions"] = *q.maxExpansions
}
if q.boost != nil {
query["boost"] = *q.boost
}
if q.queryName != "" {
query["_name"] = q.queryName
}
return source, nil
}
================================================
FILE: search_queries_match_phrase_prefix_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestMatchPhrasePrefixQuery(t *testing.T) {
q := NewMatchPhrasePrefixQuery("message", "this is a test").Boost(0.3).MaxExpansions(5)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"match_phrase_prefix":{"message":{"boost":0.3,"max_expansions":5,"query":"this is a test"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_queries_match_phrase_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestMatchPhraseQuery(t *testing.T) {
q := NewMatchPhraseQuery("message", "this is a test").
Analyzer("my_analyzer").
Boost(0.7)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"match_phrase":{"message":{"analyzer":"my_analyzer","boost":0.7,"query":"this is a test"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_queries_match_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestMatchQuery(t *testing.T) {
q := NewMatchQuery("message", "this is a test")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"match":{"message":{"query":"this is a test"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestMatchQueryWithOptions(t *testing.T) {
q := NewMatchQuery("message", "this is a test").Analyzer("whitespace").Operator("or").Boost(2.5)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"match":{"message":{"analyzer":"whitespace","boost":2.5,"operator":"or","query":"this is a test"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestMatchQueryWithInt64(t *testing.T) {
q := NewMatchQuery("message", 459751182159713792)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"match":{"message":{"query":459751182159713792}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_queries_more_like_this.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import "errors"
// MoreLikeThis query (MLT Query) finds documents that are "like" a given
// set of documents. In order to do so, MLT selects a set of representative
// terms of these input documents, forms a query using these terms, executes
// the query and returns the results. The user controls the input documents,
// how the terms should be selected and how the query is formed.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-mlt-query.html
type MoreLikeThisQuery struct {
fields []string
docs []*MoreLikeThisQueryItem
unlikeDocs []*MoreLikeThisQueryItem
include *bool
minimumShouldMatch string
minTermFreq *int
maxQueryTerms *int
stopWords []string
minDocFreq *int
maxDocFreq *int
minWordLength *int
maxWordLength *int
boostTerms *float64
boost *float64
analyzer string
failOnUnsupportedField *bool
queryName string
}
// NewMoreLikeThisQuery creates and initializes a new MoreLikeThisQuery.
func NewMoreLikeThisQuery() *MoreLikeThisQuery {
return &MoreLikeThisQuery{
fields: make([]string, 0),
stopWords: make([]string, 0),
docs: make([]*MoreLikeThisQueryItem, 0),
unlikeDocs: make([]*MoreLikeThisQueryItem, 0),
}
}
// Field adds one or more field names to the query.
func (q *MoreLikeThisQuery) Field(fields ...string) *MoreLikeThisQuery {
q.fields = append(q.fields, fields...)
return q
}
// StopWord sets the stopwords. Any word in this set is considered
// "uninteresting" and ignored. Even if your Analyzer allows stopwords,
// you might want to tell the MoreLikeThis code to ignore them, as for
// the purposes of document similarity it seems reasonable to assume that
// "a stop word is never interesting".
func (q *MoreLikeThisQuery) StopWord(stopWords ...string) *MoreLikeThisQuery {
q.stopWords = append(q.stopWords, stopWords...)
return q
}
// LikeText sets the text to use in order to find documents that are "like" this.
func (q *MoreLikeThisQuery) LikeText(likeTexts ...string) *MoreLikeThisQuery {
for _, s := range likeTexts {
item := NewMoreLikeThisQueryItem().LikeText(s)
q.docs = append(q.docs, item)
}
return q
}
// LikeItems sets the documents to use in order to find documents that are "like" this.
func (q *MoreLikeThisQuery) LikeItems(docs ...*MoreLikeThisQueryItem) *MoreLikeThisQuery {
q.docs = append(q.docs, docs...)
return q
}
// IgnoreLikeText sets the text from which the terms should not be selected from.
func (q *MoreLikeThisQuery) IgnoreLikeText(ignoreLikeText ...string) *MoreLikeThisQuery {
for _, s := range ignoreLikeText {
item := NewMoreLikeThisQueryItem().LikeText(s)
q.unlikeDocs = append(q.unlikeDocs, item)
}
return q
}
// IgnoreLikeItems sets the documents from which the terms should not be selected from.
func (q *MoreLikeThisQuery) IgnoreLikeItems(ignoreDocs ...*MoreLikeThisQueryItem) *MoreLikeThisQuery {
q.unlikeDocs = append(q.unlikeDocs, ignoreDocs...)
return q
}
// Ids sets the document ids to use in order to find documents that are "like" this.
func (q *MoreLikeThisQuery) Ids(ids ...string) *MoreLikeThisQuery {
for _, id := range ids {
item := NewMoreLikeThisQueryItem().Id(id)
q.docs = append(q.docs, item)
}
return q
}
// Include specifies whether the input documents should also be included
// in the results returned. Defaults to false.
func (q *MoreLikeThisQuery) Include(include bool) *MoreLikeThisQuery {
q.include = &include
return q
}
// MinimumShouldMatch sets the number of terms that must match the generated
// query expressed in the common syntax for minimum should match.
// The default value is "30%".
//
// This used to be "PercentTermsToMatch" in Elasticsearch versions before 2.0.
func (q *MoreLikeThisQuery) MinimumShouldMatch(minimumShouldMatch string) *MoreLikeThisQuery {
q.minimumShouldMatch = minimumShouldMatch
return q
}
// MinTermFreq is the frequency below which terms will be ignored in the
// source doc. The default frequency is 2.
func (q *MoreLikeThisQuery) MinTermFreq(minTermFreq int) *MoreLikeThisQuery {
q.minTermFreq = &minTermFreq
return q
}
// MaxQueryTerms sets the maximum number of query terms that will be included
// in any generated query. It defaults to 25.
func (q *MoreLikeThisQuery) MaxQueryTerms(maxQueryTerms int) *MoreLikeThisQuery {
q.maxQueryTerms = &maxQueryTerms
return q
}
// MinDocFreq sets the frequency at which words will be ignored which do
// not occur in at least this many docs. The default is 5.
func (q *MoreLikeThisQuery) MinDocFreq(minDocFreq int) *MoreLikeThisQuery {
q.minDocFreq = &minDocFreq
return q
}
// MaxDocFreq sets the maximum frequency for which words may still appear.
// Words that appear in more than this many docs will be ignored.
// It defaults to unbounded.
func (q *MoreLikeThisQuery) MaxDocFreq(maxDocFreq int) *MoreLikeThisQuery {
q.maxDocFreq = &maxDocFreq
return q
}
// MinWordLength sets the minimum word length below which words will be
// ignored. It defaults to 0.
func (q *MoreLikeThisQuery) MinWordLength(minWordLength int) *MoreLikeThisQuery {
q.minWordLength = &minWordLength
return q
}
// MaxWordLength sets the maximum word length above which words will be ignored.
// Defaults to unbounded (0).
func (q *MoreLikeThisQuery) MaxWordLength(maxWordLength int) *MoreLikeThisQuery {
q.maxWordLength = &maxWordLength
return q
}
// BoostTerms sets the boost factor to use when boosting terms.
// It defaults to 1.
func (q *MoreLikeThisQuery) BoostTerms(boostTerms float64) *MoreLikeThisQuery {
q.boostTerms = &boostTerms
return q
}
// Analyzer specifies the analyzer that will be use to analyze the text.
// Defaults to the analyzer associated with the field.
func (q *MoreLikeThisQuery) Analyzer(analyzer string) *MoreLikeThisQuery {
q.analyzer = analyzer
return q
}
// Boost sets the boost for this query.
func (q *MoreLikeThisQuery) Boost(boost float64) *MoreLikeThisQuery {
q.boost = &boost
return q
}
// FailOnUnsupportedField indicates whether to fail or return no result
// when this query is run against a field which is not supported such as
// a binary/numeric field.
func (q *MoreLikeThisQuery) FailOnUnsupportedField(fail bool) *MoreLikeThisQuery {
q.failOnUnsupportedField = &fail
return q
}
// QueryName sets the query name for the filter that can be used when
// searching for matched_filters per hit.
func (q *MoreLikeThisQuery) QueryName(queryName string) *MoreLikeThisQuery {
q.queryName = queryName
return q
}
// Source creates the source for the MLT query.
// It may return an error if the caller forgot to specify any documents to
// be "liked" in the MoreLikeThisQuery.
func (q *MoreLikeThisQuery) Source() (interface{}, error) {
// {
// "match_all" : { ... }
// }
if len(q.docs) == 0 {
return nil, errors.New(`more_like_this requires some documents to be "liked"`)
}
source := make(map[string]interface{})
params := make(map[string]interface{})
source["more_like_this"] = params
if len(q.fields) > 0 {
params["fields"] = q.fields
}
var likes []interface{}
for _, doc := range q.docs {
src, err := doc.Source()
if err != nil {
return nil, err
}
likes = append(likes, src)
}
params["like"] = likes
if len(q.unlikeDocs) > 0 {
var dontLikes []interface{}
for _, doc := range q.unlikeDocs {
src, err := doc.Source()
if err != nil {
return nil, err
}
dontLikes = append(dontLikes, src)
}
params["unlike"] = dontLikes
}
if q.minimumShouldMatch != "" {
params["minimum_should_match"] = q.minimumShouldMatch
}
if q.minTermFreq != nil {
params["min_term_freq"] = *q.minTermFreq
}
if q.maxQueryTerms != nil {
params["max_query_terms"] = *q.maxQueryTerms
}
if len(q.stopWords) > 0 {
params["stop_words"] = q.stopWords
}
if q.minDocFreq != nil {
params["min_doc_freq"] = *q.minDocFreq
}
if q.maxDocFreq != nil {
params["max_doc_freq"] = *q.maxDocFreq
}
if q.minWordLength != nil {
params["min_word_length"] = *q.minWordLength
}
if q.maxWordLength != nil {
params["max_word_length"] = *q.maxWordLength
}
if q.boostTerms != nil {
params["boost_terms"] = *q.boostTerms
}
if q.boost != nil {
params["boost"] = *q.boost
}
if q.analyzer != "" {
params["analyzer"] = q.analyzer
}
if q.failOnUnsupportedField != nil {
params["fail_on_unsupported_field"] = *q.failOnUnsupportedField
}
if q.queryName != "" {
params["_name"] = q.queryName
}
if q.include != nil {
params["include"] = *q.include
}
return source, nil
}
// -- MoreLikeThisQueryItem --
// MoreLikeThisQueryItem represents a single item of a MoreLikeThisQuery
// to be "liked" or "unliked".
type MoreLikeThisQueryItem struct {
likeText string
index string
typ string
id string
doc interface{}
fields []string
routing string
fsc *FetchSourceContext
version int64
versionType string
}
// NewMoreLikeThisQueryItem creates and initializes a MoreLikeThisQueryItem.
func NewMoreLikeThisQueryItem() *MoreLikeThisQueryItem {
return &MoreLikeThisQueryItem{
version: -1,
}
}
// LikeText represents a text to be "liked".
func (item *MoreLikeThisQueryItem) LikeText(likeText string) *MoreLikeThisQueryItem {
item.likeText = likeText
return item
}
// Index represents the index of the item.
func (item *MoreLikeThisQueryItem) Index(index string) *MoreLikeThisQueryItem {
item.index = index
return item
}
// Type represents the document type of the item.
//
// Deprecated: Types are in the process of being removed.
func (item *MoreLikeThisQueryItem) Type(typ string) *MoreLikeThisQueryItem {
item.typ = typ
return item
}
// Id represents the document id of the item.
func (item *MoreLikeThisQueryItem) Id(id string) *MoreLikeThisQueryItem {
item.id = id
return item
}
// Doc represents a raw document template for the item.
func (item *MoreLikeThisQueryItem) Doc(doc interface{}) *MoreLikeThisQueryItem {
item.doc = doc
return item
}
// Fields represents the list of fields of the item.
func (item *MoreLikeThisQueryItem) Fields(fields ...string) *MoreLikeThisQueryItem {
item.fields = append(item.fields, fields...)
return item
}
// Routing sets the routing associated with the item.
func (item *MoreLikeThisQueryItem) Routing(routing string) *MoreLikeThisQueryItem {
item.routing = routing
return item
}
// FetchSourceContext represents the fetch source of the item which controls
// if and how _source should be returned.
func (item *MoreLikeThisQueryItem) FetchSourceContext(fsc *FetchSourceContext) *MoreLikeThisQueryItem {
item.fsc = fsc
return item
}
// Version specifies the version of the item.
func (item *MoreLikeThisQueryItem) Version(version int64) *MoreLikeThisQueryItem {
item.version = version
return item
}
// VersionType represents the version type of the item.
func (item *MoreLikeThisQueryItem) VersionType(versionType string) *MoreLikeThisQueryItem {
item.versionType = versionType
return item
}
// Source returns the JSON-serializable fragment of the entity.
func (item *MoreLikeThisQueryItem) Source() (interface{}, error) {
if item.likeText != "" {
return item.likeText, nil
}
source := make(map[string]interface{})
if item.index != "" {
source["_index"] = item.index
}
if item.typ != "" {
source["_type"] = item.typ
}
if item.id != "" {
source["_id"] = item.id
}
if item.doc != nil {
source["doc"] = item.doc
}
if len(item.fields) > 0 {
source["fields"] = item.fields
}
if item.routing != "" {
source["routing"] = item.routing
}
if item.fsc != nil {
src, err := item.fsc.Source()
if err != nil {
return nil, err
}
source["_source"] = src
}
if item.version >= 0 {
source["_version"] = item.version
}
if item.versionType != "" {
source["_version_type"] = item.versionType
}
return source, nil
}
================================================
FILE: search_queries_more_like_this_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"testing"
)
func TestMoreLikeThisQuerySourceWithLikeText(t *testing.T) {
q := NewMoreLikeThisQuery().LikeText("Golang topic").Field("message")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatal(err)
}
got := string(data)
expected := `{"more_like_this":{"fields":["message"],"like":["Golang topic"]}}`
if got != expected {
t.Fatalf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestMoreLikeThisQuerySourceWithLikeAndUnlikeItems(t *testing.T) {
q := NewMoreLikeThisQuery()
q = q.LikeItems(
NewMoreLikeThisQueryItem().Id("1"),
NewMoreLikeThisQueryItem().Index(testIndexName2).Type("comment").Id("2").Routing("routing_id"),
)
q = q.IgnoreLikeItems(NewMoreLikeThisQueryItem().Id("3"))
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatal(err)
}
got := string(data)
expected := `{"more_like_this":{"like":[{"_id":"1"},{"_id":"2","_index":"elastic-test2","_type":"comment","routing":"routing_id"}],"unlike":[{"_id":"3"}]}}`
if got != expected {
t.Fatalf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestMoreLikeThisQuery(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
tweet2 := tweet{User: "olivere", Message: "Another Golang topic."}
tweet3 := tweet{User: "sandrae", Message: "Cycling is fun."}
// Add all documents
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("3").BodyJson(&tweet3).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Common query
mltq := NewMoreLikeThisQuery().LikeText("Golang topic").Field("message")
res, err := client.Search().
Index(testIndexName).
Query(mltq).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if res.Hits == nil {
t.Errorf("expected SearchResult.Hits != nil; got nil")
}
}
================================================
FILE: search_queries_multi_match.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"fmt"
"strings"
)
// MultiMatchQuery builds on the MatchQuery to allow multi-field queries.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-multi-match-query.html
type MultiMatchQuery struct {
text interface{}
fields []string
fieldBoosts map[string]*float64
typ string // best_fields, boolean, most_fields, cross_fields, phrase, phrase_prefix
operator string // AND or OR
analyzer string
boost *float64
slop *int
fuzziness string
prefixLength *int
maxExpansions *int
minimumShouldMatch string
rewrite string
fuzzyRewrite string
tieBreaker *float64
lenient *bool
cutoffFrequency *float64
zeroTermsQuery string
queryName string
}
// MultiMatchQuery creates and initializes a new MultiMatchQuery.
func NewMultiMatchQuery(text interface{}, fields ...string) *MultiMatchQuery {
q := &MultiMatchQuery{
text: text,
fieldBoosts: make(map[string]*float64),
}
q.fields = append(q.fields, fields...)
return q
}
// Field adds a field to run the multi match against.
func (q *MultiMatchQuery) Field(field string) *MultiMatchQuery {
q.fields = append(q.fields, field)
return q
}
// FieldWithBoost adds a field to run the multi match against with a specific boost.
func (q *MultiMatchQuery) FieldWithBoost(field string, boost float64) *MultiMatchQuery {
q.fields = append(q.fields, field)
q.fieldBoosts[field] = &boost
return q
}
// Type can be "best_fields", "boolean", "most_fields", "cross_fields",
// "phrase", "phrase_prefix" or "bool_prefix"
func (q *MultiMatchQuery) Type(typ string) *MultiMatchQuery {
switch strings.ToLower(typ) {
default: // best_fields / boolean
q.typ = "best_fields"
case "most_fields":
q.typ = "most_fields"
case "cross_fields":
q.typ = "cross_fields"
case "phrase":
q.typ = "phrase"
case "phrase_prefix":
q.typ = "phrase_prefix"
case "bool_prefix":
q.typ = "bool_prefix"
}
return q
}
// Operator sets the operator to use when using boolean query.
// It can be either AND or OR (default).
func (q *MultiMatchQuery) Operator(operator string) *MultiMatchQuery {
q.operator = operator
return q
}
// Analyzer sets the analyzer to use explicitly. It defaults to use explicit
// mapping config for the field, or, if not set, the default search analyzer.
func (q *MultiMatchQuery) Analyzer(analyzer string) *MultiMatchQuery {
q.analyzer = analyzer
return q
}
// Boost sets the boost for this query.
func (q *MultiMatchQuery) Boost(boost float64) *MultiMatchQuery {
q.boost = &boost
return q
}
// Slop sets the phrase slop if evaluated to a phrase query type.
func (q *MultiMatchQuery) Slop(slop int) *MultiMatchQuery {
q.slop = &slop
return q
}
// Fuzziness sets the fuzziness used when evaluated to a fuzzy query type.
// It defaults to "AUTO".
func (q *MultiMatchQuery) Fuzziness(fuzziness string) *MultiMatchQuery {
q.fuzziness = fuzziness
return q
}
// PrefixLength for the fuzzy process.
func (q *MultiMatchQuery) PrefixLength(prefixLength int) *MultiMatchQuery {
q.prefixLength = &prefixLength
return q
}
// MaxExpansions is the number of term expansions to use when using fuzzy
// or prefix type query. It defaults to unbounded so it's recommended
// to set it to a reasonable value for faster execution.
func (q *MultiMatchQuery) MaxExpansions(maxExpansions int) *MultiMatchQuery {
q.maxExpansions = &maxExpansions
return q
}
// MinimumShouldMatch represents the minimum number of optional should clauses
// to match.
func (q *MultiMatchQuery) MinimumShouldMatch(minimumShouldMatch string) *MultiMatchQuery {
q.minimumShouldMatch = minimumShouldMatch
return q
}
func (q *MultiMatchQuery) Rewrite(rewrite string) *MultiMatchQuery {
q.rewrite = rewrite
return q
}
func (q *MultiMatchQuery) FuzzyRewrite(fuzzyRewrite string) *MultiMatchQuery {
q.fuzzyRewrite = fuzzyRewrite
return q
}
// TieBreaker for "best-match" disjunction queries (OR queries).
// The tie breaker capability allows documents that match more than one
// query clause (in this case on more than one field) to be scored better
// than documents that match only the best of the fields, without confusing
// this with the better case of two distinct matches in the multiple fields.
//
// A tie-breaker value of 1.0 is interpreted as a signal to score queries as
// "most-match" queries where all matching query clauses are considered for scoring.
func (q *MultiMatchQuery) TieBreaker(tieBreaker float64) *MultiMatchQuery {
q.tieBreaker = &tieBreaker
return q
}
// Lenient indicates whether format based failures will be ignored.
func (q *MultiMatchQuery) Lenient(lenient bool) *MultiMatchQuery {
q.lenient = &lenient
return q
}
// CutoffFrequency sets a cutoff value in [0..1] (or absolute number >=1)
// representing the maximum threshold of a terms document frequency to be
// considered a low frequency term.
func (q *MultiMatchQuery) CutoffFrequency(cutoff float64) *MultiMatchQuery {
q.cutoffFrequency = &cutoff
return q
}
// ZeroTermsQuery can be "all" or "none".
func (q *MultiMatchQuery) ZeroTermsQuery(zeroTermsQuery string) *MultiMatchQuery {
q.zeroTermsQuery = zeroTermsQuery
return q
}
// QueryName sets the query name for the filter that can be used when
// searching for matched filters per hit.
func (q *MultiMatchQuery) QueryName(queryName string) *MultiMatchQuery {
q.queryName = queryName
return q
}
// Source returns JSON for the query.
func (q *MultiMatchQuery) Source() (interface{}, error) {
//
// {
// "multi_match" : {
// "query" : "this is a test",
// "fields" : [ "subject", "message" ]
// }
// }
source := make(map[string]interface{})
multiMatch := make(map[string]interface{})
source["multi_match"] = multiMatch
multiMatch["query"] = q.text
var fields []string
for _, field := range q.fields {
if boost, found := q.fieldBoosts[field]; found {
if boost != nil {
fields = append(fields, fmt.Sprintf("%s^%f", field, *boost))
} else {
fields = append(fields, field)
}
} else {
fields = append(fields, field)
}
}
if fields == nil {
multiMatch["fields"] = []string{}
} else {
multiMatch["fields"] = fields
}
if q.typ != "" {
multiMatch["type"] = q.typ
}
if q.operator != "" {
multiMatch["operator"] = q.operator
}
if q.analyzer != "" {
multiMatch["analyzer"] = q.analyzer
}
if q.boost != nil {
multiMatch["boost"] = *q.boost
}
if q.slop != nil {
multiMatch["slop"] = *q.slop
}
if q.fuzziness != "" {
multiMatch["fuzziness"] = q.fuzziness
}
if q.prefixLength != nil {
multiMatch["prefix_length"] = *q.prefixLength
}
if q.maxExpansions != nil {
multiMatch["max_expansions"] = *q.maxExpansions
}
if q.minimumShouldMatch != "" {
multiMatch["minimum_should_match"] = q.minimumShouldMatch
}
if q.rewrite != "" {
multiMatch["rewrite"] = q.rewrite
}
if q.fuzzyRewrite != "" {
multiMatch["fuzzy_rewrite"] = q.fuzzyRewrite
}
if q.tieBreaker != nil {
multiMatch["tie_breaker"] = *q.tieBreaker
}
if q.lenient != nil {
multiMatch["lenient"] = *q.lenient
}
if q.cutoffFrequency != nil {
multiMatch["cutoff_frequency"] = *q.cutoffFrequency
}
if q.zeroTermsQuery != "" {
multiMatch["zero_terms_query"] = q.zeroTermsQuery
}
if q.queryName != "" {
multiMatch["_name"] = q.queryName
}
return source, nil
}
================================================
FILE: search_queries_multi_match_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestMultiMatchQuery(t *testing.T) {
q := NewMultiMatchQuery("this is a test", "subject", "message")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"multi_match":{"fields":["subject","message"],"query":"this is a test"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestMultiMatchQueryWithNoFields(t *testing.T) {
q := NewMultiMatchQuery("accident")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"multi_match":{"fields":[],"query":"accident"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestMultiMatchQueryBestFields(t *testing.T) {
q := NewMultiMatchQuery("this is a test", "subject", "message").Type("best_fields").TieBreaker(0)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"multi_match":{"fields":["subject","message"],"query":"this is a test","tie_breaker":0,"type":"best_fields"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestMultiMatchQueryMostFields(t *testing.T) {
q := NewMultiMatchQuery("this is a test", "subject", "message").Type("most_fields").TieBreaker(1)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"multi_match":{"fields":["subject","message"],"query":"this is a test","tie_breaker":1,"type":"most_fields"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestMultiMatchQueryCrossFields(t *testing.T) {
q := NewMultiMatchQuery("this is a test", "subject", "message").Type("cross_fields").TieBreaker(0)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"multi_match":{"fields":["subject","message"],"query":"this is a test","tie_breaker":0,"type":"cross_fields"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestMultiMatchQueryPhrase(t *testing.T) {
q := NewMultiMatchQuery("this is a test", "subject", "message").Type("phrase").TieBreaker(0)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"multi_match":{"fields":["subject","message"],"query":"this is a test","tie_breaker":0,"type":"phrase"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestMultiMatchQueryPhrasePrefix(t *testing.T) {
q := NewMultiMatchQuery("this is a test", "subject", "message").Type("phrase_prefix").TieBreaker(0)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"multi_match":{"fields":["subject","message"],"query":"this is a test","tie_breaker":0,"type":"phrase_prefix"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestMultiMatchQueryBestFieldsWithCustomTieBreaker(t *testing.T) {
q := NewMultiMatchQuery("this is a test", "subject", "message").
Type("best_fields").
TieBreaker(0.3)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"multi_match":{"fields":["subject","message"],"query":"this is a test","tie_breaker":0.3,"type":"best_fields"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestMultiMatchQueryBoolPrefix(t *testing.T) {
q := NewMultiMatchQuery("this is a test", "subject", "message").Type("bool_prefix").TieBreaker(1)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"multi_match":{"fields":["subject","message"],"query":"this is a test","tie_breaker":1,"type":"bool_prefix"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestMultiMatchQueryOptions(t *testing.T) {
tests := []struct {
Query *MultiMatchQuery
Want string
}{
{
Query: NewMultiMatchQuery("this is a test", "message"),
Want: `{"multi_match":{"fields":["message"],"query":"this is a test"}}`,
},
{
Query: NewMultiMatchQuery("this is a test", "message").Type("best_fields").TieBreaker(0),
Want: `{"multi_match":{"fields":["message"],"query":"this is a test","tie_breaker":0,"type":"best_fields"}}`,
},
{
Query: NewMultiMatchQuery("this is a test", "message").Type("best_fields").TieBreaker(0.5),
Want: `{"multi_match":{"fields":["message"],"query":"this is a test","tie_breaker":0.5,"type":"best_fields"}}`,
},
{
Query: NewMultiMatchQuery("this is a test", "message").TieBreaker(0.5).Type("best_fields"),
Want: `{"multi_match":{"fields":["message"],"query":"this is a test","tie_breaker":0.5,"type":"best_fields"}}`,
},
{
Query: NewMultiMatchQuery("this is a test", "message").Type("cross_fields").TieBreaker(0.5),
Want: `{"multi_match":{"fields":["message"],"query":"this is a test","tie_breaker":0.5,"type":"cross_fields"}}`,
},
{
Query: NewMultiMatchQuery("this is a test", "message").TieBreaker(0.5).Type("cross_fields"),
Want: `{"multi_match":{"fields":["message"],"query":"this is a test","tie_breaker":0.5,"type":"cross_fields"}}`,
},
}
for i, tt := range tests {
src, err := tt.Query.Source()
if err != nil {
t.Fatalf("#%d: %v", i, err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("#%d: marshaling to JSON failed: %v", i, err)
}
if want, have := tt.Want, string(data); want != have {
t.Errorf("#%d: want\n%s\n, have:\n%s", i, want, have)
}
}
}
func TestMultiMatchQueryDefaultTieBreaker(t *testing.T) {
// should not add tie_breaker field within the query DSL unless specified
q := NewMultiMatchQuery("this is a test", "subject", "message").Type("bool_prefix")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"multi_match":{"fields":["subject","message"],"query":"this is a test","type":"bool_prefix"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_queries_nested.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// NestedQuery allows to query nested objects / docs.
// The query is executed against the nested objects / docs as if they were
// indexed as separate docs (they are, internally) and resulting in the
// root parent doc (or parent nested mapping).
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-nested-query.html
type NestedQuery struct {
query Query
path string
scoreMode string
boost *float64
queryName string
innerHit *InnerHit
ignoreUnmapped *bool
}
// NewNestedQuery creates and initializes a new NestedQuery.
func NewNestedQuery(path string, query Query) *NestedQuery {
return &NestedQuery{path: path, query: query}
}
// ScoreMode specifies the score mode.
func (q *NestedQuery) ScoreMode(scoreMode string) *NestedQuery {
q.scoreMode = scoreMode
return q
}
// Boost sets the boost for this query.
func (q *NestedQuery) Boost(boost float64) *NestedQuery {
q.boost = &boost
return q
}
// QueryName sets the query name for the filter that can be used
// when searching for matched_filters per hit
func (q *NestedQuery) QueryName(queryName string) *NestedQuery {
q.queryName = queryName
return q
}
// InnerHit sets the inner hit definition in the scope of this nested query
// and reusing the defined path and query.
func (q *NestedQuery) InnerHit(innerHit *InnerHit) *NestedQuery {
q.innerHit = innerHit
return q
}
// IgnoreUnmapped sets the ignore_unmapped option for the filter that ignores
// unmapped nested fields
func (q *NestedQuery) IgnoreUnmapped(value bool) *NestedQuery {
q.ignoreUnmapped = &value
return q
}
// Source returns JSON for the query.
func (q *NestedQuery) Source() (interface{}, error) {
query := make(map[string]interface{})
nq := make(map[string]interface{})
query["nested"] = nq
src, err := q.query.Source()
if err != nil {
return nil, err
}
nq["query"] = src
nq["path"] = q.path
if q.scoreMode != "" {
nq["score_mode"] = q.scoreMode
}
if q.boost != nil {
nq["boost"] = *q.boost
}
if q.queryName != "" {
nq["_name"] = q.queryName
}
if q.ignoreUnmapped != nil {
nq["ignore_unmapped"] = *q.ignoreUnmapped
}
if q.innerHit != nil {
src, err := q.innerHit.Source()
if err != nil {
return nil, err
}
nq["inner_hits"] = src
}
return query, nil
}
================================================
FILE: search_queries_nested_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestNestedQuery(t *testing.T) {
bq := NewBoolQuery()
bq = bq.Must(NewTermQuery("obj1.name", "blue"))
bq = bq.Must(NewRangeQuery("obj1.count").Gt(5))
q := NewNestedQuery("obj1", bq).QueryName("qname")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"nested":{"_name":"qname","path":"obj1","query":{"bool":{"must":[{"term":{"obj1.name":"blue"}},{"range":{"obj1.count":{"from":5,"include_lower":false,"include_upper":true,"to":null}}}]}}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestNestedQueryWithInnerHit(t *testing.T) {
bq := NewBoolQuery()
bq = bq.Must(NewTermQuery("obj1.name", "blue"))
bq = bq.Must(NewRangeQuery("obj1.count").Gt(5))
q := NewNestedQuery("obj1", bq)
q = q.QueryName("qname")
q = q.InnerHit(NewInnerHit().Name("comments").Query(NewTermQuery("user", "olivere")))
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"nested":{"_name":"qname","inner_hits":{"name":"comments","query":{"term":{"user":"olivere"}}},"path":"obj1","query":{"bool":{"must":[{"term":{"obj1.name":"blue"}},{"range":{"obj1.count":{"from":5,"include_lower":false,"include_upper":true,"to":null}}}]}}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestNestedQueryWithIgnoreUnmapped(t *testing.T) {
var tests = []struct {
query *BoolQuery
expected string
}{
{
NewBoolQuery().Must(NewNestedQuery("path", NewTermQuery("test", "test"))),
`{"bool":{"must":{"nested":{"path":"path","query":{"term":{"test":"test"}}}}}}`,
},
{
NewBoolQuery().Must(NewNestedQuery("path", NewTermQuery("test", "test")).IgnoreUnmapped(true)),
`{"bool":{"must":{"nested":{"ignore_unmapped":true,"path":"path","query":{"term":{"test":"test"}}}}}}`,
},
{
NewBoolQuery().Must(NewNestedQuery("path", NewTermQuery("test", "test")).IgnoreUnmapped(false)),
`{"bool":{"must":{"nested":{"ignore_unmapped":false,"path":"path","query":{"term":{"test":"test"}}}}}}`,
},
}
for _, test := range tests {
src, err := test.query.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
if got != test.expected {
t.Errorf("expected\n%s\n,got:\n%s", test.expected, got)
}
}
}
================================================
FILE: search_queries_parent_id.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// ParentIdQuery can be used to find child documents which belong to a
// particular parent. Given the following mapping definition.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-parent-id-query.html
type ParentIdQuery struct {
typ string
id string
ignoreUnmapped *bool
boost *float64
queryName string
innerHit *InnerHit
}
// NewParentIdQuery creates and initializes a new parent_id query.
func NewParentIdQuery(typ, id string) *ParentIdQuery {
return &ParentIdQuery{
typ: typ,
id: id,
}
}
// Type sets the parent type.
func (q *ParentIdQuery) Type(typ string) *ParentIdQuery {
q.typ = typ
return q
}
// Id sets the id.
func (q *ParentIdQuery) Id(id string) *ParentIdQuery {
q.id = id
return q
}
// IgnoreUnmapped specifies whether unmapped types should be ignored.
// If set to false, the query failes when an unmapped type is found.
func (q *ParentIdQuery) IgnoreUnmapped(ignore bool) *ParentIdQuery {
q.ignoreUnmapped = &ignore
return q
}
// Boost sets the boost for this query.
func (q *ParentIdQuery) Boost(boost float64) *ParentIdQuery {
q.boost = &boost
return q
}
// QueryName specifies the query name for the filter that can be used when
// searching for matched filters per hit.
func (q *ParentIdQuery) QueryName(queryName string) *ParentIdQuery {
q.queryName = queryName
return q
}
// InnerHit sets the inner hit definition in the scope of this query and
// reusing the defined type and query.
func (q *ParentIdQuery) InnerHit(innerHit *InnerHit) *ParentIdQuery {
q.innerHit = innerHit
return q
}
// Source returns JSON for the parent_id query.
func (q *ParentIdQuery) Source() (interface{}, error) {
// {
// "parent_id" : {
// "type" : "blog",
// "id" : "1"
// }
// }
source := make(map[string]interface{})
query := make(map[string]interface{})
source["parent_id"] = query
query["type"] = q.typ
query["id"] = q.id
if q.boost != nil {
query["boost"] = *q.boost
}
if q.ignoreUnmapped != nil {
query["ignore_unmapped"] = *q.ignoreUnmapped
}
if q.queryName != "" {
query["_name"] = q.queryName
}
if q.innerHit != nil {
src, err := q.innerHit.Source()
if err != nil {
return nil, err
}
query["inner_hits"] = src
}
return source, nil
}
================================================
FILE: search_queries_parent_id_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestParentIdQueryTest(t *testing.T) {
tests := []struct {
Query Query
Expected string
}{
// #0
{
Query: NewParentIdQuery("blog_tag", "1"),
Expected: `{"parent_id":{"id":"1","type":"blog_tag"}}`,
},
// #1
{
Query: NewParentIdQuery("blog_tag", "1").IgnoreUnmapped(true),
Expected: `{"parent_id":{"id":"1","ignore_unmapped":true,"type":"blog_tag"}}`,
},
// #2
{
Query: NewParentIdQuery("blog_tag", "1").IgnoreUnmapped(false),
Expected: `{"parent_id":{"id":"1","ignore_unmapped":false,"type":"blog_tag"}}`,
},
// #3
{
Query: NewParentIdQuery("blog_tag", "1").IgnoreUnmapped(true).Boost(5).QueryName("my_parent_query"),
Expected: `{"parent_id":{"_name":"my_parent_query","boost":5,"id":"1","ignore_unmapped":true,"type":"blog_tag"}}`,
},
}
for i, tt := range tests {
src, err := tt.Query.Source()
if err != nil {
t.Fatalf("#%d: encoding Source failed: %v", i, err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("#%d: marshaling to JSON failed: %v", i, err)
}
if want, got := tt.Expected, string(data); want != got {
t.Fatalf("#%d: expected\n%s\ngot:\n%s", i, want, got)
}
}
}
================================================
FILE: search_queries_percolator.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import "errors"
// PercolatorQuery can be used to match queries stored in an index.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-percolate-query.html
type PercolatorQuery struct {
field string
name string
documentType string // deprecated
documents []interface{}
indexedDocumentIndex string
indexedDocumentType string
indexedDocumentId string
indexedDocumentRouting string
indexedDocumentPreference string
indexedDocumentVersion *int64
}
// NewPercolatorQuery creates and initializes a new Percolator query.
func NewPercolatorQuery() *PercolatorQuery {
return &PercolatorQuery{}
}
func (q *PercolatorQuery) Field(field string) *PercolatorQuery {
q.field = field
return q
}
// Name used for identification purposes in "_percolator_document_slot" response
// field when multiple percolate queries have been specified in the main query.
func (q *PercolatorQuery) Name(name string) *PercolatorQuery {
q.name = name
return q
}
// Deprecated: DocumentType is deprecated as of 6.0.
func (q *PercolatorQuery) DocumentType(typ string) *PercolatorQuery {
q.documentType = typ
return q
}
func (q *PercolatorQuery) Document(docs ...interface{}) *PercolatorQuery {
q.documents = append(q.documents, docs...)
return q
}
func (q *PercolatorQuery) IndexedDocumentIndex(index string) *PercolatorQuery {
q.indexedDocumentIndex = index
return q
}
func (q *PercolatorQuery) IndexedDocumentType(typ string) *PercolatorQuery {
q.indexedDocumentType = typ
return q
}
func (q *PercolatorQuery) IndexedDocumentId(id string) *PercolatorQuery {
q.indexedDocumentId = id
return q
}
func (q *PercolatorQuery) IndexedDocumentRouting(routing string) *PercolatorQuery {
q.indexedDocumentRouting = routing
return q
}
func (q *PercolatorQuery) IndexedDocumentPreference(preference string) *PercolatorQuery {
q.indexedDocumentPreference = preference
return q
}
func (q *PercolatorQuery) IndexedDocumentVersion(version int64) *PercolatorQuery {
q.indexedDocumentVersion = &version
return q
}
// Source returns JSON for the percolate query.
func (q *PercolatorQuery) Source() (interface{}, error) {
if len(q.field) == 0 {
return nil, errors.New("elastic: Field is required in PercolatorQuery")
}
// {
// "percolate" : { ... }
// }
source := make(map[string]interface{})
params := make(map[string]interface{})
source["percolate"] = params
params["field"] = q.field
if q.documentType != "" {
params["document_type"] = q.documentType
}
if q.name != "" {
params["name"] = q.name
}
switch len(q.documents) {
case 0:
case 1:
params["document"] = q.documents[0]
default:
params["documents"] = q.documents
}
if s := q.indexedDocumentIndex; s != "" {
params["index"] = s
}
if s := q.indexedDocumentType; s != "" {
params["type"] = s
}
if s := q.indexedDocumentId; s != "" {
params["id"] = s
}
if s := q.indexedDocumentRouting; s != "" {
params["routing"] = s
}
if s := q.indexedDocumentPreference; s != "" {
params["preference"] = s
}
if v := q.indexedDocumentVersion; v != nil {
params["version"] = *v
}
return source, nil
}
================================================
FILE: search_queries_percolator_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestPercolatorQuery(t *testing.T) {
q := NewPercolatorQuery().
Field("query").
Document(map[string]interface{}{
"message": "A new bonsai tree in the office",
})
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"percolate":{"document":{"message":"A new bonsai tree in the office"},"field":"query"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestPercolatorQueryWithMultipleDocuments(t *testing.T) {
q := NewPercolatorQuery().
Field("query").
Document(
map[string]interface{}{
"message": "bonsai tree",
}, map[string]interface{}{
"message": "new tree",
},
)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"percolate":{"documents":[{"message":"bonsai tree"},{"message":"new tree"}],"field":"query"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestPercolatorQueryWithExistingDocument(t *testing.T) {
q := NewPercolatorQuery().
Field("query").
IndexedDocumentIndex("my-index").
IndexedDocumentType("_doc").
IndexedDocumentId("2").
IndexedDocumentVersion(1)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"percolate":{"field":"query","id":"2","index":"my-index","type":"_doc","version":1}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestPercolatorQueryWithDetails(t *testing.T) {
q := NewPercolatorQuery().
Field("query").
Document(map[string]interface{}{
"message": "A new bonsai tree in the office",
}).
IndexedDocumentIndex("index").
IndexedDocumentId("1").
IndexedDocumentRouting("route").
IndexedDocumentPreference("one").
IndexedDocumentVersion(1)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"percolate":{"document":{"message":"A new bonsai tree in the office"},"field":"query","id":"1","index":"index","preference":"one","routing":"route","version":1}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestPercolatorQueryWithMissingFields(t *testing.T) {
q := NewPercolatorQuery() // no Field, Document, or Query
_, err := q.Source()
if err == nil {
t.Fatal("expected error, got nil")
}
}
================================================
FILE: search_queries_pinned.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// PinnedQuery is a query that promotes selected documents to rank higher than those matching a given query.
//
// For more details, see:
// https://www.elastic.co/guide/en/elasticsearch/reference/7.8/query-dsl-pinned-query.html
type PinnedQuery struct {
ids []string
organic Query
}
// NewPinnedQuery creates and initializes a new pinned query.
func NewPinnedQuery() *PinnedQuery {
return &PinnedQuery{}
}
// Ids sets an array of document IDs listed in the order they are to appear in results.
func (q *PinnedQuery) Ids(ids ...string) *PinnedQuery {
q.ids = ids
return q
}
// Organic sets a choice of query used to rank documents which will be ranked below the "pinned" document ids.
func (q *PinnedQuery) Organic(query Query) *PinnedQuery {
q.organic = query
return q
}
// Source returns the JSON serializable content for this query.
func (q *PinnedQuery) Source() (interface{}, error) {
// {
// "pinned": {
// "ids": [ "1", "4", "100" ],
// "organic": {
// "match": {
// "description": "iphone"
// }
// }
// }
// }
query := make(map[string]interface{})
params := make(map[string]interface{})
query["pinned"] = params
if len(q.ids) > 0 {
params["ids"] = q.ids
}
if q.organic != nil {
src, err := q.organic.Source()
if err != nil {
return nil, err
}
params["organic"] = src
}
return query, nil
}
================================================
FILE: search_queries_pinned_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestPinnedQueryTest(t *testing.T) {
tests := []struct {
Query Query
Expected string
}{
// #0
{
Query: NewPinnedQuery(),
Expected: `{"pinned":{}}`,
},
// #1
{
Query: NewPinnedQuery().Ids("1", "2", "3"),
Expected: `{"pinned":{"ids":["1","2","3"]}}`,
},
// #2
{
Query: NewPinnedQuery().Organic(NewMatchAllQuery()),
Expected: `{"pinned":{"organic":{"match_all":{}}}}`,
},
// #3
{
Query: NewPinnedQuery().Ids("1", "2", "3").Organic(NewMatchAllQuery()),
Expected: `{"pinned":{"ids":["1","2","3"],"organic":{"match_all":{}}}}`,
},
}
for i, tt := range tests {
src, err := tt.Query.Source()
if err != nil {
t.Fatalf("#%d: encoding Source failed: %v", i, err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("#%d: marshaling to JSON failed: %v", i, err)
}
if want, got := tt.Expected, string(data); want != got {
t.Fatalf("#%d: expected\n%s\ngot:\n%s", i, want, got)
}
}
}
================================================
FILE: search_queries_prefix.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// PrefixQuery matches documents that have fields containing terms
// with a specified prefix (not analyzed).
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-prefix-query.html
type PrefixQuery struct {
name string
prefix string
boost *float64
rewrite string
caseInsensitive *bool
queryName string
}
// NewPrefixQuery creates and initializes a new PrefixQuery.
func NewPrefixQuery(name string, prefix string) *PrefixQuery {
return &PrefixQuery{name: name, prefix: prefix}
}
// Boost sets the boost for this query.
func (q *PrefixQuery) Boost(boost float64) *PrefixQuery {
q.boost = &boost
return q
}
func (q *PrefixQuery) Rewrite(rewrite string) *PrefixQuery {
q.rewrite = rewrite
return q
}
func (q *PrefixQuery) CaseInsensitive(caseInsensitive bool) *PrefixQuery {
q.caseInsensitive = &caseInsensitive
return q
}
// QueryName sets the query name for the filter that can be used when
// searching for matched_filters per hit.
func (q *PrefixQuery) QueryName(queryName string) *PrefixQuery {
q.queryName = queryName
return q
}
// Source returns JSON for the query.
func (q *PrefixQuery) Source() (interface{}, error) {
source := make(map[string]interface{})
query := make(map[string]interface{})
source["prefix"] = query
if q.boost == nil && q.rewrite == "" && q.queryName == "" && q.caseInsensitive == nil {
query[q.name] = q.prefix
} else {
subQuery := make(map[string]interface{})
subQuery["value"] = q.prefix
if q.boost != nil {
subQuery["boost"] = *q.boost
}
if q.rewrite != "" {
subQuery["rewrite"] = q.rewrite
}
if q.caseInsensitive != nil {
subQuery["case_insensitive"] = *q.caseInsensitive
}
if q.queryName != "" {
subQuery["_name"] = q.queryName
}
query[q.name] = subQuery
}
return source, nil
}
================================================
FILE: search_queries_prefix_example_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic_test
import (
"context"
"github.com/olivere/elastic/v7"
)
func ExamplePrefixQuery() {
// Get a client to the local Elasticsearch instance.
client, err := elastic.NewClient()
if err != nil {
// Handle error
panic(err)
}
// Define wildcard query
q := elastic.NewPrefixQuery("user", "oli")
q = q.QueryName("my_query_name")
searchResult, err := client.Search().
Index("twitter").
Query(q).
Pretty(true).
Do(context.Background())
if err != nil {
// Handle error
panic(err)
}
_ = searchResult
}
================================================
FILE: search_queries_prefix_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestPrefixQuery(t *testing.T) {
q := NewPrefixQuery("user", "ki")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"prefix":{"user":"ki"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestPrefixQueryWithCaseInsensitive(t *testing.T) {
q := NewPrefixQuery("user", "ki").CaseInsensitive(true)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"prefix":{"user":{"case_insensitive":true,"value":"ki"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestPrefixQueryWithOptions(t *testing.T) {
q := NewPrefixQuery("user", "ki").CaseInsensitive(true)
q = q.QueryName("my_query_name")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"prefix":{"user":{"_name":"my_query_name","case_insensitive":true,"value":"ki"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_queries_query_string.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"fmt"
)
// QueryStringQuery uses the query parser in order to parse its content.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-query-string-query.html
type QueryStringQuery struct {
queryString string
defaultField string
defaultOperator string
analyzer string
quoteAnalyzer string
quoteFieldSuffix string
allowLeadingWildcard *bool
lowercaseExpandedTerms *bool // Deprecated: Decision is now made by the analyzer
enablePositionIncrements *bool
analyzeWildcard *bool
locale string // Deprecated: Decision is now made by the analyzer
boost *float64
fuzziness string
fuzzyPrefixLength *int
fuzzyMaxExpansions *int
fuzzyRewrite string
phraseSlop *int
fields []string
fieldBoosts map[string]*float64
tieBreaker *float64
rewrite string
minimumShouldMatch string
lenient *bool
queryName string
timeZone string
maxDeterminizedStates *int
escape *bool
typ string
}
// NewQueryStringQuery creates and initializes a new QueryStringQuery.
func NewQueryStringQuery(queryString string) *QueryStringQuery {
return &QueryStringQuery{
queryString: queryString,
fields: make([]string, 0),
fieldBoosts: make(map[string]*float64),
}
}
// DefaultField specifies the field to run against when no prefix field
// is specified. Only relevant when not explicitly adding fields the query
// string will run against.
func (q *QueryStringQuery) DefaultField(defaultField string) *QueryStringQuery {
q.defaultField = defaultField
return q
}
// Field adds a field to run the query string against.
func (q *QueryStringQuery) Field(field string) *QueryStringQuery {
q.fields = append(q.fields, field)
return q
}
// Type sets how multiple fields should be combined to build textual part queries,
// e.g. "best_fields".
func (q *QueryStringQuery) Type(typ string) *QueryStringQuery {
q.typ = typ
return q
}
// FieldWithBoost adds a field to run the query string against with a specific boost.
func (q *QueryStringQuery) FieldWithBoost(field string, boost float64) *QueryStringQuery {
q.fields = append(q.fields, field)
q.fieldBoosts[field] = &boost
return q
}
// TieBreaker is used when more than one field is used with the query string,
// and combined queries are using dismax.
func (q *QueryStringQuery) TieBreaker(tieBreaker float64) *QueryStringQuery {
q.tieBreaker = &tieBreaker
return q
}
// DefaultOperator sets the boolean operator of the query parser used to
// parse the query string.
//
// In default mode (OR) terms without any modifiers
// are considered optional, e.g. "capital of Hungary" is equal to
// "capital OR of OR Hungary".
//
// In AND mode, terms are considered to be in conjunction. The above mentioned
// query is then parsed as "capital AND of AND Hungary".
func (q *QueryStringQuery) DefaultOperator(operator string) *QueryStringQuery {
q.defaultOperator = operator
return q
}
// Analyzer is an optional analyzer used to analyze the query string.
// Note, if a field has search analyzer defined for it, then it will be used
// automatically. Defaults to the smart search analyzer.
func (q *QueryStringQuery) Analyzer(analyzer string) *QueryStringQuery {
q.analyzer = analyzer
return q
}
// QuoteAnalyzer is an optional analyzer to be used to analyze the query string
// for phrase searches. Note, if a field has search analyzer defined for it,
// then it will be used automatically. Defaults to the smart search analyzer.
func (q *QueryStringQuery) QuoteAnalyzer(quoteAnalyzer string) *QueryStringQuery {
q.quoteAnalyzer = quoteAnalyzer
return q
}
// MaxDeterminizedState protects against too-difficult regular expression queries.
func (q *QueryStringQuery) MaxDeterminizedState(maxDeterminizedStates int) *QueryStringQuery {
q.maxDeterminizedStates = &maxDeterminizedStates
return q
}
// AllowLeadingWildcard specifies whether leading wildcards should be allowed
// or not (defaults to true).
func (q *QueryStringQuery) AllowLeadingWildcard(allowLeadingWildcard bool) *QueryStringQuery {
q.allowLeadingWildcard = &allowLeadingWildcard
return q
}
// LowercaseExpandedTerms indicates whether terms of wildcard, prefix, fuzzy
// and range queries are automatically lower-cased or not. Default is true.
//
// Deprecated: Decision is now made by the analyzer.
func (q *QueryStringQuery) LowercaseExpandedTerms(lowercaseExpandedTerms bool) *QueryStringQuery {
q.lowercaseExpandedTerms = &lowercaseExpandedTerms
return q
}
// EnablePositionIncrements indicates whether to enable position increments
// in result query. Defaults to true.
//
// When set, result phrase and multi-phrase queries will be aware of position
// increments. Useful when e.g. a StopFilter increases the position increment
// of the token that follows an omitted token.
func (q *QueryStringQuery) EnablePositionIncrements(enablePositionIncrements bool) *QueryStringQuery {
q.enablePositionIncrements = &enablePositionIncrements
return q
}
// Fuzziness sets the edit distance for fuzzy queries. Default is "AUTO".
func (q *QueryStringQuery) Fuzziness(fuzziness string) *QueryStringQuery {
q.fuzziness = fuzziness
return q
}
// FuzzyPrefixLength sets the minimum prefix length for fuzzy queries.
// Default is 1.
func (q *QueryStringQuery) FuzzyPrefixLength(fuzzyPrefixLength int) *QueryStringQuery {
q.fuzzyPrefixLength = &fuzzyPrefixLength
return q
}
func (q *QueryStringQuery) FuzzyMaxExpansions(fuzzyMaxExpansions int) *QueryStringQuery {
q.fuzzyMaxExpansions = &fuzzyMaxExpansions
return q
}
func (q *QueryStringQuery) FuzzyRewrite(fuzzyRewrite string) *QueryStringQuery {
q.fuzzyRewrite = fuzzyRewrite
return q
}
// PhraseSlop sets the default slop for phrases. If zero, then exact matches
// are required. Default value is zero.
func (q *QueryStringQuery) PhraseSlop(phraseSlop int) *QueryStringQuery {
q.phraseSlop = &phraseSlop
return q
}
// AnalyzeWildcard indicates whether to enabled analysis on wildcard and prefix queries.
func (q *QueryStringQuery) AnalyzeWildcard(analyzeWildcard bool) *QueryStringQuery {
q.analyzeWildcard = &analyzeWildcard
return q
}
func (q *QueryStringQuery) Rewrite(rewrite string) *QueryStringQuery {
q.rewrite = rewrite
return q
}
func (q *QueryStringQuery) MinimumShouldMatch(minimumShouldMatch string) *QueryStringQuery {
q.minimumShouldMatch = minimumShouldMatch
return q
}
// Boost sets the boost for this query.
func (q *QueryStringQuery) Boost(boost float64) *QueryStringQuery {
q.boost = &boost
return q
}
// QuoteFieldSuffix is an optional field name suffix to automatically
// try and add to the field searched when using quoted text.
func (q *QueryStringQuery) QuoteFieldSuffix(quoteFieldSuffix string) *QueryStringQuery {
q.quoteFieldSuffix = quoteFieldSuffix
return q
}
// Lenient indicates whether the query string parser should be lenient
// when parsing field values. It defaults to the index setting and if not
// set, defaults to false.
func (q *QueryStringQuery) Lenient(lenient bool) *QueryStringQuery {
q.lenient = &lenient
return q
}
// QueryName sets the query name for the filter that can be used when
// searching for matched_filters per hit.
func (q *QueryStringQuery) QueryName(queryName string) *QueryStringQuery {
q.queryName = queryName
return q
}
// Locale specifies the locale to be used for string conversions.
//
// Deprecated: Decision is now made by the analyzer.
func (q *QueryStringQuery) Locale(locale string) *QueryStringQuery {
q.locale = locale
return q
}
// TimeZone can be used to automatically adjust to/from fields using a
// timezone. Only used with date fields, of course.
func (q *QueryStringQuery) TimeZone(timeZone string) *QueryStringQuery {
q.timeZone = timeZone
return q
}
// Escape performs escaping of the query string.
func (q *QueryStringQuery) Escape(escape bool) *QueryStringQuery {
q.escape = &escape
return q
}
// Source returns JSON for the query.
func (q *QueryStringQuery) Source() (interface{}, error) {
source := make(map[string]interface{})
query := make(map[string]interface{})
source["query_string"] = query
query["query"] = q.queryString
if q.defaultField != "" {
query["default_field"] = q.defaultField
}
if len(q.fields) > 0 {
var fields []string
for _, field := range q.fields {
if boost, found := q.fieldBoosts[field]; found {
if boost != nil {
fields = append(fields, fmt.Sprintf("%s^%f", field, *boost))
} else {
fields = append(fields, field)
}
} else {
fields = append(fields, field)
}
}
query["fields"] = fields
}
if q.tieBreaker != nil {
query["tie_breaker"] = *q.tieBreaker
}
if q.defaultOperator != "" {
query["default_operator"] = q.defaultOperator
}
if q.analyzer != "" {
query["analyzer"] = q.analyzer
}
if q.quoteAnalyzer != "" {
query["quote_analyzer"] = q.quoteAnalyzer
}
if q.maxDeterminizedStates != nil {
query["max_determinized_states"] = *q.maxDeterminizedStates
}
if q.allowLeadingWildcard != nil {
query["allow_leading_wildcard"] = *q.allowLeadingWildcard
}
if q.lowercaseExpandedTerms != nil {
query["lowercase_expanded_terms"] = *q.lowercaseExpandedTerms
}
if q.enablePositionIncrements != nil {
query["enable_position_increments"] = *q.enablePositionIncrements
}
if q.fuzziness != "" {
query["fuzziness"] = q.fuzziness
}
if q.boost != nil {
query["boost"] = *q.boost
}
if q.fuzzyPrefixLength != nil {
query["fuzzy_prefix_length"] = *q.fuzzyPrefixLength
}
if q.fuzzyMaxExpansions != nil {
query["fuzzy_max_expansions"] = *q.fuzzyMaxExpansions
}
if q.fuzzyRewrite != "" {
query["fuzzy_rewrite"] = q.fuzzyRewrite
}
if q.phraseSlop != nil {
query["phrase_slop"] = *q.phraseSlop
}
if q.analyzeWildcard != nil {
query["analyze_wildcard"] = *q.analyzeWildcard
}
if q.rewrite != "" {
query["rewrite"] = q.rewrite
}
if q.minimumShouldMatch != "" {
query["minimum_should_match"] = q.minimumShouldMatch
}
if q.quoteFieldSuffix != "" {
query["quote_field_suffix"] = q.quoteFieldSuffix
}
if q.lenient != nil {
query["lenient"] = *q.lenient
}
if q.queryName != "" {
query["_name"] = q.queryName
}
if q.locale != "" {
query["locale"] = q.locale
}
if q.timeZone != "" {
query["time_zone"] = q.timeZone
}
if q.escape != nil {
query["escape"] = *q.escape
}
if q.typ != "" {
query["type"] = q.typ
}
return source, nil
}
================================================
FILE: search_queries_query_string_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"testing"
)
func TestQueryStringQuery(t *testing.T) {
q := NewQueryStringQuery(`this AND that OR thus`)
q = q.DefaultField("content")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"query_string":{"default_field":"content","query":"this AND that OR thus"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestQueryStringQueryTimeZone(t *testing.T) {
q := NewQueryStringQuery(`tweet_date:[2015-01-01 TO 2017-12-31]`)
q = q.TimeZone("Europe/Berlin")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"query_string":{"query":"tweet_date:[2015-01-01 TO 2017-12-31]","time_zone":"Europe/Berlin"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestQueryStringQueryIntegration(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t) //, SetTraceLog(log.New(os.Stdout, "", log.LstdFlags)))
q := NewQueryStringQuery("Golang")
// Match all should return all documents
searchResult, err := client.Search().
Index(testIndexName).
Query(q).
Pretty(true).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.Hits == nil {
t.Errorf("expected SearchResult.Hits != nil; got nil")
}
if got, want := searchResult.TotalHits(), int64(1); got != want {
t.Errorf("expected SearchResult.TotalHits() = %d; got %d", want, got)
}
if got, want := len(searchResult.Hits.Hits), 1; got != want {
t.Errorf("expected len(SearchResult.Hits.Hits) = %d; got %d", want, got)
}
}
================================================
FILE: search_queries_range.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// RangeQuery matches documents with fields that have terms within a certain range.
//
// For details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-range-query.html
type RangeQuery struct {
name string
from interface{}
to interface{}
timeZone string
includeLower bool
includeUpper bool
boost *float64
queryName string
format string
relation string
}
// NewRangeQuery creates and initializes a new RangeQuery.
func NewRangeQuery(name string) *RangeQuery {
return &RangeQuery{name: name, includeLower: true, includeUpper: true}
}
// From indicates the from part of the RangeQuery.
// Use nil to indicate an unbounded from part.
func (q *RangeQuery) From(from interface{}) *RangeQuery {
q.from = from
return q
}
// Gt indicates a greater-than value for the from part.
// Use nil to indicate an unbounded from part.
func (q *RangeQuery) Gt(from interface{}) *RangeQuery {
q.from = from
q.includeLower = false
return q
}
// Gte indicates a greater-than-or-equal value for the from part.
// Use nil to indicate an unbounded from part.
func (q *RangeQuery) Gte(from interface{}) *RangeQuery {
q.from = from
q.includeLower = true
return q
}
// To indicates the to part of the RangeQuery.
// Use nil to indicate an unbounded to part.
func (q *RangeQuery) To(to interface{}) *RangeQuery {
q.to = to
return q
}
// Lt indicates a less-than value for the to part.
// Use nil to indicate an unbounded to part.
func (q *RangeQuery) Lt(to interface{}) *RangeQuery {
q.to = to
q.includeUpper = false
return q
}
// Lte indicates a less-than-or-equal value for the to part.
// Use nil to indicate an unbounded to part.
func (q *RangeQuery) Lte(to interface{}) *RangeQuery {
q.to = to
q.includeUpper = true
return q
}
// IncludeLower indicates whether the lower bound should be included or not.
// Defaults to true.
func (q *RangeQuery) IncludeLower(includeLower bool) *RangeQuery {
q.includeLower = includeLower
return q
}
// IncludeUpper indicates whether the upper bound should be included or not.
// Defaults to true.
func (q *RangeQuery) IncludeUpper(includeUpper bool) *RangeQuery {
q.includeUpper = includeUpper
return q
}
// Boost sets the boost for this query.
func (q *RangeQuery) Boost(boost float64) *RangeQuery {
q.boost = &boost
return q
}
// QueryName sets the query name for the filter that can be used when
// searching for matched_filters per hit.
func (q *RangeQuery) QueryName(queryName string) *RangeQuery {
q.queryName = queryName
return q
}
// TimeZone is used for date fields. In that case, we can adjust the
// from/to fields using a timezone.
func (q *RangeQuery) TimeZone(timeZone string) *RangeQuery {
q.timeZone = timeZone
return q
}
// Format is used for date fields. In that case, we can set the format
// to be used instead of the mapper format.
func (q *RangeQuery) Format(format string) *RangeQuery {
q.format = format
return q
}
// Relation is used for range fields. which can be one of
// "within", "contains", "intersects" (default) and "disjoint".
func (q *RangeQuery) Relation(relation string) *RangeQuery {
q.relation = relation
return q
}
// Source returns JSON for the query.
func (q *RangeQuery) Source() (interface{}, error) {
source := make(map[string]interface{})
rangeQ := make(map[string]interface{})
source["range"] = rangeQ
params := make(map[string]interface{})
rangeQ[q.name] = params
params["from"] = q.from
params["to"] = q.to
if q.timeZone != "" {
params["time_zone"] = q.timeZone
}
if q.format != "" {
params["format"] = q.format
}
if q.relation != "" {
params["relation"] = q.relation
}
if q.boost != nil {
params["boost"] = *q.boost
}
params["include_lower"] = q.includeLower
params["include_upper"] = q.includeUpper
if q.queryName != "" {
rangeQ["_name"] = q.queryName
}
return source, nil
}
================================================
FILE: search_queries_range_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestRangeQuery(t *testing.T) {
q := NewRangeQuery("postDate").From("2010-03-01").To("2010-04-01").Boost(3).Relation("within")
q = q.QueryName("my_query")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"range":{"_name":"my_query","postDate":{"boost":3,"from":"2010-03-01","include_lower":true,"include_upper":true,"relation":"within","to":"2010-04-01"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestRangeQueryWithTimeZone(t *testing.T) {
q := NewRangeQuery("born").
Gte("2012-01-01").
Lte("now").
TimeZone("+1:00")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"range":{"born":{"from":"2012-01-01","include_lower":true,"include_upper":true,"time_zone":"+1:00","to":"now"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestRangeQueryWithFormat(t *testing.T) {
q := NewRangeQuery("born").
Gte("2012/01/01").
Lte("now").
Format("yyyy/MM/dd")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"range":{"born":{"format":"yyyy/MM/dd","from":"2012/01/01","include_lower":true,"include_upper":true,"to":"now"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_queries_rank_feature.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// RankFeatureQuery boosts the relevance score of documents based on the
// numeric value of a rank_feature or rank_features field.
//
// The RankFeatureQuery is typically used in the should clause of a BoolQuery
// so its relevance scores are added to other scores from the BoolQuery.
//
// For more details, see:
// https://www.elastic.co/guide/en/elasticsearch/reference/7.14/query-dsl-rank-feature-query.html
type RankFeatureQuery struct {
field string
scoreFunc RankFeatureScoreFunction
boost *float64
queryName string
}
// NewRankFeatureQuery creates and initializes a new RankFeatureQuery.
func NewRankFeatureQuery(field string) *RankFeatureQuery {
return &RankFeatureQuery{
field: field,
}
}
// Field name.
func (q *RankFeatureQuery) Field(field string) *RankFeatureQuery {
q.field = field
return q
}
// ScoreFunction specifies the score function for the RankFeatureQuery.
func (q *RankFeatureQuery) ScoreFunction(f RankFeatureScoreFunction) *RankFeatureQuery {
q.scoreFunc = f
return q
}
// Boost sets the boost for this query.
func (q *RankFeatureQuery) Boost(boost float64) *RankFeatureQuery {
q.boost = &boost
return q
}
// QueryName sets the query name for the filter that can be used when
// searching for matched_filters per hit.
func (q *RankFeatureQuery) QueryName(queryName string) *RankFeatureQuery {
q.queryName = queryName
return q
}
// Source returns the JSON serializable content for this query.
func (q *RankFeatureQuery) Source() (interface{}, error) {
// {
// "rank_feature": {
// "field": "pagerank",
// "saturation": {
// "pivot": 8
// }
// }
// }
query := make(map[string]interface{})
params := make(map[string]interface{})
query["rank_feature"] = params
params["field"] = q.field
if q.scoreFunc != nil {
src, err := q.scoreFunc.Source()
if err != nil {
return nil, err
}
params[q.scoreFunc.Name()] = src
}
if q.boost != nil {
params["boost"] = *q.boost
}
if q.queryName != "" {
params["_name"] = q.queryName
}
return query, nil
}
// -- Score functions --
// RankFeatureScoreFunction specifies the interface for score functions
// in the context of a RankFeatureQuery.
type RankFeatureScoreFunction interface {
Name() string
Source() (interface{}, error)
}
// -- Log score function --
// RankFeatureLogScoreFunction represents a Logarithmic score function for a
// RankFeatureQuery.
//
// See here for details:
// https://www.elastic.co/guide/en/elasticsearch/reference/7.14/query-dsl-rank-feature-query.html#rank-feature-query-logarithm
type RankFeatureLogScoreFunction struct {
scalingFactor float64
}
// NewRankFeatureLogScoreFunction returns a new RankFeatureLogScoreFunction
// with the given scaling factor.
func NewRankFeatureLogScoreFunction(scalingFactor float64) *RankFeatureLogScoreFunction {
return &RankFeatureLogScoreFunction{
scalingFactor: scalingFactor,
}
}
// Name of the score function.
func (f *RankFeatureLogScoreFunction) Name() string { return "log" }
// Source returns a serializable JSON object for building the query.
func (f *RankFeatureLogScoreFunction) Source() (interface{}, error) {
return map[string]interface{}{
"scaling_factor": f.scalingFactor,
}, nil
}
// -- Saturation score function --
// RankFeatureSaturationScoreFunction represents a Log score function for a
// RankFeatureQuery.
//
// See here for details:
// https://www.elastic.co/guide/en/elasticsearch/reference/7.14/query-dsl-rank-feature-query.html#rank-feature-query-saturation
type RankFeatureSaturationScoreFunction struct {
pivot *float64
}
// NewRankFeatureSaturationScoreFunction initializes a new
// RankFeatureSaturationScoreFunction.
func NewRankFeatureSaturationScoreFunction() *RankFeatureSaturationScoreFunction {
return &RankFeatureSaturationScoreFunction{}
}
// Pivot specifies the pivot to use.
func (f *RankFeatureSaturationScoreFunction) Pivot(pivot float64) *RankFeatureSaturationScoreFunction {
f.pivot = &pivot
return f
}
// Name of the score function.
func (f *RankFeatureSaturationScoreFunction) Name() string { return "saturation" }
// Source returns a serializable JSON object for building the query.
func (f *RankFeatureSaturationScoreFunction) Source() (interface{}, error) {
m := make(map[string]interface{})
if f.pivot != nil {
m["pivot"] = *f.pivot
}
return m, nil
}
// -- Sigmoid score function --
// RankFeatureSigmoidScoreFunction represents a Sigmoid score function for a
// RankFeatureQuery.
//
// See here for details:
// https://www.elastic.co/guide/en/elasticsearch/reference/7.14/query-dsl-rank-feature-query.html#rank-feature-query-sigmoid
type RankFeatureSigmoidScoreFunction struct {
pivot float64
exponent float64
}
// NewRankFeatureSigmoidScoreFunction returns a new RankFeatureSigmoidScoreFunction
// with the given scaling factor.
func NewRankFeatureSigmoidScoreFunction(pivot, exponent float64) *RankFeatureSigmoidScoreFunction {
return &RankFeatureSigmoidScoreFunction{
pivot: pivot,
exponent: exponent,
}
}
// Name of the score function.
func (f *RankFeatureSigmoidScoreFunction) Name() string { return "sigmoid" }
// Source returns a serializable JSON object for building the query.
func (f *RankFeatureSigmoidScoreFunction) Source() (interface{}, error) {
return map[string]interface{}{
"pivot": f.pivot,
"exponent": f.exponent,
}, nil
}
// -- Linear score function --
// RankFeatureLinearScoreFunction represents a Linear score function for a
// RankFeatureQuery.
//
// See here for details:
// https://www.elastic.co/guide/en/elasticsearch/reference/7.14/query-dsl-rank-feature-query.html#rank-feature-query-linear
type RankFeatureLinearScoreFunction struct {
}
// NewRankFeatureLinearScoreFunction initializes a new
// RankFeatureLinearScoreFunction.
func NewRankFeatureLinearScoreFunction() *RankFeatureLinearScoreFunction {
return &RankFeatureLinearScoreFunction{}
}
// Name of the score function.
func (f *RankFeatureLinearScoreFunction) Name() string { return "linear" }
// Source returns a serializable JSON object for building the query.
func (f *RankFeatureLinearScoreFunction) Source() (interface{}, error) {
return map[string]interface{}{}, nil
}
================================================
FILE: search_queries_rank_feature_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestRankFeatureQueryTest(t *testing.T) {
tests := []struct {
Query Query
Expected string
}{
// #0
{
Query: NewRankFeatureQuery("pagerank"),
Expected: `{"rank_feature":{"field":"pagerank"}}`,
},
// #1
{
Query: NewRankFeatureQuery("url_length").Boost(0.1),
Expected: `{"rank_feature":{"boost":0.1,"field":"url_length"}}`,
},
// #2
{
Query: NewRankFeatureQuery("pagerank").ScoreFunction(NewRankFeatureSaturationScoreFunction().Pivot(8)),
Expected: `{"rank_feature":{"field":"pagerank","saturation":{"pivot":8}}}`,
},
// #3
{
Query: NewRankFeatureQuery("pagerank").ScoreFunction(NewRankFeatureSaturationScoreFunction()),
Expected: `{"rank_feature":{"field":"pagerank","saturation":{}}}`,
},
// #4
{
Query: NewRankFeatureQuery("pagerank").ScoreFunction(NewRankFeatureLogScoreFunction(4)),
Expected: `{"rank_feature":{"field":"pagerank","log":{"scaling_factor":4}}}`,
},
// #5
{
Query: NewRankFeatureQuery("pagerank").ScoreFunction(NewRankFeatureSigmoidScoreFunction(7, 0.6)),
Expected: `{"rank_feature":{"field":"pagerank","sigmoid":{"exponent":0.6,"pivot":7}}}`,
},
// #6
{
Query: NewRankFeatureQuery("pagerank").ScoreFunction(NewRankFeatureLinearScoreFunction()),
Expected: `{"rank_feature":{"field":"pagerank","linear":{}}}`,
},
}
for i, tt := range tests {
src, err := tt.Query.Source()
if err != nil {
t.Fatalf("#%d: encoding Source failed: %v", i, err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("#%d: marshaling to JSON failed: %v", i, err)
}
if want, got := tt.Expected, string(data); want != got {
t.Fatalf("#%d: expected\n%s\ngot:\n%s", i, want, got)
}
}
}
================================================
FILE: search_queries_raw_string.go
================================================
// Copyright 2012-present Oliver Eilhard, John Stanford. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import "encoding/json"
// RawStringQuery can be used to treat a string representation of an ES query
// as a Query. Example usage:
// q := RawStringQuery("{\"match_all\":{}}")
// db.Search().Query(q).From(1).Size(100).Do()
type RawStringQuery string
// NewRawStringQuery ininitializes a new RawStringQuery.
// It is the same as RawStringQuery(q).
func NewRawStringQuery(q string) RawStringQuery {
return RawStringQuery(q)
}
// Source returns the JSON encoded body
func (q RawStringQuery) Source() (interface{}, error) {
var f interface{}
err := json.Unmarshal([]byte(q), &f)
return f, err
}
================================================
FILE: search_queries_raw_string_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestRawStringQuery(t *testing.T) {
q := RawStringQuery(`{"match_all":{}}`)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"match_all":{}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestNewRawStringQuery(t *testing.T) {
q := NewRawStringQuery(`{"match_all":{}}`)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"match_all":{}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_queries_regexp.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// RegexpQuery allows you to use regular expression term queries.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-regexp-query.html
type RegexpQuery struct {
name string
regexp string
flags string
boost *float64
rewrite string
caseInsensitive *bool
queryName string
maxDeterminizedStates *int
}
// NewRegexpQuery creates and initializes a new RegexpQuery.
func NewRegexpQuery(name string, regexp string) *RegexpQuery {
return &RegexpQuery{name: name, regexp: regexp}
}
// Flags sets the regexp flags.
func (q *RegexpQuery) Flags(flags string) *RegexpQuery {
q.flags = flags
return q
}
// MaxDeterminizedStates protects against complex regular expressions.
func (q *RegexpQuery) MaxDeterminizedStates(maxDeterminizedStates int) *RegexpQuery {
q.maxDeterminizedStates = &maxDeterminizedStates
return q
}
// Boost sets the boost for this query.
func (q *RegexpQuery) Boost(boost float64) *RegexpQuery {
q.boost = &boost
return q
}
func (q *RegexpQuery) Rewrite(rewrite string) *RegexpQuery {
q.rewrite = rewrite
return q
}
func (q *RegexpQuery) CaseInsensitive(caseInsensitive bool) *RegexpQuery {
q.caseInsensitive = &caseInsensitive
return q
}
// QueryName sets the query name for the filter that can be used
// when searching for matched_filters per hit
func (q *RegexpQuery) QueryName(queryName string) *RegexpQuery {
q.queryName = queryName
return q
}
// Source returns the JSON-serializable query data.
func (q *RegexpQuery) Source() (interface{}, error) {
source := make(map[string]interface{})
query := make(map[string]interface{})
source["regexp"] = query
x := make(map[string]interface{})
x["value"] = q.regexp
if q.flags != "" {
x["flags"] = q.flags
}
if q.maxDeterminizedStates != nil {
x["max_determinized_states"] = *q.maxDeterminizedStates
}
if q.boost != nil {
x["boost"] = *q.boost
}
if q.rewrite != "" {
x["rewrite"] = q.rewrite
}
if q.caseInsensitive != nil {
x["case_insensitive"] = *q.caseInsensitive
}
if q.queryName != "" {
x["name"] = q.queryName
}
query[q.name] = x
return source, nil
}
================================================
FILE: search_queries_regexp_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestRegexpQuery(t *testing.T) {
q := NewRegexpQuery("name.first", "s.*y")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"regexp":{"name.first":{"value":"s.*y"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestRegexpQueryWithOptions(t *testing.T) {
q := NewRegexpQuery("name.first", "s.*y").
Boost(1.2).
Flags("INTERSECTION|COMPLEMENT|EMPTY").
CaseInsensitive(true).
QueryName("my_query_name")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"regexp":{"name.first":{"boost":1.2,"case_insensitive":true,"flags":"INTERSECTION|COMPLEMENT|EMPTY","name":"my_query_name","value":"s.*y"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_queries_script.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import "errors"
// ScriptQuery allows to define scripts as filters.
//
// For details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-script-query.html
type ScriptQuery struct {
script *Script
queryName string
}
// NewScriptQuery creates and initializes a new ScriptQuery.
func NewScriptQuery(script *Script) *ScriptQuery {
return &ScriptQuery{
script: script,
}
}
// QueryName sets the query name for the filter that can be used
// when searching for matched_filters per hit
func (q *ScriptQuery) QueryName(queryName string) *ScriptQuery {
q.queryName = queryName
return q
}
// Source returns JSON for the query.
func (q *ScriptQuery) Source() (interface{}, error) {
if q.script == nil {
return nil, errors.New("ScriptQuery expected a script")
}
source := make(map[string]interface{})
params := make(map[string]interface{})
source["script"] = params
src, err := q.script.Source()
if err != nil {
return nil, err
}
params["script"] = src
if q.queryName != "" {
params["_name"] = q.queryName
}
return source, nil
}
================================================
FILE: search_queries_script_score.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import "errors"
// ScriptScoreQuery uses a script to provide a custom score for returned documents.
//
// A ScriptScoreQuery query is useful if, for example, a scoring function is
// expensive and you only need to calculate the score of a filtered set of documents.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.4/query-dsl-script-score-query.html
type ScriptScoreQuery struct {
query Query
script *Script
minScore *float64
boost *float64
queryName string
}
// NewScriptScoreQuery creates and initializes a new script_score query.
func NewScriptScoreQuery(query Query, script *Script) *ScriptScoreQuery {
return &ScriptScoreQuery{
query: query,
script: script,
}
}
// Query to be used in the ScriptScoreQuery.
func (q *ScriptScoreQuery) Query(query Query) *ScriptScoreQuery {
q.query = query
return q
}
// Script to calculate the score.
func (q *ScriptScoreQuery) Script(script *Script) *ScriptScoreQuery {
q.script = script
return q
}
// MinScore sets the minimum score.
func (q *ScriptScoreQuery) MinScore(minScore float64) *ScriptScoreQuery {
q.minScore = &minScore
return q
}
// Boost sets the boost for this query.
func (q *ScriptScoreQuery) Boost(boost float64) *ScriptScoreQuery {
q.boost = &boost
return q
}
// QueryName sets the query name for the filter.
func (q *ScriptScoreQuery) QueryName(queryName string) *ScriptScoreQuery {
q.queryName = queryName
return q
}
// Source returns JSON for the function score query.
func (q *ScriptScoreQuery) Source() (interface{}, error) {
// {
// "script_score" : {
// "query" : {
// "match" : { "message": "elasticsearch" }
// },
// "script" : {
// "source" : "doc['likes'].value / 10"
// }
// }
// }
source := make(map[string]interface{})
query := make(map[string]interface{})
source["script_score"] = query
if q.query == nil {
return nil, errors.New("ScriptScoreQuery: Query is missing")
}
if q.script == nil {
return nil, errors.New("ScriptScoreQuery: Script is missing")
}
if src, err := q.query.Source(); err != nil {
return nil, err
} else {
query["query"] = src
}
if src, err := q.script.Source(); err != nil {
return nil, err
} else {
query["script"] = src
}
if v := q.minScore; v != nil {
query["min_score"] = *v
}
if v := q.boost; v != nil {
query["boost"] = *v
}
if q.queryName != "" {
query["_name"] = q.queryName
}
return source, nil
}
================================================
FILE: search_queries_script_score_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"testing"
)
func TestScriptScoreQuery(t *testing.T) {
q := NewScriptScoreQuery(
NewMatchQuery("message", "elasticsearch"),
NewScript("doc['likes'].value / 10"),
).MinScore(1.1).Boost(5.0).QueryName("my_query")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"script_score":{"_name":"my_query","boost":5,"min_score":1.1,"query":{"match":{"message":{"query":"elasticsearch"}}},"script":{"source":"doc['likes'].value / 10"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestScriptScoreQueryIntegration(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t) //, SetTraceLog(log.New(os.Stdout, "", 0)))
res, err := client.Search().
Index(testIndexName).
Query(
NewScriptScoreQuery(
NewMatchQuery("message", "Golang"),
NewScript("(1 + doc['retweets'].value) * 10"),
),
).
Pretty(true).
Do(context.Background())
if err != nil {
t.Fatal(err)
}
if res.Hits == nil {
t.Errorf("expected Hits != nil; got nil")
}
if want, have := int64(1), res.TotalHits(); want != have {
t.Errorf("expected TotalHits() = %d; got %d", want, have)
}
if want, have := 1, len(res.Hits.Hits); want != have {
t.Errorf("expected len(Hits.Hits) = %d; got %d", want, have)
}
hit := res.Hits.Hits[0]
if want, have := testIndexName, hit.Index; want != have {
t.Fatalf("expected Hits.Hit.Index = %q; got %q", want, have)
}
if want, have := "1", hit.Id; want != have {
t.Fatalf("expected Hits.Hit.Id = %q; got %q", want, have)
}
if hit.Score == nil {
t.Fatal("expected Hits.Hit.Score != nil")
}
if want, have := float64(1090), *hit.Score; want != have {
t.Fatalf("expected Hits.Hit.Score = %v; got %v", want, have)
}
var tw tweet
if err := json.Unmarshal(hit.Source, &tw); err != nil {
t.Fatal(err)
}
if want, have := "olivere", tw.User; want != have {
t.Fatalf("expected User = %q; got %q", want, have)
}
}
================================================
FILE: search_queries_script_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestScriptQuery(t *testing.T) {
q := NewScriptQuery(NewScript("doc['num1'.value > 1"))
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"script":{"script":{"source":"doc['num1'.value \u003e 1"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestScriptQueryWithParams(t *testing.T) {
q := NewScriptQuery(NewScript("doc['num1'.value > 1"))
q = q.QueryName("MyQueryName")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"script":{"_name":"MyQueryName","script":{"source":"doc['num1'.value \u003e 1"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_queries_simple_query_string.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"fmt"
"strings"
)
// SimpleQueryStringQuery is a query that uses the SimpleQueryParser
// to parse its context. Unlike the regular query_string query,
// the simple_query_string query will never throw an exception,
// and discards invalid parts of the query.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-simple-query-string-query.html
type SimpleQueryStringQuery struct {
queryText string
analyzer string
quoteFieldSuffix string
defaultOperator string
fields []string
fieldBoosts map[string]*float64
minimumShouldMatch string
flags string
boost *float64
lowercaseExpandedTerms *bool // deprecated
lenient *bool
analyzeWildcard *bool
locale string // deprecated
queryName string
autoGenerateSynonymsPhraseQuery *bool
fuzzyPrefixLength int
fuzzyMaxExpansions int
fuzzyTranspositions *bool
}
// NewSimpleQueryStringQuery creates and initializes a new SimpleQueryStringQuery.
func NewSimpleQueryStringQuery(text string) *SimpleQueryStringQuery {
return &SimpleQueryStringQuery{
queryText: text,
fields: make([]string, 0),
fieldBoosts: make(map[string]*float64),
fuzzyPrefixLength: -1,
fuzzyMaxExpansions: -1,
}
}
// Field adds a field to run the query against.
func (q *SimpleQueryStringQuery) Field(field string) *SimpleQueryStringQuery {
q.fields = append(q.fields, field)
return q
}
// Field adds a field to run the query against with a specific boost.
func (q *SimpleQueryStringQuery) FieldWithBoost(field string, boost float64) *SimpleQueryStringQuery {
q.fields = append(q.fields, field)
q.fieldBoosts[field] = &boost
return q
}
// Boost sets the boost for this query.
func (q *SimpleQueryStringQuery) Boost(boost float64) *SimpleQueryStringQuery {
q.boost = &boost
return q
}
// QuoteFieldSuffix is an optional field name suffix to automatically
// try and add to the field searched when using quoted text.
func (q *SimpleQueryStringQuery) QuoteFieldSuffix(quoteFieldSuffix string) *SimpleQueryStringQuery {
q.quoteFieldSuffix = quoteFieldSuffix
return q
}
// QueryName sets the query name for the filter that can be used when
// searching for matched_filters per hit.
func (q *SimpleQueryStringQuery) QueryName(queryName string) *SimpleQueryStringQuery {
q.queryName = queryName
return q
}
// Analyzer specifies the analyzer to use for the query.
func (q *SimpleQueryStringQuery) Analyzer(analyzer string) *SimpleQueryStringQuery {
q.analyzer = analyzer
return q
}
// DefaultOperator specifies the default operator for the query.
func (q *SimpleQueryStringQuery) DefaultOperator(defaultOperator string) *SimpleQueryStringQuery {
q.defaultOperator = defaultOperator
return q
}
// Flags sets the flags for the query.
func (q *SimpleQueryStringQuery) Flags(flags string) *SimpleQueryStringQuery {
q.flags = flags
return q
}
// LowercaseExpandedTerms indicates whether terms of wildcard, prefix, fuzzy
// and range queries are automatically lower-cased or not. Default is true.
//
// Deprecated: Decision is now made by the analyzer.
func (q *SimpleQueryStringQuery) LowercaseExpandedTerms(lowercaseExpandedTerms bool) *SimpleQueryStringQuery {
q.lowercaseExpandedTerms = &lowercaseExpandedTerms
return q
}
// Locale to be used in the query.
//
// Deprecated: Decision is now made by the analyzer.
func (q *SimpleQueryStringQuery) Locale(locale string) *SimpleQueryStringQuery {
q.locale = locale
return q
}
// Lenient indicates whether the query string parser should be lenient
// when parsing field values. It defaults to the index setting and if not
// set, defaults to false.
func (q *SimpleQueryStringQuery) Lenient(lenient bool) *SimpleQueryStringQuery {
q.lenient = &lenient
return q
}
// AnalyzeWildcard indicates whether to enabled analysis on wildcard and prefix queries.
func (q *SimpleQueryStringQuery) AnalyzeWildcard(analyzeWildcard bool) *SimpleQueryStringQuery {
q.analyzeWildcard = &analyzeWildcard
return q
}
// MinimumShouldMatch specifies the minimumShouldMatch to apply to the
// resulting query should that be a Boolean query.
func (q *SimpleQueryStringQuery) MinimumShouldMatch(minimumShouldMatch string) *SimpleQueryStringQuery {
q.minimumShouldMatch = minimumShouldMatch
return q
}
// AutoGenerateSynonymsPhraseQuery indicates whether phrase queries should be
// automatically generated for multi terms synonyms. Defaults to true.
func (q *SimpleQueryStringQuery) AutoGenerateSynonymsPhraseQuery(enable bool) *SimpleQueryStringQuery {
q.autoGenerateSynonymsPhraseQuery = &enable
return q
}
// FuzzyPrefixLength defines the prefix length in fuzzy queries.
func (q *SimpleQueryStringQuery) FuzzyPrefixLength(fuzzyPrefixLength int) *SimpleQueryStringQuery {
q.fuzzyPrefixLength = fuzzyPrefixLength
return q
}
// FuzzyMaxExpansions defines the number of terms fuzzy queries will expand to.
func (q *SimpleQueryStringQuery) FuzzyMaxExpansions(fuzzyMaxExpansions int) *SimpleQueryStringQuery {
q.fuzzyMaxExpansions = fuzzyMaxExpansions
return q
}
// FuzzyTranspositions defines whether to use transpositions in fuzzy queries.
func (q *SimpleQueryStringQuery) FuzzyTranspositions(fuzzyTranspositions bool) *SimpleQueryStringQuery {
q.fuzzyTranspositions = &fuzzyTranspositions
return q
}
// Source returns JSON for the query.
func (q *SimpleQueryStringQuery) Source() (interface{}, error) {
// {
// "simple_query_string" : {
// "query" : "\"fried eggs\" +(eggplant | potato) -frittata",
// "analyzer" : "snowball",
// "fields" : ["body^5","_all"],
// "default_operator" : "and"
// }
// }
source := make(map[string]interface{})
query := make(map[string]interface{})
source["simple_query_string"] = query
query["query"] = q.queryText
if len(q.fields) > 0 {
var fields []string
for _, field := range q.fields {
if boost, found := q.fieldBoosts[field]; found {
if boost != nil {
fields = append(fields, fmt.Sprintf("%s^%f", field, *boost))
} else {
fields = append(fields, field)
}
} else {
fields = append(fields, field)
}
}
query["fields"] = fields
}
if q.flags != "" {
query["flags"] = q.flags
}
if q.analyzer != "" {
query["analyzer"] = q.analyzer
}
if q.defaultOperator != "" {
query["default_operator"] = strings.ToLower(q.defaultOperator)
}
if q.lowercaseExpandedTerms != nil {
query["lowercase_expanded_terms"] = *q.lowercaseExpandedTerms
}
if q.lenient != nil {
query["lenient"] = *q.lenient
}
if q.analyzeWildcard != nil {
query["analyze_wildcard"] = *q.analyzeWildcard
}
if q.locale != "" {
query["locale"] = q.locale
}
if q.queryName != "" {
query["_name"] = q.queryName
}
if q.minimumShouldMatch != "" {
query["minimum_should_match"] = q.minimumShouldMatch
}
if q.quoteFieldSuffix != "" {
query["quote_field_suffix"] = q.quoteFieldSuffix
}
if q.boost != nil {
query["boost"] = *q.boost
}
if v := q.autoGenerateSynonymsPhraseQuery; v != nil {
query["auto_generate_synonyms_phrase_query"] = *v
}
if v := q.fuzzyPrefixLength; v != -1 {
query["fuzzy_prefix_length"] = v
}
if v := q.fuzzyMaxExpansions; v != -1 {
query["fuzzy_max_expansions"] = v
}
if v := q.fuzzyTranspositions; v != nil {
query["fuzzy_transpositions"] = *v
}
return source, nil
}
================================================
FILE: search_queries_simple_query_string_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"testing"
)
func TestSimpleQueryStringQuery(t *testing.T) {
q := NewSimpleQueryStringQuery(`"fried eggs" +(eggplant | potato) -frittata`)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"simple_query_string":{"query":"\"fried eggs\" +(eggplant | potato) -frittata"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSimpleQueryStringQueryExec(t *testing.T) {
// client := setupTestClientAndCreateIndexAndLog(t, SetTraceLog(log.New(os.Stdout, "", 0)))
client := setupTestClientAndCreateIndex(t)
tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
tweet2 := tweet{User: "olivere", Message: "Another unrelated topic."}
tweet3 := tweet{User: "sandrae", Message: "Cycling is fun."}
// Add all documents
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("3").BodyJson(&tweet3).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Match all should return all documents
searchResult, err := client.Search().
Index(testIndexName).
Query(NewSimpleQueryStringQuery("+Golang +Elasticsearch")).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.Hits == nil {
t.Errorf("expected SearchResult.Hits != nil; got nil")
}
if searchResult.TotalHits() != 1 {
t.Errorf("expected SearchResult.TotalHits() = %d; got %d", 1, searchResult.TotalHits())
}
if len(searchResult.Hits.Hits) != 1 {
t.Errorf("expected len(SearchResult.Hits.Hits) = %d; got %d", 1, len(searchResult.Hits.Hits))
}
for _, hit := range searchResult.Hits.Hits {
if hit.Index != testIndexName {
t.Errorf("expected SearchResult.Hits.Hit.Index = %q; got %q", testIndexName, hit.Index)
}
item := make(map[string]interface{})
err := json.Unmarshal(hit.Source, &item)
if err != nil {
t.Fatal(err)
}
}
}
================================================
FILE: search_queries_slice.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// SliceQuery allows to partition the documents into several slices.
// It is used e.g. to slice scroll operations in Elasticsearch 5.0 or later.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-request-scroll.html#sliced-scroll
// for details.
type SliceQuery struct {
field string
id *int
max *int
}
// NewSliceQuery creates a new SliceQuery.
func NewSliceQuery() *SliceQuery {
return &SliceQuery{}
}
// Field is the name of the field to slice against (_uid by default).
func (s *SliceQuery) Field(field string) *SliceQuery {
s.field = field
return s
}
// Id is the id of the slice.
func (s *SliceQuery) Id(id int) *SliceQuery {
s.id = &id
return s
}
// Max is the maximum number of slices.
func (s *SliceQuery) Max(max int) *SliceQuery {
s.max = &max
return s
}
// Source returns the JSON body.
func (s *SliceQuery) Source() (interface{}, error) {
m := make(map[string]interface{})
if s.field != "" {
m["field"] = s.field
}
if s.id != nil {
m["id"] = *s.id
}
if s.max != nil {
m["max"] = *s.max
}
return m, nil
}
================================================
FILE: search_queries_slice_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestSliceQuery(t *testing.T) {
q := NewSliceQuery().Field("date").Id(0).Max(2)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"field":"date","id":0,"max":2}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_queries_span_first.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// SpanFirstQuery spans near the beginning of a field.
// The span first query maps to Lucene SpanFirstQuery
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.7/query-dsl-span-first-query.html
// for details.
type SpanFirstQuery struct {
match Query
end int
boost *float64
queryName string
}
// NewSpanFirstQuery creates a new SpanFirstQuery.
func NewSpanFirstQuery(query Query, end int) *SpanFirstQuery {
return &SpanFirstQuery{
match: query,
end: end,
}
}
// Match sets the query, e.g. a SpanTermQuery.
func (q *SpanFirstQuery) Match(query Query) *SpanFirstQuery {
q.match = query
return q
}
// End specifies the maximum end position of the match, which needs to be positive.
func (q *SpanFirstQuery) End(end int) *SpanFirstQuery {
q.end = end
return q
}
// Boost sets the boost for this query.
func (q *SpanFirstQuery) Boost(boost float64) *SpanFirstQuery {
q.boost = &boost
return q
}
// QueryName sets the query name for the filter that can be used when
// searching for matched_filters per hit.
func (q *SpanFirstQuery) QueryName(queryName string) *SpanFirstQuery {
q.queryName = queryName
return q
}
// Source returns the JSON body.
func (q *SpanFirstQuery) Source() (interface{}, error) {
m := make(map[string]interface{})
c := make(map[string]interface{})
if v := q.match; v != nil {
src, err := q.match.Source()
if err != nil {
return nil, err
}
c["match"] = src
}
c["end"] = q.end
if v := q.boost; v != nil {
c["boost"] = *v
}
if v := q.queryName; v != "" {
c["query_name"] = v
}
m["span_first"] = c
return m, nil
}
================================================
FILE: search_queries_span_first_integration_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestSpanFirstQueryIntegration(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t) //, SetTraceLog(log.New(os.Stdout, "", log.LstdFlags)))
_, err := client.Search().
Index(testIndexName).
Query(NewSpanFirstQuery(NewSpanTermQuery("message", "Golang"), 1)).
Pretty(true).
Do(context.Background())
if err != nil {
t.Fatal(err)
}
}
================================================
FILE: search_queries_span_first_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestSpanFirstQuery(t *testing.T) {
q := NewSpanFirstQuery(
NewSpanTermQuery("user", "kimchy"),
3,
)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"span_first":{"end":3,"match":{"span_term":{"user":{"value":"kimchy"}}}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_queries_span_near.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// SpanNearQuery matches spans which are near one another. One can specify slop,
// the maximum number of intervening unmatched positions, as well as whether
// matches are required to be in-order. The span near query maps to Lucene SpanNearQuery.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.7/query-dsl-span-near-query.html
// for details.
type SpanNearQuery struct {
clauses []Query
slop *int
inOrder *bool
boost *float64
queryName string
}
// NewSpanNearQuery creates a new SpanNearQuery.
func NewSpanNearQuery(clauses ...Query) *SpanNearQuery {
return &SpanNearQuery{
clauses: clauses,
}
}
// Add clauses to use in the query.
func (q *SpanNearQuery) Add(clauses ...Query) *SpanNearQuery {
q.clauses = append(q.clauses, clauses...)
return q
}
// Clauses to use in the query.
func (q *SpanNearQuery) Clauses(clauses ...Query) *SpanNearQuery {
q.clauses = clauses
return q
}
// Slop controls the maximum number of intervening unmatched positions permitted.
func (q *SpanNearQuery) Slop(slop int) *SpanNearQuery {
q.slop = &slop
return q
}
// InOrder, when true, the spans from each clause must be in the same order as
// in Clauses and must be non-overlapping. Defaults to true.
func (q *SpanNearQuery) InOrder(inOrder bool) *SpanNearQuery {
q.inOrder = &inOrder
return q
}
// Boost sets the boost for this query.
func (q *SpanNearQuery) Boost(boost float64) *SpanNearQuery {
q.boost = &boost
return q
}
// QueryName sets the query name for the filter that can be used when
// searching for matched_filters per hit.
func (q *SpanNearQuery) QueryName(queryName string) *SpanNearQuery {
q.queryName = queryName
return q
}
// Source returns the JSON body.
func (q *SpanNearQuery) Source() (interface{}, error) {
m := make(map[string]interface{})
c := make(map[string]interface{})
if len(q.clauses) > 0 {
var clauses []interface{}
for _, clause := range q.clauses {
src, err := clause.Source()
if err != nil {
return nil, err
}
clauses = append(clauses, src)
}
c["clauses"] = clauses
}
if v := q.slop; v != nil {
c["slop"] = *v
}
if v := q.inOrder; v != nil {
c["in_order"] = *v
}
if v := q.boost; v != nil {
c["boost"] = *v
}
if v := q.queryName; v != "" {
c["query_name"] = v
}
m["span_near"] = c
return m, nil
}
================================================
FILE: search_queries_span_near_integration_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestSpanNearQueryIntegration(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t) //, SetTraceLog(log.New(os.Stdout, "", log.LstdFlags)))
_, err := client.Search().
Index(testIndexName).
Query(
NewSpanNearQuery(
NewSpanTermQuery("message", "Golang"),
NewSpanTermQuery("message", "Elasticsearch"),
).Boost(2),
).
Pretty(true).
Do(context.Background())
if err != nil {
t.Fatal(err)
}
}
================================================
FILE: search_queries_span_near_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestSpanNearQuery(t *testing.T) {
q := NewSpanNearQuery(
NewSpanTermQuery("field", "value1"),
NewSpanTermQuery("field", "value2"),
NewSpanTermQuery("field", "value3"),
).Slop(12).InOrder(false)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"span_near":{"clauses":[{"span_term":{"field":{"value":"value1"}}},{"span_term":{"field":{"value":"value2"}}},{"span_term":{"field":{"value":"value3"}}}],"in_order":false,"slop":12}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_queries_span_term.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// SpanTermQuery matches spans containing a term. The span term query maps to Lucene SpanTermQuery.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.7/query-dsl-span-term-query.html
// for details.
type SpanTermQuery struct {
field string
value interface{}
boost *float64
queryName string
}
// NewSpanTermQuery creates a new SpanTermQuery. When passing values, the first one
// is used to initialize the value.
func NewSpanTermQuery(field string, value ...interface{}) *SpanTermQuery {
q := &SpanTermQuery{
field: field,
}
if len(value) > 0 {
q.value = value[0]
}
return q
}
// Field name to match the term against.
func (q *SpanTermQuery) Field(field string) *SpanTermQuery {
q.field = field
return q
}
// Value of the term.
func (q *SpanTermQuery) Value(value interface{}) *SpanTermQuery {
q.value = value
return q
}
// Boost sets the boost for this query.
func (q *SpanTermQuery) Boost(boost float64) *SpanTermQuery {
q.boost = &boost
return q
}
// QueryName sets the query name for the filter that can be used when
// searching for matched_filters per hit.
func (q *SpanTermQuery) QueryName(queryName string) *SpanTermQuery {
q.queryName = queryName
return q
}
// Source returns the JSON body.
func (q *SpanTermQuery) Source() (interface{}, error) {
m := make(map[string]interface{})
c := make(map[string]interface{})
i := make(map[string]interface{})
i["value"] = q.value
if v := q.boost; v != nil {
i["boost"] = *v
}
if v := q.queryName; v != "" {
i["query_name"] = v
}
c[q.field] = i
m["span_term"] = c
return m, nil
}
================================================
FILE: search_queries_span_term_integration_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestSpanTermQueryIntegration(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t) //, SetTraceLog(log.New(os.Stdout, "", log.LstdFlags)))
_, err := client.Search().
Index(testIndexName).
Query(NewSpanTermQuery("message", "Golang")).
Pretty(true).
Do(context.Background())
if err != nil {
t.Fatal(err)
}
}
================================================
FILE: search_queries_span_term_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestSpanTermQuery(t *testing.T) {
q := NewSpanTermQuery("user", "kimchy").Boost(2.5)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"span_term":{"user":{"boost":2.5,"value":"kimchy"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSpanTermQueryWithFieldAndValue(t *testing.T) {
q := NewSpanTermQuery("user").Value("kimchy")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"span_term":{"user":{"value":"kimchy"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_queries_term.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// TermQuery finds documents that contain the exact term specified
// in the inverted index.
//
// For details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-term-query.html
type TermQuery struct {
name string
value interface{}
boost *float64
caseInsensitive *bool
queryName string
}
// NewTermQuery creates and initializes a new TermQuery.
func NewTermQuery(name string, value interface{}) *TermQuery {
return &TermQuery{name: name, value: value}
}
// Boost sets the boost for this query.
func (q *TermQuery) Boost(boost float64) *TermQuery {
q.boost = &boost
return q
}
func (q *TermQuery) CaseInsensitive(caseInsensitive bool) *TermQuery {
q.caseInsensitive = &caseInsensitive
return q
}
// QueryName sets the query name for the filter that can be used
// when searching for matched_filters per hit
func (q *TermQuery) QueryName(queryName string) *TermQuery {
q.queryName = queryName
return q
}
// Source returns JSON for the query.
func (q *TermQuery) Source() (interface{}, error) {
// {"term":{"name":"value"}}
source := make(map[string]interface{})
tq := make(map[string]interface{})
source["term"] = tq
if q.boost == nil && q.caseInsensitive == nil && q.queryName == "" {
tq[q.name] = q.value
} else {
subQ := make(map[string]interface{})
subQ["value"] = q.value
if q.boost != nil {
subQ["boost"] = *q.boost
}
if q.caseInsensitive != nil {
subQ["case_insensitive"] = *q.caseInsensitive
}
if q.queryName != "" {
subQ["_name"] = q.queryName
}
tq[q.name] = subQ
}
return source, nil
}
================================================
FILE: search_queries_term_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestTermQuery(t *testing.T) {
q := NewTermQuery("user", "ki")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"term":{"user":"ki"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestTermQueryWithCaseInsensitive(t *testing.T) {
q := NewTermQuery("user", "ki").CaseInsensitive(true)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"term":{"user":{"case_insensitive":true,"value":"ki"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestTermQueryWithOptions(t *testing.T) {
q := NewTermQuery("user", "ki")
q = q.Boost(2.79)
q = q.QueryName("my_tq")
q = q.CaseInsensitive(true)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"term":{"user":{"_name":"my_tq","boost":2.79,"case_insensitive":true,"value":"ki"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_queries_terms.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// TermsQuery filters documents that have fields that match any
// of the provided terms (not analyzed).
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-terms-query.html
type TermsQuery struct {
name string
values []interface{}
termsLookup *TermsLookup
queryName string
boost *float64
}
// NewTermsQuery creates and initializes a new TermsQuery.
func NewTermsQuery(name string, values ...interface{}) *TermsQuery {
q := &TermsQuery{
name: name,
values: make([]interface{}, 0),
}
if len(values) > 0 {
q.values = append(q.values, values...)
}
return q
}
// NewTermsQueryFromStrings creates and initializes a new TermsQuery
// from strings.
func NewTermsQueryFromStrings(name string, values ...string) *TermsQuery {
q := &TermsQuery{
name: name,
values: make([]interface{}, 0),
}
for _, v := range values {
q.values = append(q.values, v)
}
return q
}
// TermsLookup adds terms lookup details to the query.
func (q *TermsQuery) TermsLookup(lookup *TermsLookup) *TermsQuery {
q.termsLookup = lookup
return q
}
// Boost sets the boost for this query.
func (q *TermsQuery) Boost(boost float64) *TermsQuery {
q.boost = &boost
return q
}
// QueryName sets the query name for the filter that can be used
// when searching for matched_filters per hit
func (q *TermsQuery) QueryName(queryName string) *TermsQuery {
q.queryName = queryName
return q
}
// Creates the query source for the term query.
func (q *TermsQuery) Source() (interface{}, error) {
// {"terms":{"name":["value1","value2"]}}
source := make(map[string]interface{})
params := make(map[string]interface{})
source["terms"] = params
if q.termsLookup != nil {
src, err := q.termsLookup.Source()
if err != nil {
return nil, err
}
params[q.name] = src
} else {
params[q.name] = q.values
if q.boost != nil {
params["boost"] = *q.boost
}
if q.queryName != "" {
params["_name"] = q.queryName
}
}
return source, nil
}
================================================
FILE: search_queries_terms_set.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// TermsSetQuery returns any documents that match with at least
// one or more of the provided terms. The terms are not analyzed
// and thus must match exactly. The number of terms that must
// match varies per document and is either controlled by a
// minimum should match field or computed per document in a
// minimum should match script.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-terms-set-query.html
type TermsSetQuery struct {
name string
values []interface{}
minimumShouldMatchField string
minimumShouldMatchScript *Script
queryName string
boost *float64
}
// NewTermsSetQuery creates and initializes a new TermsSetQuery.
func NewTermsSetQuery(name string, values ...interface{}) *TermsSetQuery {
q := &TermsSetQuery{
name: name,
values: make([]interface{}, 0),
}
if len(values) > 0 {
q.values = append(q.values, values...)
}
return q
}
// MinimumShouldMatchField specifies the field to match.
func (q *TermsSetQuery) MinimumShouldMatchField(minimumShouldMatchField string) *TermsSetQuery {
q.minimumShouldMatchField = minimumShouldMatchField
return q
}
// MinimumShouldMatchScript specifies the script to match.
func (q *TermsSetQuery) MinimumShouldMatchScript(minimumShouldMatchScript *Script) *TermsSetQuery {
q.minimumShouldMatchScript = minimumShouldMatchScript
return q
}
// Boost sets the boost for this query.
func (q *TermsSetQuery) Boost(boost float64) *TermsSetQuery {
q.boost = &boost
return q
}
// QueryName sets the query name for the filter that can be used
// when searching for matched_filters per hit
func (q *TermsSetQuery) QueryName(queryName string) *TermsSetQuery {
q.queryName = queryName
return q
}
// Source creates the query source for the term query.
func (q *TermsSetQuery) Source() (interface{}, error) {
// {"terms_set":{"codes":{"terms":["abc","def"],"minimum_should_match_field":"required_matches"}}}
source := make(map[string]interface{})
inner := make(map[string]interface{})
params := make(map[string]interface{})
inner[q.name] = params
source["terms_set"] = inner
// terms
params["terms"] = q.values
// minimum_should_match_field
if match := q.minimumShouldMatchField; match != "" {
params["minimum_should_match_field"] = match
}
// minimum_should_match_script
if match := q.minimumShouldMatchScript; match != nil {
src, err := match.Source()
if err != nil {
return nil, err
}
params["minimum_should_match_script"] = src
}
// Common parameters for all queries
if q.boost != nil {
params["boost"] = *q.boost
}
if q.queryName != "" {
params["_name"] = q.queryName
}
return source, nil
}
================================================
FILE: search_queries_terms_set_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"testing"
)
func TestTermsSetQueryWithField(t *testing.T) {
q := NewTermsSetQuery("codes", "abc", "def", "ghi").MinimumShouldMatchField("required_matches")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"terms_set":{"codes":{"minimum_should_match_field":"required_matches","terms":["abc","def","ghi"]}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestTermsSetQueryWithScript(t *testing.T) {
q := NewTermsSetQuery("codes", "abc", "def", "ghi").
MinimumShouldMatchScript(
NewScript(`Math.min(params.num_terms, doc['required_matches'].value)`),
)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"terms_set":{"codes":{"minimum_should_match_script":{"source":"Math.min(params.num_terms, doc['required_matches'].value)"},"terms":["abc","def","ghi"]}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSearchTermsSetQuery(t *testing.T) {
//client := setupTestClientAndCreateIndexAndAddDocs(t, SetTraceLog(log.New(os.Stdout, "", log.LstdFlags)))
client := setupTestClientAndCreateIndexAndAddDocs(t)
// Match all should return all documents
searchResult, err := client.Search().
Index(testIndexName).
Query(
NewTermsSetQuery("user", "olivere", "sandrae").
MinimumShouldMatchField("retweets"),
).
Pretty(true).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.Hits == nil {
t.Errorf("expected SearchResult.Hits != nil; got nil")
}
if got, want := searchResult.TotalHits(), int64(1); got != want {
t.Errorf("expected SearchResult.TotalHits() = %d; got %d", want, got)
}
if got, want := len(searchResult.Hits.Hits), 1; got != want {
t.Errorf("expected len(SearchResult.Hits.Hits) = %d; got %d", want, got)
}
}
================================================
FILE: search_queries_terms_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestTermsQuery(t *testing.T) {
q := NewTermsQuery("user", "ki")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"terms":{"user":["ki"]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestTermsQueryFromStrings(t *testing.T) {
values := []string{"one", "two", "three"}
q := NewTermsQueryFromStrings("tags", values...)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"terms":{"tags":["one","two","three"]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestTermsQueryWithEmptyArray(t *testing.T) {
included := make([]interface{}, 0)
q := NewTermsQuery("tags", included...)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"terms":{"tags":[]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestTermsQueryWithTermsLookup(t *testing.T) {
q := NewTermsQuery("user").
TermsLookup(NewTermsLookup().Index("users").Type("user").Id("2").Path("followers"))
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"terms":{"user":{"id":"2","index":"users","path":"followers","type":"user"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestTermQuerysWithOptions(t *testing.T) {
q := NewTermsQuery("user", "ki", "ko")
q = q.Boost(2.79)
q = q.QueryName("my_tq")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"terms":{"_name":"my_tq","boost":2.79,"user":["ki","ko"]}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_queries_type.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// TypeQuery filters documents matching the provided document / mapping type.
//
// For details, see:
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-type-query.html
type TypeQuery struct {
typ string
}
func NewTypeQuery(typ string) *TypeQuery {
return &TypeQuery{typ: typ}
}
// Source returns JSON for the query.
func (q *TypeQuery) Source() (interface{}, error) {
source := make(map[string]interface{})
params := make(map[string]interface{})
source["type"] = params
params["value"] = q.typ
return source, nil
}
================================================
FILE: search_queries_type_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestTypeQuery(t *testing.T) {
q := NewTypeQuery("my_type")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"type":{"value":"my_type"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_queries_wildcard.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// WildcardQuery matches documents that have fields matching a wildcard
// expression (not analyzed). Supported wildcards are *, which matches
// any character sequence (including the empty one), and ?, which matches
// any single character. Note this query can be slow, as it needs to iterate
// over many terms. In order to prevent extremely slow wildcard queries,
// a wildcard term should not start with one of the wildcards * or ?.
// The wildcard query maps to Lucene WildcardQuery.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.x/query-dsl-wildcard-query.html
type WildcardQuery struct {
name string
wildcard string
boost *float64
rewrite string
queryName string
caseInsensitive *bool
}
// NewWildcardQuery creates and initializes a new WildcardQuery.
func NewWildcardQuery(name, wildcard string) *WildcardQuery {
return &WildcardQuery{
name: name,
wildcard: wildcard,
}
}
// Boost sets the boost for this query.
func (q *WildcardQuery) Boost(boost float64) *WildcardQuery {
q.boost = &boost
return q
}
func (q *WildcardQuery) Rewrite(rewrite string) *WildcardQuery {
q.rewrite = rewrite
return q
}
// QueryName sets the name of this query.
func (q *WildcardQuery) QueryName(queryName string) *WildcardQuery {
q.queryName = queryName
return q
}
// CaseInsensitive sets case insensitive matching of this query.
func (q *WildcardQuery) CaseInsensitive(caseInsensitive bool) *WildcardQuery {
q.caseInsensitive = &caseInsensitive
return q
}
// Source returns the JSON serializable body of this query.
func (q *WildcardQuery) Source() (interface{}, error) {
// {
// "wildcard" : {
// "user" : {
// "value" : "ki*y",
// "boost" : 1.0,
// "case_insensitive" : true
// }
// }
source := make(map[string]interface{})
query := make(map[string]interface{})
source["wildcard"] = query
wq := make(map[string]interface{})
query[q.name] = wq
wq["value"] = q.wildcard
if q.boost != nil {
wq["boost"] = *q.boost
}
if q.rewrite != "" {
wq["rewrite"] = q.rewrite
}
if q.queryName != "" {
wq["_name"] = q.queryName
}
if q.caseInsensitive != nil {
wq["case_insensitive"] = *q.caseInsensitive
}
return source, nil
}
================================================
FILE: search_queries_wildcard_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic_test
import (
"context"
"encoding/json"
"testing"
"github.com/olivere/elastic/v7"
)
func ExampleWildcardQuery() {
// Get a client to the local Elasticsearch instance.
client, err := elastic.NewClient()
if err != nil {
// Handle error
panic(err)
}
// Define wildcard query
q := elastic.NewWildcardQuery("user", "oli*er?").Boost(1.2)
searchResult, err := client.Search().
Index("twitter"). // search in index "twitter"
Query(q). // use wildcard query defined above
Do(context.TODO()) // execute
if err != nil {
// Handle error
panic(err)
}
_ = searchResult
}
func TestWildcardQuery(t *testing.T) {
q := elastic.NewWildcardQuery("user", "ki*y??")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"wildcard":{"user":{"value":"ki*y??"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestWildcardQueryWithBoost(t *testing.T) {
q := elastic.NewWildcardQuery("user", "ki*y??").Boost(1.2)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"wildcard":{"user":{"boost":1.2,"value":"ki*y??"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestWildcardQueryWithCaseInsensitive(t *testing.T) {
q := elastic.NewWildcardQuery("user", "ki*y??").CaseInsensitive(true)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"wildcard":{"user":{"case_insensitive":true,"value":"ki*y??"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_queries_wrapper.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// WrapperQuery accepts any other query as base64 encoded string.
//
// For details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-wrapper-query.html.
type WrapperQuery struct {
source string
}
// NewWrapperQuery creates and initializes a new WrapperQuery.
func NewWrapperQuery(source string) *WrapperQuery {
return &WrapperQuery{source: source}
}
// Source returns JSON for the query.
func (q *WrapperQuery) Source() (interface{}, error) {
// {"wrapper":{"query":"..."}}
source := make(map[string]interface{})
tq := make(map[string]interface{})
source["wrapper"] = tq
tq["query"] = q.source
return source, nil
}
================================================
FILE: search_queries_wrapper_integration_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/base64"
"encoding/json"
"testing"
)
func TestWrapperQueryIntegration(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t) //, SetTraceLog(log.New(os.Stdout, "", log.LstdFlags)))
tq := NewTermQuery("user", "olivere")
src, err := tq.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
base64string := base64.StdEncoding.EncodeToString(data)
q := NewWrapperQuery(base64string)
// Match all should return all documents
searchResult, err := client.Search().
Index(testIndexName).
Query(q).
Pretty(true).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.Hits == nil {
t.Errorf("expected SearchResult.Hits != nil; got nil")
}
if got, want := searchResult.TotalHits(), int64(2); got != want {
t.Errorf("expected SearchResult.TotalHits() = %d; got %d", want, got)
}
if got, want := len(searchResult.Hits.Hits), 2; got != want {
t.Errorf("expected len(SearchResult.Hits.Hits) = %d; got %d", want, got)
}
}
================================================
FILE: search_queries_wrapper_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestWrapperQuery(t *testing.T) {
q := NewWrapperQuery("eyJ0ZXJtIiA6IHsgInVzZXIiIDogIktpbWNoeSIgfX0=")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"wrapper":{"query":"eyJ0ZXJtIiA6IHsgInVzZXIiIDogIktpbWNoeSIgfX0="}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_request.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"strings"
)
// SearchRequest combines a search request and its
// query details (see SearchSource).
// It is used in combination with MultiSearch.
type SearchRequest struct {
searchType string
indices []string
types []string
routing *string
preference *string
requestCache *bool
allowPartialSearchResults *bool
ignoreUnavailable *bool
allowNoIndices *bool
expandWildcards string
scroll string
source interface{}
searchSource *SearchSource
batchedReduceSize *int
maxConcurrentShardRequests *int
preFilterShardSize *int
}
// NewSearchRequest creates a new search request.
func NewSearchRequest() *SearchRequest {
return &SearchRequest{
searchSource: NewSearchSource(),
}
}
// SearchType must be one of "dfs_query_then_fetch", "dfs_query_and_fetch",
// "query_then_fetch", or "query_and_fetch".
func (r *SearchRequest) SearchType(searchType string) *SearchRequest {
r.searchType = searchType
return r
}
// SearchTypeDfsQueryThenFetch sets search type to "dfs_query_then_fetch".
func (r *SearchRequest) SearchTypeDfsQueryThenFetch() *SearchRequest {
return r.SearchType("dfs_query_then_fetch")
}
// SearchTypeQueryThenFetch sets search type to "query_then_fetch".
func (r *SearchRequest) SearchTypeQueryThenFetch() *SearchRequest {
return r.SearchType("query_then_fetch")
}
// Index specifies the indices to use in the request.
func (r *SearchRequest) Index(indices ...string) *SearchRequest {
r.indices = append(r.indices, indices...)
return r
}
// HasIndices returns true if there are indices used, false otherwise.
func (r *SearchRequest) HasIndices() bool {
return len(r.indices) > 0
}
// Type specifies one or more types to be used.
//
// Deprecated: Types are in the process of being removed. Instead of using a type, prefer to
// filter on a field on the document.
func (r *SearchRequest) Type(types ...string) *SearchRequest {
r.types = append(r.types, types...)
return r
}
// Routing specifies the routing parameter. It is a comma-separated list.
func (r *SearchRequest) Routing(routing string) *SearchRequest {
r.routing = &routing
return r
}
// Routings to be used in the request.
func (r *SearchRequest) Routings(routings ...string) *SearchRequest {
if routings != nil {
routings := strings.Join(routings, ",")
r.routing = &routings
} else {
r.routing = nil
}
return r
}
// Preference to execute the search. Defaults to randomize across shards.
// Can be set to "_local" to prefer local shards, "_primary" to execute
// only on primary shards, or a custom value, which guarantees that the
// same order will be used across different requests.
func (r *SearchRequest) Preference(preference string) *SearchRequest {
r.preference = &preference
return r
}
// RequestCache specifies if this request should use the request cache
// or not, assuming that it can. By default, will default to the index
// level setting if request cache is enabled or not.
func (r *SearchRequest) RequestCache(requestCache bool) *SearchRequest {
r.requestCache = &requestCache
return r
}
// IgnoreUnavailable indicates whether specified concrete indices should be
// ignored when unavailable (missing or closed).
func (s *SearchRequest) IgnoreUnavailable(ignoreUnavailable bool) *SearchRequest {
s.ignoreUnavailable = &ignoreUnavailable
return s
}
// AllowNoIndices indicates whether to ignore if a wildcard indices
// expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified).
func (s *SearchRequest) AllowNoIndices(allowNoIndices bool) *SearchRequest {
s.allowNoIndices = &allowNoIndices
return s
}
// ExpandWildcards indicates whether to expand wildcard expression to
// concrete indices that are open, closed or both.
func (s *SearchRequest) ExpandWildcards(expandWildcards string) *SearchRequest {
s.expandWildcards = expandWildcards
return s
}
// Scroll, if set, will enable scrolling of the search request.
// Pass a timeout value, e.g. "2m" or "30s" as a value.
func (r *SearchRequest) Scroll(scroll string) *SearchRequest {
r.scroll = scroll
return r
}
// SearchSource allows passing your own SearchSource, overriding
// all values set on the request (except Source).
func (r *SearchRequest) SearchSource(searchSource *SearchSource) *SearchRequest {
if searchSource == nil {
r.searchSource = NewSearchSource()
return r
}
r.searchSource = searchSource
return r
}
// Source allows passing your own request body. It will have preference over
// all other properties set on the request.
func (r *SearchRequest) Source(source interface{}) *SearchRequest {
r.source = source
return r
}
// Timeout value for the request, e.g. "30s" or "2m".
func (r *SearchRequest) Timeout(timeout string) *SearchRequest {
r.searchSource = r.searchSource.Timeout(timeout)
return r
}
// TerminateAfter, when set, specifies an optional document count,
// upon collecting which the search query will terminate early.
func (r *SearchRequest) TerminateAfter(docs int) *SearchRequest {
r.searchSource = r.searchSource.TerminateAfter(docs)
return r
}
// Query for the search.
func (r *SearchRequest) Query(query Query) *SearchRequest {
r.searchSource = r.searchSource.Query(query)
return r
}
// PostFilter is a filter that will be executed after the query
// has been executed and only has affect on the search hits
// (not aggregations). This filter is always executed as last
// filtering mechanism.
func (r *SearchRequest) PostFilter(filter Query) *SearchRequest {
r.searchSource = r.searchSource.PostFilter(filter)
return r
}
// MinScore below which documents are filtered out.
func (r *SearchRequest) MinScore(minScore float64) *SearchRequest {
r.searchSource = r.searchSource.MinScore(minScore)
return r
}
// From index to start search from (default is 0).
func (r *SearchRequest) From(from int) *SearchRequest {
r.searchSource = r.searchSource.From(from)
return r
}
// Size is the number of search hits to return (default is 10).
func (r *SearchRequest) Size(size int) *SearchRequest {
r.searchSource = r.searchSource.Size(size)
return r
}
// Explain indicates whether to return an explanation for each hit.
func (r *SearchRequest) Explain(explain bool) *SearchRequest {
r.searchSource = r.searchSource.Explain(explain)
return r
}
// Version indicates whether each hit should be returned with
// its version.
func (r *SearchRequest) Version(version bool) *SearchRequest {
r.searchSource = r.searchSource.Version(version)
return r
}
// IndexBoost sets a boost a specific index will receive when
// the query is executed against it.
func (r *SearchRequest) IndexBoost(index string, boost float64) *SearchRequest {
r.searchSource = r.searchSource.IndexBoost(index, boost)
return r
}
// Stats groups that this request will be aggregated under.
func (r *SearchRequest) Stats(statsGroup ...string) *SearchRequest {
r.searchSource = r.searchSource.Stats(statsGroup...)
return r
}
// FetchSource indicates whether the response should contain the stored
// _source for every hit.
func (r *SearchRequest) FetchSource(fetchSource bool) *SearchRequest {
r.searchSource = r.searchSource.FetchSource(fetchSource)
return r
}
// FetchSourceIncludeExclude specifies that _source should be returned
// with each hit, where "include" and "exclude" serve as a simple wildcard
// matcher that gets applied to its fields
// (e.g. include := []string{"obj1.*","obj2.*"}, exclude := []string{"description.*"}).
func (r *SearchRequest) FetchSourceIncludeExclude(include, exclude []string) *SearchRequest {
r.searchSource = r.searchSource.FetchSourceIncludeExclude(include, exclude)
return r
}
// FetchSourceContext indicates how the _source should be fetched.
func (r *SearchRequest) FetchSourceContext(fsc *FetchSourceContext) *SearchRequest {
r.searchSource = r.searchSource.FetchSourceContext(fsc)
return r
}
// DocValueField adds a docvalue based field to load and return.
// The field does not have to be stored, but it's recommended to use
// non analyzed or numeric fields.
func (r *SearchRequest) DocValueField(field string) *SearchRequest {
r.searchSource = r.searchSource.DocvalueField(field)
return r
}
// DocValueFieldWithFormat adds a docvalue based field to load and return.
// The field does not have to be stored, but it's recommended to use
// non analyzed or numeric fields.
func (r *SearchRequest) DocValueFieldWithFormat(field DocvalueField) *SearchRequest {
r.searchSource = r.searchSource.DocvalueFieldWithFormat(field)
return r
}
// DocValueFields adds one or more docvalue based field to load and return.
// The fields do not have to be stored, but it's recommended to use
// non analyzed or numeric fields.
func (r *SearchRequest) DocValueFields(fields ...string) *SearchRequest {
r.searchSource = r.searchSource.DocvalueFields(fields...)
return r
}
// DocValueFieldsWithFormat adds one or more docvalue based field to load and return.
// The fields do not have to be stored, but it's recommended to use
// non analyzed or numeric fields.
func (r *SearchRequest) DocValueFieldsWithFormat(fields ...DocvalueField) *SearchRequest {
r.searchSource = r.searchSource.DocvalueFieldsWithFormat(fields...)
return r
}
// StoredField adds a stored field to load and return
// (note, it must be stored) as part of the search request.
func (r *SearchRequest) StoredField(field string) *SearchRequest {
r.searchSource = r.searchSource.StoredField(field)
return r
}
// NoStoredFields indicates that no fields should be loaded,
// resulting in only id and type to be returned per field.
func (r *SearchRequest) NoStoredFields() *SearchRequest {
r.searchSource = r.searchSource.NoStoredFields()
return r
}
// StoredFields adds one or more stored field to load and return
// (note, they must be stored) as part of the search request.
func (r *SearchRequest) StoredFields(fields ...string) *SearchRequest {
r.searchSource = r.searchSource.StoredFields(fields...)
return r
}
// ScriptField adds a script based field to load and return.
// The field does not have to be stored, but it's recommended
// to use non analyzed or numeric fields.
func (r *SearchRequest) ScriptField(field *ScriptField) *SearchRequest {
r.searchSource = r.searchSource.ScriptField(field)
return r
}
// ScriptFields adds one or more script based field to load and return.
// The fields do not have to be stored, but it's recommended
// to use non analyzed or numeric fields.
func (r *SearchRequest) ScriptFields(fields ...*ScriptField) *SearchRequest {
r.searchSource = r.searchSource.ScriptFields(fields...)
return r
}
// Sort adds a sort order.
func (r *SearchRequest) Sort(field string, ascending bool) *SearchRequest {
r.searchSource = r.searchSource.Sort(field, ascending)
return r
}
// SortWithInfo adds a sort order.
func (r *SearchRequest) SortWithInfo(info SortInfo) *SearchRequest {
r.searchSource = r.searchSource.SortWithInfo(info)
return r
}
// SortBy adds a sort order.
func (r *SearchRequest) SortBy(sorter ...Sorter) *SearchRequest {
r.searchSource = r.searchSource.SortBy(sorter...)
return r
}
// SearchAfter sets the sort values that indicates which docs this
// request should "search after".
func (r *SearchRequest) SearchAfter(sortValues ...interface{}) *SearchRequest {
r.searchSource = r.searchSource.SearchAfter(sortValues...)
return r
}
// Slice allows partitioning the documents in multiple slices.
// It is e.g. used to slice a scroll operation, supported in
// Elasticsearch 5.0 or later.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-request-scroll.html#sliced-scroll
// for details.
func (r *SearchRequest) Slice(sliceQuery Query) *SearchRequest {
r.searchSource = r.searchSource.Slice(sliceQuery)
return r
}
// TrackScores is applied when sorting and controls if scores will be
// tracked as well. Defaults to false.
func (r *SearchRequest) TrackScores(trackScores bool) *SearchRequest {
r.searchSource = r.searchSource.TrackScores(trackScores)
return r
}
// TrackTotalHits indicates if the total hit count for the query should be tracked.
// Defaults to true.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-request-track-total-hits.html
// for details.
func (r *SearchRequest) TrackTotalHits(trackTotalHits interface{}) *SearchRequest {
r.searchSource = r.searchSource.TrackTotalHits(trackTotalHits)
return r
}
// Aggregation adds an aggreation to perform as part of the search.
func (r *SearchRequest) Aggregation(name string, aggregation Aggregation) *SearchRequest {
r.searchSource = r.searchSource.Aggregation(name, aggregation)
return r
}
// Highlight adds highlighting to the search.
func (r *SearchRequest) Highlight(highlight *Highlight) *SearchRequest {
r.searchSource = r.searchSource.Highlight(highlight)
return r
}
// Suggester adds a suggester to the search.
func (r *SearchRequest) Suggester(suggester Suggester) *SearchRequest {
r.searchSource = r.searchSource.Suggester(suggester)
return r
}
// Rescorer adds a rescorer to the search.
func (r *SearchRequest) Rescorer(rescore *Rescore) *SearchRequest {
r.searchSource = r.searchSource.Rescorer(rescore)
return r
}
// ClearRescorers removes all rescorers from the search.
func (r *SearchRequest) ClearRescorers() *SearchRequest {
r.searchSource = r.searchSource.ClearRescorers()
return r
}
// Profile specifies that this search source should activate the
// Profile API for queries made on it.
func (r *SearchRequest) Profile(profile bool) *SearchRequest {
r.searchSource = r.searchSource.Profile(profile)
return r
}
// Collapse adds field collapsing.
func (r *SearchRequest) Collapse(collapse *CollapseBuilder) *SearchRequest {
r.searchSource = r.searchSource.Collapse(collapse)
return r
}
// PointInTime specifies an optional PointInTime to be used in the context
// of this search.
func (s *SearchRequest) PointInTime(pointInTime *PointInTime) *SearchRequest {
s.searchSource = s.searchSource.PointInTime(pointInTime)
return s
}
// AllowPartialSearchResults indicates if this request should allow partial
// results. (If method is not called, will default to the cluster level
// setting).
func (r *SearchRequest) AllowPartialSearchResults(allow bool) *SearchRequest {
r.allowPartialSearchResults = &allow
return r
}
// BatchedReduceSize specifies the number of shard results that should be
// reduced at once on the coordinating node. This value should be used
// as a protection mechanism to reduce the memory overhead per search request
// if the potential number of shards in the request can be large.
func (r *SearchRequest) BatchedReduceSize(size int) *SearchRequest {
r.batchedReduceSize = &size
return r
}
// MaxConcurrentShardRequests sets the number of shard requests that should
// be executed concurrently. This value should be used as a protection
// mechanism to reduce the number of shard requests fired per high level
// search request. Searches that hit the entire cluster can be throttled
// with this number to reduce the cluster load. The default grows with
// the number of nodes in the cluster but is at most 256.
func (r *SearchRequest) MaxConcurrentShardRequests(size int) *SearchRequest {
r.maxConcurrentShardRequests = &size
return r
}
// PreFilterShardSize sets a threshold that enforces a pre-filter roundtrip
// to pre-filter search shards based on query rewriting if the number of
// shards the search request expands to exceeds the threshold.
// This filter roundtrip can limit the number of shards significantly if for
// instance a shard can not match any documents based on it's rewrite
// method ie. if date filters are mandatory to match but the shard
// bounds and the query are disjoint. The default is 128.
func (r *SearchRequest) PreFilterShardSize(size int) *SearchRequest {
r.preFilterShardSize = &size
return r
}
// header is used e.g. by MultiSearch to get information about the search header
// of one SearchRequest.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-multi-search.html
func (r *SearchRequest) header() interface{} {
h := make(map[string]interface{})
if r.searchType != "" {
h["search_type"] = r.searchType
}
switch len(r.indices) {
case 0:
case 1:
h["index"] = r.indices[0]
default:
h["indices"] = r.indices
}
switch len(r.types) {
case 0:
case 1:
h["type"] = r.types[0]
default:
h["types"] = r.types
}
if r.routing != nil && *r.routing != "" {
h["routing"] = *r.routing
}
if r.preference != nil && *r.preference != "" {
h["preference"] = *r.preference
}
if r.requestCache != nil {
h["request_cache"] = *r.requestCache
}
if r.ignoreUnavailable != nil {
h["ignore_unavailable"] = *r.ignoreUnavailable
}
if r.allowNoIndices != nil {
h["allow_no_indices"] = *r.allowNoIndices
}
if r.expandWildcards != "" {
h["expand_wildcards"] = r.expandWildcards
}
if v := r.allowPartialSearchResults; v != nil {
h["allow_partial_search_results"] = *v
}
if r.scroll != "" {
h["scroll"] = r.scroll
}
return h
}
// Body allows to access the search body of the request, as generated by the DSL.
// Notice that Body is read-only. You must not change the request body.
//
// Body is used e.g. by MultiSearch to get information about the search body
// of one SearchRequest.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-multi-search.html
func (r *SearchRequest) Body() (string, error) {
if r.source == nil {
// Default: No custom source specified
src, err := r.searchSource.Source()
if err != nil {
return "", err
}
body, err := json.Marshal(src)
if err != nil {
return "", err
}
return string(body), nil
}
switch t := r.source.(type) {
default:
body, err := json.Marshal(r.source)
if err != nil {
return "", err
}
return string(body), nil
case *SearchSource:
src, err := t.Source()
if err != nil {
return "", err
}
body, err := json.Marshal(src)
if err != nil {
return "", err
}
return string(body), nil
case json.RawMessage:
return string(t), nil
case *json.RawMessage:
return string(*t), nil
case string:
return t, nil
case *string:
if t != nil {
return *t, nil
}
return "{}", nil
}
}
// source returns the search source. It is used by Reindex.
func (r *SearchRequest) sourceAsMap() (interface{}, error) {
if r.source == nil {
// Default: No custom source specified
return r.searchSource.Source()
}
switch t := r.source.(type) {
default:
body, err := json.Marshal(r.source)
if err != nil {
return "", err
}
return RawStringQuery(body), nil
case *SearchSource:
return t.Source()
case json.RawMessage:
return RawStringQuery(string(t)), nil
case *json.RawMessage:
return RawStringQuery(string(*t)), nil
case string:
return RawStringQuery(t), nil
case *string:
if t != nil {
return RawStringQuery(*t), nil
}
return RawStringQuery("{}"), nil
}
}
================================================
FILE: search_request_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
_ "net/http"
"testing"
)
func TestSearchRequestIndex(t *testing.T) {
builder := NewSearchRequest().Index("test")
data, err := json.Marshal(builder.header())
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"index":"test"}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSearchRequestIndices(t *testing.T) {
builder := NewSearchRequest().Index("test", "test2")
data, err := json.Marshal(builder.header())
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"indices":["test","test2"]}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSearchRequestHasIndices(t *testing.T) {
builder := NewSearchRequest()
if builder.HasIndices() {
t.Errorf("expected HasIndices to return true; got %v", builder.HasIndices())
}
builder = builder.Index("test", "test2")
if !builder.HasIndices() {
t.Errorf("expected HasIndices to return false; got %v", builder.HasIndices())
}
}
func TestSearchRequestIgnoreUnavailable(t *testing.T) {
builder := NewSearchRequest().Index("test").IgnoreUnavailable(true)
data, err := json.Marshal(builder.header())
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"ignore_unavailable":true,"index":"test"}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_shards.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"time"
"github.com/olivere/elastic/v7/uritemplates"
)
// SearchShardsService returns the indices and shards that a search request would be executed against.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-shards.html
type SearchShardsService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
index []string
routing string
local *bool
preference string
ignoreUnavailable *bool
allowNoIndices *bool
expandWildcards string
}
// NewSearchShardsService creates a new SearchShardsService.
func NewSearchShardsService(client *Client) *SearchShardsService {
return &SearchShardsService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *SearchShardsService) Pretty(pretty bool) *SearchShardsService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *SearchShardsService) Human(human bool) *SearchShardsService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *SearchShardsService) ErrorTrace(errorTrace bool) *SearchShardsService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *SearchShardsService) FilterPath(filterPath ...string) *SearchShardsService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *SearchShardsService) Header(name string, value string) *SearchShardsService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *SearchShardsService) Headers(headers http.Header) *SearchShardsService {
s.headers = headers
return s
}
// Index sets the names of the indices to restrict the results.
func (s *SearchShardsService) Index(index ...string) *SearchShardsService {
s.index = append(s.index, index...)
return s
}
//A boolean value whether to read the cluster state locally in order to
//determine where shards are allocated instead of using the Master node’s cluster state.
func (s *SearchShardsService) Local(local bool) *SearchShardsService {
s.local = &local
return s
}
// Routing sets a specific routing value.
func (s *SearchShardsService) Routing(routing string) *SearchShardsService {
s.routing = routing
return s
}
// Preference specifies the node or shard the operation should be performed on (default: random).
func (s *SearchShardsService) Preference(preference string) *SearchShardsService {
s.preference = preference
return s
}
// IgnoreUnavailable indicates whether the specified concrete indices
// should be ignored when unavailable (missing or closed).
func (s *SearchShardsService) IgnoreUnavailable(ignoreUnavailable bool) *SearchShardsService {
s.ignoreUnavailable = &ignoreUnavailable
return s
}
// AllowNoIndices indicates whether to ignore if a wildcard indices
// expression resolves into no concrete indices. (This includes `_all` string
// or when no indices have been specified).
func (s *SearchShardsService) AllowNoIndices(allowNoIndices bool) *SearchShardsService {
s.allowNoIndices = &allowNoIndices
return s
}
// ExpandWildcards indicates whether to expand wildcard expression to
// concrete indices that are open, closed or both.
func (s *SearchShardsService) ExpandWildcards(expandWildcards string) *SearchShardsService {
s.expandWildcards = expandWildcards
return s
}
// buildURL builds the URL for the operation.
func (s *SearchShardsService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/{index}/_search_shards", map[string]string{
"index": strings.Join(s.index, ","),
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.preference != "" {
params.Set("preference", s.preference)
}
if s.local != nil {
params.Set("local", fmt.Sprintf("%v", *s.local))
}
if s.routing != "" {
params.Set("routing", s.routing)
}
if s.allowNoIndices != nil {
params.Set("allow_no_indices", fmt.Sprintf("%v", *s.allowNoIndices))
}
if s.expandWildcards != "" {
params.Set("expand_wildcards", s.expandWildcards)
}
if s.ignoreUnavailable != nil {
params.Set("ignore_unavailable", fmt.Sprintf("%v", *s.ignoreUnavailable))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *SearchShardsService) Validate() error {
var invalid []string
if len(s.index) < 1 {
invalid = append(invalid, "Index")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *SearchShardsService) Do(ctx context.Context) (*SearchShardsResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(SearchShardsResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// SearchShardsResponse is the response of SearchShardsService.Do.
type SearchShardsResponse struct {
Nodes map[string]interface{} `json:"nodes"`
Indices map[string]interface{} `json:"indices"`
Shards [][]*SearchShardsResponseShardsInfo `json:"shards"`
}
type SearchShardsResponseShardsInfo struct {
Index string `json:"index"`
Node string `json:"node"`
Primary bool `json:"primary"`
Shard uint `json:"shard"`
State string `json:"state"`
AllocationId *AllocationId `json:"allocation_id,omitempty"`
RelocatingNode string `json:"relocating_node"`
ExpectedShardSizeInBytes int64 `json:"expected_shard_size_in_bytes,omitempty"`
RecoverySource *RecoverySource `json:"recovery_source,omitempty"`
UnassignedInfo *UnassignedInfo `json:"unassigned_info,omitempty"`
}
type RecoverySource struct {
Type string `json:"type"`
// TODO add missing fields here based on the Type
}
type AllocationId struct {
Id string `json:"id"`
RelocationId string `json:"relocation_id,omitempty"`
}
type UnassignedInfo struct {
Reason string `json:"reason"`
At *time.Time `json:"at,omitempty"`
FailedAttempts int `json:"failed_attempts,omitempty"`
Delayed bool `json:"delayed"`
Details string `json:"details,omitempty"`
AllocationStatus string `json:"allocation_status"`
}
================================================
FILE: search_shards_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestSearchShards(t *testing.T) {
client := setupTestClientAndCreateIndex(t) //, SetTraceLog(log.New(os.Stdout, "", log.LstdFlags)))
indexes := []string{testIndexName}
shardsInfo, err := client.SearchShards(indexes...).
Pretty(true).
Do(context.Background())
if err != nil {
t.Fatal(err)
}
if shardsInfo == nil {
t.Fatal("expected to return an shards information")
}
if len(shardsInfo.Shards) < 1 {
t.Fatal("expected to return minimun one shard information")
}
if shardsInfo.Shards[0][0].Index != testIndexName {
t.Fatal("expected to return shard info concerning requested index")
}
if shardsInfo.Shards[0][0].State != "STARTED" {
t.Fatal("expected to return STARTED status for running shards")
}
}
================================================
FILE: search_source.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"fmt"
)
// SearchSource enables users to build the search source.
// It resembles the SearchSourceBuilder in Elasticsearch.
type SearchSource struct {
query Query // query
postQuery Query // post_filter
sliceQuery Query // slice
from int // from
size int // size
explain *bool // explain
version *bool // version
seqNoAndPrimaryTerm *bool // seq_no_primary_term
sorters []Sorter // sort
trackScores *bool // track_scores
trackTotalHits interface{} // track_total_hits
searchAfterSortValues []interface{} // search_after
minScore *float64 // min_score
timeout string // timeout
terminateAfter *int // terminate_after
storedFieldNames []string // stored_fields
docvalueFields DocvalueFields // docvalue_fields
scriptFields []*ScriptField // script_fields
fetchSourceContext *FetchSourceContext // _source
aggregations map[string]Aggregation // aggregations / aggs
highlight *Highlight // highlight
globalSuggestText string
suggesters []Suggester // suggest
rescores []*Rescore // rescore
defaultRescoreWindowSize *int
indexBoosts IndexBoosts // indices_boost
stats []string // stats
innerHits map[string]*InnerHit
collapse *CollapseBuilder // collapse
profile bool // profile
// TODO extBuilders []SearchExtBuilder // ext
pointInTime *PointInTime // pit
runtimeMappings RuntimeMappings
}
// NewSearchSource initializes a new SearchSource.
func NewSearchSource() *SearchSource {
return &SearchSource{
from: -1,
size: -1,
aggregations: make(map[string]Aggregation),
innerHits: make(map[string]*InnerHit),
}
}
// Query sets the query to use with this search source.
func (s *SearchSource) Query(query Query) *SearchSource {
s.query = query
return s
}
// Profile specifies that this search source should activate the
// Profile API for queries made on it.
func (s *SearchSource) Profile(profile bool) *SearchSource {
s.profile = profile
return s
}
// PostFilter will be executed after the query has been executed and
// only affects the search hits, not the aggregations.
// This filter is always executed as the last filtering mechanism.
func (s *SearchSource) PostFilter(postFilter Query) *SearchSource {
s.postQuery = postFilter
return s
}
// Slice allows partitioning the documents in multiple slices.
// It is e.g. used to slice a scroll operation, supported in
// Elasticsearch 5.0 or later.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-request-scroll.html#sliced-scroll
// for details.
func (s *SearchSource) Slice(sliceQuery Query) *SearchSource {
s.sliceQuery = sliceQuery
return s
}
// From index to start the search from. Defaults to 0.
func (s *SearchSource) From(from int) *SearchSource {
s.from = from
return s
}
// Size is the number of search hits to return. Defaults to 10.
func (s *SearchSource) Size(size int) *SearchSource {
s.size = size
return s
}
// MinScore sets the minimum score below which docs will be filtered out.
func (s *SearchSource) MinScore(minScore float64) *SearchSource {
s.minScore = &minScore
return s
}
// Explain indicates whether each search hit should be returned with
// an explanation of the hit (ranking).
func (s *SearchSource) Explain(explain bool) *SearchSource {
s.explain = &explain
return s
}
// Version indicates whether each search hit should be returned with
// a version associated to it.
func (s *SearchSource) Version(version bool) *SearchSource {
s.version = &version
return s
}
// SeqNoAndPrimaryTerm indicates whether SearchHits should be returned with the
// sequence number and primary term of the last modification of the document.
func (s *SearchSource) SeqNoAndPrimaryTerm(enabled bool) *SearchSource {
s.seqNoAndPrimaryTerm = &enabled
return s
}
// Timeout controls how long a search is allowed to take, e.g. "1s" or "500ms".
func (s *SearchSource) Timeout(timeout string) *SearchSource {
s.timeout = timeout
return s
}
// TimeoutInMillis controls how many milliseconds a search is allowed
// to take before it is canceled.
func (s *SearchSource) TimeoutInMillis(timeoutInMillis int) *SearchSource {
s.timeout = fmt.Sprintf("%dms", timeoutInMillis)
return s
}
// TerminateAfter specifies the maximum number of documents to collect for
// each shard, upon reaching which the query execution will terminate early.
func (s *SearchSource) TerminateAfter(terminateAfter int) *SearchSource {
s.terminateAfter = &terminateAfter
return s
}
// Sort adds a sort order.
func (s *SearchSource) Sort(field string, ascending bool) *SearchSource {
s.sorters = append(s.sorters, SortInfo{Field: field, Ascending: ascending})
return s
}
// SortWithInfo adds a sort order.
func (s *SearchSource) SortWithInfo(info SortInfo) *SearchSource {
s.sorters = append(s.sorters, info)
return s
}
// SortBy adds a sort order.
func (s *SearchSource) SortBy(sorter ...Sorter) *SearchSource {
s.sorters = append(s.sorters, sorter...)
return s
}
func (s *SearchSource) hasSort() bool {
return len(s.sorters) > 0
}
// TrackScores is applied when sorting and controls if scores will be
// tracked as well. Defaults to false.
func (s *SearchSource) TrackScores(trackScores bool) *SearchSource {
s.trackScores = &trackScores
return s
}
// TrackTotalHits controls how the total number of hits should be tracked.
// Defaults to 10000 which will count the total hit accurately up to 10,000 hits.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-request-track-total-hits.html
// for details.
func (s *SearchSource) TrackTotalHits(trackTotalHits interface{}) *SearchSource {
s.trackTotalHits = trackTotalHits
return s
}
// SearchAfter allows a different form of pagination by using a live cursor,
// using the results of the previous page to help the retrieval of the next.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-request-search-after.html
func (s *SearchSource) SearchAfter(sortValues ...interface{}) *SearchSource {
s.searchAfterSortValues = append(s.searchAfterSortValues, sortValues...)
return s
}
// Aggregation adds an aggreation to perform as part of the search.
func (s *SearchSource) Aggregation(name string, aggregation Aggregation) *SearchSource {
s.aggregations[name] = aggregation
return s
}
// DefaultRescoreWindowSize sets the rescore window size for rescores
// that don't specify their window.
func (s *SearchSource) DefaultRescoreWindowSize(defaultRescoreWindowSize int) *SearchSource {
s.defaultRescoreWindowSize = &defaultRescoreWindowSize
return s
}
// Highlight adds highlighting to the search.
func (s *SearchSource) Highlight(highlight *Highlight) *SearchSource {
s.highlight = highlight
return s
}
// Highlighter returns the highlighter.
func (s *SearchSource) Highlighter() *Highlight {
if s.highlight == nil {
s.highlight = NewHighlight()
}
return s.highlight
}
// GlobalSuggestText defines the global text to use with all suggesters.
// This avoids repetition.
func (s *SearchSource) GlobalSuggestText(text string) *SearchSource {
s.globalSuggestText = text
return s
}
// Suggester adds a suggester to the search.
func (s *SearchSource) Suggester(suggester Suggester) *SearchSource {
s.suggesters = append(s.suggesters, suggester)
return s
}
// Rescorer adds a rescorer to the search.
func (s *SearchSource) Rescorer(rescore *Rescore) *SearchSource {
s.rescores = append(s.rescores, rescore)
return s
}
// ClearRescorers removes all rescorers from the search.
func (s *SearchSource) ClearRescorers() *SearchSource {
s.rescores = make([]*Rescore, 0)
return s
}
// FetchSource indicates whether the response should contain the stored
// _source for every hit.
func (s *SearchSource) FetchSource(fetchSource bool) *SearchSource {
if s.fetchSourceContext == nil {
s.fetchSourceContext = NewFetchSourceContext(fetchSource)
} else {
s.fetchSourceContext.SetFetchSource(fetchSource)
}
return s
}
// FetchSourceContext indicates how the _source should be fetched.
func (s *SearchSource) FetchSourceContext(fetchSourceContext *FetchSourceContext) *SearchSource {
s.fetchSourceContext = fetchSourceContext
return s
}
// FetchSourceIncludeExclude specifies that _source should be returned
// with each hit, where "include" and "exclude" serve as a simple wildcard
// matcher that gets applied to its fields
// (e.g. include := []string{"obj1.*","obj2.*"}, exclude := []string{"description.*"}).
func (s *SearchSource) FetchSourceIncludeExclude(include, exclude []string) *SearchSource {
s.fetchSourceContext = NewFetchSourceContext(true).
Include(include...).
Exclude(exclude...)
return s
}
// NoStoredFields indicates that no fields should be loaded, resulting in only
// id and type to be returned per field.
func (s *SearchSource) NoStoredFields() *SearchSource {
s.storedFieldNames = []string{}
return s
}
// StoredField adds a single field to load and return (note, must be stored) as
// part of the search request. If none are specified, the source of the
// document will be returned.
func (s *SearchSource) StoredField(storedFieldName string) *SearchSource {
s.storedFieldNames = append(s.storedFieldNames, storedFieldName)
return s
}
// StoredFields sets the fields to load and return as part of the search request.
// If none are specified, the source of the document will be returned.
func (s *SearchSource) StoredFields(storedFieldNames ...string) *SearchSource {
s.storedFieldNames = append(s.storedFieldNames, storedFieldNames...)
return s
}
// DocvalueField adds a single field to load from the field data cache
// and return as part of the search request.
func (s *SearchSource) DocvalueField(fieldDataField string) *SearchSource {
s.docvalueFields = append(s.docvalueFields, DocvalueField{Field: fieldDataField})
return s
}
// DocvalueField adds a single docvalue field to load from the field data cache
// and return as part of the search request.
func (s *SearchSource) DocvalueFieldWithFormat(fieldDataFieldWithFormat DocvalueField) *SearchSource {
s.docvalueFields = append(s.docvalueFields, fieldDataFieldWithFormat)
return s
}
// DocvalueFields adds one or more fields to load from the field data cache
// and return as part of the search request.
func (s *SearchSource) DocvalueFields(docvalueFields ...string) *SearchSource {
for _, f := range docvalueFields {
s.docvalueFields = append(s.docvalueFields, DocvalueField{Field: f})
}
return s
}
// DocvalueFields adds one or more docvalue fields to load from the field data cache
// and return as part of the search request.
func (s *SearchSource) DocvalueFieldsWithFormat(docvalueFields ...DocvalueField) *SearchSource {
s.docvalueFields = append(s.docvalueFields, docvalueFields...)
return s
}
// ScriptField adds a single script field with the provided script.
func (s *SearchSource) ScriptField(scriptField *ScriptField) *SearchSource {
s.scriptFields = append(s.scriptFields, scriptField)
return s
}
// ScriptFields adds one or more script fields with the provided scripts.
func (s *SearchSource) ScriptFields(scriptFields ...*ScriptField) *SearchSource {
s.scriptFields = append(s.scriptFields, scriptFields...)
return s
}
// IndexBoost sets the boost that a specific index will receive when the
// query is executed against it.
func (s *SearchSource) IndexBoost(index string, boost float64) *SearchSource {
s.indexBoosts = append(s.indexBoosts, IndexBoost{Index: index, Boost: boost})
return s
}
// IndexBoosts sets the boosts for specific indices.
func (s *SearchSource) IndexBoosts(boosts ...IndexBoost) *SearchSource {
s.indexBoosts = append(s.indexBoosts, boosts...)
return s
}
// Stats group this request will be aggregated under.
func (s *SearchSource) Stats(statsGroup ...string) *SearchSource {
s.stats = append(s.stats, statsGroup...)
return s
}
// InnerHit adds an inner hit to return with the result.
func (s *SearchSource) InnerHit(name string, innerHit *InnerHit) *SearchSource {
s.innerHits[name] = innerHit
return s
}
// Collapse adds field collapsing.
func (s *SearchSource) Collapse(collapse *CollapseBuilder) *SearchSource {
s.collapse = collapse
return s
}
// PointInTime specifies an optional PointInTime to be used in the context
// of this search.
func (s *SearchSource) PointInTime(pointInTime *PointInTime) *SearchSource {
s.pointInTime = pointInTime
return s
}
// RuntimeMappings specifies optional runtime mappings.
func (s *SearchSource) RuntimeMappings(runtimeMappings RuntimeMappings) *SearchSource {
s.runtimeMappings = runtimeMappings
return s
}
// Source returns the serializable JSON for the source builder.
func (s *SearchSource) Source() (interface{}, error) {
source := make(map[string]interface{})
if s.from != -1 {
source["from"] = s.from
}
if s.size != -1 {
source["size"] = s.size
}
if s.timeout != "" {
source["timeout"] = s.timeout
}
if s.terminateAfter != nil {
source["terminate_after"] = *s.terminateAfter
}
if s.query != nil {
src, err := s.query.Source()
if err != nil {
return nil, err
}
source["query"] = src
}
if s.postQuery != nil {
src, err := s.postQuery.Source()
if err != nil {
return nil, err
}
source["post_filter"] = src
}
if s.minScore != nil {
source["min_score"] = *s.minScore
}
if s.version != nil {
source["version"] = *s.version
}
if s.explain != nil {
source["explain"] = *s.explain
}
if s.profile {
source["profile"] = s.profile
}
if s.fetchSourceContext != nil {
src, err := s.fetchSourceContext.Source()
if err != nil {
return nil, err
}
source["_source"] = src
}
if s.storedFieldNames != nil {
switch len(s.storedFieldNames) {
case 1:
source["stored_fields"] = s.storedFieldNames[0]
default:
source["stored_fields"] = s.storedFieldNames
}
}
if len(s.docvalueFields) > 0 {
src, err := s.docvalueFields.Source()
if err != nil {
return nil, err
}
source["docvalue_fields"] = src
}
if len(s.scriptFields) > 0 {
sfmap := make(map[string]interface{})
for _, scriptField := range s.scriptFields {
src, err := scriptField.Source()
if err != nil {
return nil, err
}
sfmap[scriptField.FieldName] = src
}
source["script_fields"] = sfmap
}
if len(s.sorters) > 0 {
var sortarr []interface{}
for _, sorter := range s.sorters {
src, err := sorter.Source()
if err != nil {
return nil, err
}
sortarr = append(sortarr, src)
}
source["sort"] = sortarr
}
if v := s.trackScores; v != nil {
source["track_scores"] = *v
}
if v := s.trackTotalHits; v != nil {
source["track_total_hits"] = v
}
if len(s.searchAfterSortValues) > 0 {
source["search_after"] = s.searchAfterSortValues
}
if s.sliceQuery != nil {
src, err := s.sliceQuery.Source()
if err != nil {
return nil, err
}
source["slice"] = src
}
if len(s.indexBoosts) > 0 {
src, err := s.indexBoosts.Source()
if err != nil {
return nil, err
}
source["indices_boost"] = src
}
if len(s.aggregations) > 0 {
aggsMap := make(map[string]interface{})
for name, aggregate := range s.aggregations {
src, err := aggregate.Source()
if err != nil {
return nil, err
}
aggsMap[name] = src
}
source["aggregations"] = aggsMap
}
if s.highlight != nil {
src, err := s.highlight.Source()
if err != nil {
return nil, err
}
source["highlight"] = src
}
if len(s.suggesters) > 0 {
suggesters := make(map[string]interface{})
for _, s := range s.suggesters {
src, err := s.Source(false)
if err != nil {
return nil, err
}
suggesters[s.Name()] = src
}
if s.globalSuggestText != "" {
suggesters["text"] = s.globalSuggestText
}
source["suggest"] = suggesters
}
if len(s.rescores) > 0 {
// Strip empty rescores from request
var rescores []*Rescore
for _, r := range s.rescores {
if !r.IsEmpty() {
rescores = append(rescores, r)
}
}
if len(rescores) == 1 {
rescores[0].defaultRescoreWindowSize = s.defaultRescoreWindowSize
src, err := rescores[0].Source()
if err != nil {
return nil, err
}
source["rescore"] = src
} else {
var slice []interface{}
for _, r := range rescores {
r.defaultRescoreWindowSize = s.defaultRescoreWindowSize
src, err := r.Source()
if err != nil {
return nil, err
}
slice = append(slice, src)
}
source["rescore"] = slice
}
}
if len(s.stats) > 0 {
source["stats"] = s.stats
}
// TODO ext builders
if s.collapse != nil {
src, err := s.collapse.Source()
if err != nil {
return nil, err
}
source["collapse"] = src
}
if v := s.seqNoAndPrimaryTerm; v != nil {
source["seq_no_primary_term"] = *v
}
if len(s.innerHits) > 0 {
// Top-level inner hits
// See http://www.elastic.co/guide/en/elasticsearch/reference/1.5/search-request-inner-hits.html#top-level-inner-hits
// "inner_hits": {
// "": {
// "": {
// "": {
// ,
// [,"inner_hits" : { []+ } ]?
// }
// }
// },
// [,"" : { ... } ]*
// }
m := make(map[string]interface{})
for name, hit := range s.innerHits {
if hit.path != "" {
src, err := hit.Source()
if err != nil {
return nil, err
}
path := make(map[string]interface{})
path[hit.path] = src
m[name] = map[string]interface{}{
"path": path,
}
} else if hit.typ != "" {
src, err := hit.Source()
if err != nil {
return nil, err
}
typ := make(map[string]interface{})
typ[hit.typ] = src
m[name] = map[string]interface{}{
"type": typ,
}
} else {
// TODO the Java client throws here, because either path or typ must be specified
_ = m
}
}
source["inner_hits"] = m
}
// Point in Time
if s.pointInTime != nil {
src, err := s.pointInTime.Source()
if err != nil {
return nil, err
}
source["pit"] = src
}
if s.runtimeMappings != nil {
src, err := s.runtimeMappings.Source()
if err != nil {
return nil, err
}
source["runtime_mappings"] = src
}
return source, nil
}
// MarshalJSON enables serializing the type as JSON.
func (q *SearchSource) MarshalJSON() ([]byte, error) {
if q == nil {
return nilByte, nil
}
src, err := q.Source()
if err != nil {
return nil, err
}
return json.Marshal(src)
}
// -- IndexBoosts --
// IndexBoost specifies an index by some boost factor.
type IndexBoost struct {
Index string
Boost float64
}
// Source generates a JSON-serializable output for IndexBoost.
func (b IndexBoost) Source() (interface{}, error) {
return map[string]interface{}{
b.Index: b.Boost,
}, nil
}
// IndexBoosts is a slice of IndexBoost entities.
type IndexBoosts []IndexBoost
// Source generates a JSON-serializable output for IndexBoosts.
func (b IndexBoosts) Source() (interface{}, error) {
var boosts []interface{}
for _, ib := range b {
src, err := ib.Source()
if err != nil {
return nil, err
}
boosts = append(boosts, src)
}
return boosts, nil
}
================================================
FILE: search_source_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestSearchSourceMatchAllQuery(t *testing.T) {
matchAllQ := NewMatchAllQuery()
builder := NewSearchSource().Query(matchAllQ)
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"query":{"match_all":{}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSearchSourceMarshalJSON(t *testing.T) {
matchAllQ := NewMatchAllQuery()
builder := NewSearchSource().Query(matchAllQ)
data, err := builder.MarshalJSON()
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"query":{"match_all":{}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSearchSourceNoStoredFields(t *testing.T) {
matchAllQ := NewMatchAllQuery()
builder := NewSearchSource().Query(matchAllQ).NoStoredFields()
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"query":{"match_all":{}},"stored_fields":[]}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSearchSourceStoredFields(t *testing.T) {
matchAllQ := NewMatchAllQuery()
builder := NewSearchSource().Query(matchAllQ).StoredFields("message", "tags")
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"query":{"match_all":{}},"stored_fields":["message","tags"]}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSearchSourceFetchSourceDisabled(t *testing.T) {
matchAllQ := NewMatchAllQuery()
builder := NewSearchSource().Query(matchAllQ).FetchSource(false)
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"_source":false,"query":{"match_all":{}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSearchSourceFetchSourceByWildcards(t *testing.T) {
matchAllQ := NewMatchAllQuery()
fsc := NewFetchSourceContext(true).Include("obj1.*", "obj2.*").Exclude("*.description")
builder := NewSearchSource().Query(matchAllQ).FetchSourceContext(fsc)
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"_source":{"excludes":["*.description"],"includes":["obj1.*","obj2.*"]},"query":{"match_all":{}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSearchSourceDocvalueFields(t *testing.T) {
matchAllQ := NewMatchAllQuery()
builder := NewSearchSource().Query(matchAllQ).
DocvalueFields("test1", "test2").
DocvalueFieldsWithFormat(
DocvalueField{Field: "test3", Format: "date"},
DocvalueField{Field: "test4", Format: "epoch_millis"},
)
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"docvalue_fields":["test1","test2",{"field":"test3","format":"date"},{"field":"test4","format":"epoch_millis"}],"query":{"match_all":{}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSearchSourceScriptFields(t *testing.T) {
matchAllQ := NewMatchAllQuery()
sf1 := NewScriptField("test1", NewScript("doc['my_field_name'].value * 2"))
sf2 := NewScriptField("test2", NewScript("doc['my_field_name'].value * factor").Param("factor", 3.1415927))
builder := NewSearchSource().Query(matchAllQ).ScriptFields(sf1, sf2)
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"query":{"match_all":{}},"script_fields":{"test1":{"script":{"source":"doc['my_field_name'].value * 2"}},"test2":{"script":{"params":{"factor":3.1415927},"source":"doc['my_field_name'].value * factor"}}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSearchSourcePostFilter(t *testing.T) {
matchAllQ := NewMatchAllQuery()
pf := NewTermQuery("tag", "important")
builder := NewSearchSource().Query(matchAllQ).PostFilter(pf)
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"post_filter":{"term":{"tag":"important"}},"query":{"match_all":{}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSearchSourceHighlight(t *testing.T) {
matchAllQ := NewMatchAllQuery()
hl := NewHighlight().Field("content")
builder := NewSearchSource().Query(matchAllQ).Highlight(hl)
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"highlight":{"fields":{"content":{}}},"query":{"match_all":{}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSearchSourceRescoring(t *testing.T) {
matchAllQ := NewMatchAllQuery()
rescorerQuery := NewMatchPhraseQuery("field1", "the quick brown fox").Slop(2)
rescorer := NewQueryRescorer(rescorerQuery)
rescorer = rescorer.QueryWeight(0.7)
rescorer = rescorer.RescoreQueryWeight(1.2)
rescore := NewRescore().WindowSize(50).Rescorer(rescorer)
builder := NewSearchSource().Query(matchAllQ).Rescorer(rescore)
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"query":{"match_all":{}},"rescore":{"query":{"query_weight":0.7,"rescore_query":{"match_phrase":{"field1":{"query":"the quick brown fox","slop":2}}},"rescore_query_weight":1.2},"window_size":50}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSearchSourceIndexBoost(t *testing.T) {
matchAllQ := NewMatchAllQuery()
builder := NewSearchSource().Query(matchAllQ).IndexBoost("index1", 1.4).IndexBoost("index2", 1.3)
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"indices_boost":[{"index1":1.4},{"index2":1.3}],"query":{"match_all":{}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSearchSourceMixDifferentSorters(t *testing.T) {
matchAllQ := NewMatchAllQuery()
builder := NewSearchSource().Query(matchAllQ).
Sort("a", false).
SortWithInfo(SortInfo{Field: "b", Ascending: true}).
SortBy(NewScriptSort(NewScript("doc['field_name'].value * factor").Param("factor", 1.1), "number"))
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"query":{"match_all":{}},"sort":[{"a":{"order":"desc"}},{"b":{"order":"asc"}},{"_script":{"order":"asc","script":{"params":{"factor":1.1},"source":"doc['field_name'].value * factor"},"type":"number"}}]}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSearchSourceInnerHits(t *testing.T) {
matchAllQ := NewMatchAllQuery()
builder := NewSearchSource().Query(matchAllQ).
InnerHit("comments", NewInnerHit().Type("comment").Query(NewMatchQuery("user", "olivere"))).
InnerHit("views", NewInnerHit().Path("view"))
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"inner_hits":{"comments":{"type":{"comment":{"query":{"match":{"user":{"query":"olivere"}}}}}},"views":{"path":{"view":{}}}},"query":{"match_all":{}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSearchSourceSearchAfter(t *testing.T) {
matchAllQ := NewMatchAllQuery()
builder := NewSearchSource().Query(matchAllQ).SearchAfter(1463538857, "tweet#654323")
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"query":{"match_all":{}},"search_after":[1463538857,"tweet#654323"]}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSearchSourceProfiledQuery(t *testing.T) {
matchAllQ := NewMatchAllQuery()
builder := NewSearchSource().Query(matchAllQ).Profile(true)
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"profile":true,"query":{"match_all":{}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSearchSourceSeqNoAndPrimaryTerm(t *testing.T) {
matchAllQ := NewMatchAllQuery()
builder := NewSearchSource().Query(matchAllQ).SeqNoAndPrimaryTerm(true)
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"query":{"match_all":{}},"seq_no_primary_term":true}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSearchSourcePointInTime(t *testing.T) {
matchAllQ := NewMatchAllQuery()
builder := NewSearchSource().Query(matchAllQ).PointInTime(
NewPointInTimeWithKeepAlive("pit_id", "2m"),
)
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"pit":{"id":"pit_id","keep_alive":"2m"},"query":{"match_all":{}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSearchSourceRuntimeMappings(t *testing.T) {
matchAllQ := NewMatchAllQuery()
builder := NewSearchSource().Query(matchAllQ).RuntimeMappings(RuntimeMappings{
"day_of_week": map[string]interface{}{
"type": "keyword",
},
})
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"query":{"match_all":{}},"runtime_mappings":{"day_of_week":{"type":"keyword"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_suggester_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestTermSuggester(t *testing.T) {
client := setupTestClientAndCreateIndex(t) // AndLog(t)
tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
tweet2 := tweet{User: "olivere", Message: "Another unrelated topic."}
tweet3 := tweet{User: "sandrae", Message: "Cycling is fun."}
// Add all documents
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("3").BodyJson(&tweet3).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Match all should return all documents
tsName := "my-suggestions"
ts := NewTermSuggester(tsName)
ts = ts.Text("Goolang")
ts = ts.Field("message")
searchResult, err := client.Search().
Index(testIndexName).
Query(NewMatchAllQuery()).
Suggester(ts).
Pretty(true).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.Suggest == nil {
t.Errorf("expected SearchResult.Suggest != nil; got nil")
}
mySuggestions, found := searchResult.Suggest[tsName]
if !found {
t.Errorf("expected to find SearchResult.Suggest[%s]; got false", tsName)
}
if mySuggestions == nil {
t.Errorf("expected SearchResult.Suggest[%s] != nil; got nil", tsName)
}
if len(mySuggestions) != 1 {
t.Errorf("expected 1 suggestion; got %d", len(mySuggestions))
}
mySuggestion := mySuggestions[0]
if mySuggestion.Text != "goolang" {
t.Errorf("expected Text = 'goolang'; got %s", mySuggestion.Text)
}
if mySuggestion.Offset != 0 {
t.Errorf("expected Offset = %d; got %d", 0, mySuggestion.Offset)
}
if mySuggestion.Length != 7 {
t.Errorf("expected Length = %d; got %d", 7, mySuggestion.Length)
}
if len(mySuggestion.Options) != 1 {
t.Errorf("expected 1 option; got %d", len(mySuggestion.Options))
}
myOption := mySuggestion.Options[0]
if myOption.Text != "golang" {
t.Errorf("expected Text = 'golang'; got %s", myOption.Text)
}
if score := mySuggestion.Options[0].Score; score <= 0.0 {
t.Errorf("expected options[0].Score > 0.0; got %v", score)
}
}
func TestPhraseSuggester(t *testing.T) {
client := setupTestClientAndCreateIndex(t) // AndLog(t)
tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
tweet2 := tweet{User: "olivere", Message: "Another unrelated topic."}
tweet3 := tweet{User: "sandrae", Message: "Cycling is fun."}
// Add all documents
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("3").BodyJson(&tweet3).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Match all should return all documents
phraseSuggesterName := "my-suggestions"
ps := NewPhraseSuggester(phraseSuggesterName)
ps = ps.Text("Goolang")
ps = ps.Field("message")
searchResult, err := client.Search().
Index(testIndexName).
Query(NewMatchAllQuery()).
Suggester(ps).
Pretty(true).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.Suggest == nil {
t.Errorf("expected SearchResult.Suggest != nil; got nil")
}
mySuggestions, found := searchResult.Suggest[phraseSuggesterName]
if !found {
t.Errorf("expected to find SearchResult.Suggest[%s]; got false", phraseSuggesterName)
}
if mySuggestions == nil {
t.Errorf("expected SearchResult.Suggest[%s] != nil; got nil", phraseSuggesterName)
}
if len(mySuggestions) != 1 {
t.Errorf("expected 1 suggestion; got %d", len(mySuggestions))
}
mySuggestion := mySuggestions[0]
if mySuggestion.Text != "Goolang" {
t.Errorf("expected Text = 'Goolang'; got %s", mySuggestion.Text)
}
if mySuggestion.Offset != 0 {
t.Errorf("expected Offset = %d; got %d", 0, mySuggestion.Offset)
}
if mySuggestion.Length != 7 {
t.Errorf("expected Length = %d; got %d", 7, mySuggestion.Length)
}
if want, have := 1, len(mySuggestion.Options); want != have {
t.Errorf("expected len(options) = %d; got %d", want, have)
}
if want, have := "golang", mySuggestion.Options[0].Text; want != have {
t.Errorf("expected options[0].Text = %q; got %q", want, have)
}
if score := mySuggestion.Options[0].Score; score <= 0.0 {
t.Errorf("expected options[0].Score > 0.0; got %v", score)
}
}
func TestCompletionSuggester(t *testing.T) {
client := setupTestClientAndCreateIndex(t) // AndLog(t)
tweet1 := tweet{
User: "olivere",
Message: "Welcome to Golang and Elasticsearch.",
Suggest: NewSuggestField("Golang", "Elasticsearch"),
}
tweet2 := tweet{
User: "olivere",
Message: "Another unrelated topic.",
Suggest: NewSuggestField("Another unrelated topic."),
}
tweet3 := tweet{
User: "sandrae",
Message: "Cycling is fun.",
Suggest: NewSuggestField("Cycling is fun."),
}
// Add all documents
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("3").BodyJson(&tweet3).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Match all should return all documents
suggesterName := "my-suggestions"
cs := NewCompletionSuggester(suggesterName)
cs = cs.Text("Golang")
cs = cs.Field("suggest_field")
searchResult, err := client.Search().
Index(testIndexName).
Query(NewMatchAllQuery()).
Suggester(cs).
Pretty(true).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.Suggest == nil {
t.Errorf("expected SearchResult.Suggest != nil; got nil")
}
mySuggestions, found := searchResult.Suggest[suggesterName]
if !found {
t.Errorf("expected to find SearchResult.Suggest[%s]; got false", suggesterName)
}
if mySuggestions == nil {
t.Errorf("expected SearchResult.Suggest[%s] != nil; got nil", suggesterName)
}
if len(mySuggestions) != 1 {
t.Errorf("expected 1 suggestion; got %d", len(mySuggestions))
}
mySuggestion := mySuggestions[0]
if mySuggestion.Text != "Golang" {
t.Errorf("expected Text = 'Golang'; got %s", mySuggestion.Text)
}
if mySuggestion.Offset != 0 {
t.Errorf("expected Offset = %d; got %d", 0, mySuggestion.Offset)
}
if mySuggestion.Length != 6 {
t.Errorf("expected Length = %d; got %d", 7, mySuggestion.Length)
}
if len(mySuggestion.Options) != 1 {
t.Errorf("expected 1 option; got %d", len(mySuggestion.Options))
}
myOption := mySuggestion.Options[0]
if myOption.Text != "Golang" {
t.Errorf("expected Text = 'Golang'; got %s", myOption.Text)
}
if score := mySuggestion.Options[0].ScoreUnderscore; score <= 0.0 {
t.Errorf("expected options[0].ScoreUnderscore > 0.0; got %v", score)
}
}
func TestContextSuggester(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
// TODO make a nice way of creating tweets, as currently the context fields are unsupported as part of the suggestion fields
tweet1 := `
{
"user":"olivere",
"message":"Welcome to Golang and Elasticsearch.",
"retweets":0,
"created":"0001-01-01T00:00:00Z",
"suggest_field":{
"input":[
"Golang",
"Elasticsearch"
],
"contexts":{
"user_name": ["olivere"]
}
}
}
`
tweet2 := `
{
"user":"sandrae",
"message":"I like golfing",
"retweets":0,
"created":"0001-01-01T00:00:00Z",
"suggest_field":{
"input":[
"Golfing"
],
"contexts":{
"user_name": ["sandrae"]
}
}
}
`
// Add all documents
_, err := client.Index().Index(testIndexName2).Id("1").BodyString(tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName2).Id("2").BodyString(tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testIndexName2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
suggesterName := "my-suggestions"
cs := NewContextSuggester(suggesterName)
cs = cs.Prefix("Gol")
cs = cs.Field("suggest_field")
cs = cs.ContextQueries(
NewSuggesterCategoryQuery("user_name", "olivere"),
)
searchResult, err := client.Search().
Index(testIndexName2).
Suggester(cs).
Pretty(true).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.Suggest == nil {
t.Errorf("expected SearchResult.Suggest != nil; got nil")
}
mySuggestions, found := searchResult.Suggest[suggesterName]
if !found {
t.Errorf("expected to find SearchResult.Suggest[%s]; got false", suggesterName)
}
if mySuggestions == nil {
t.Errorf("expected SearchResult.Suggest[%s] != nil; got nil", suggesterName)
}
// sandra's tweet is not returned because of the user_name context
if len(mySuggestions) != 1 {
t.Errorf("expected 1 suggestion; got %d", len(mySuggestions))
}
mySuggestion := mySuggestions[0]
if mySuggestion.Text != "Gol" {
t.Errorf("expected Text = 'Gol'; got %s", mySuggestion.Text)
}
if mySuggestion.Offset != 0 {
t.Errorf("expected Offset = %d; got %d", 0, mySuggestion.Offset)
}
if mySuggestion.Length != 3 {
t.Errorf("expected Length = %d; got %d", 3, mySuggestion.Length)
}
if len(mySuggestion.Options) != 1 {
t.Errorf("expected 1 option; got %d", len(mySuggestion.Options))
}
myOption := mySuggestion.Options[0]
if myOption.Text != "Golang" {
t.Errorf("expected Text = 'Golang'; got %s", myOption.Text)
}
if myOption.Id != "1" {
t.Errorf("expected Id = '1'; got %s", myOption.Id)
}
if score := mySuggestion.Options[0].ScoreUnderscore; score <= 0.0 {
t.Errorf("expected options[0].ScoreUnderscore > 0.0; got %v", score)
}
}
================================================
FILE: search_terms_lookup.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// TermsLookup encapsulates the parameters needed to fetch terms.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-terms-query.html#query-dsl-terms-lookup.
type TermsLookup struct {
index string
typ string
id string
path string
routing string
}
// NewTermsLookup creates and initializes a new TermsLookup.
func NewTermsLookup() *TermsLookup {
t := &TermsLookup{}
return t
}
// Index name.
func (t *TermsLookup) Index(index string) *TermsLookup {
t.index = index
return t
}
// Type name.
//
// Deprecated: Types are in the process of being removed.
func (t *TermsLookup) Type(typ string) *TermsLookup {
t.typ = typ
return t
}
// Id to look up.
func (t *TermsLookup) Id(id string) *TermsLookup {
t.id = id
return t
}
// Path to use for lookup.
func (t *TermsLookup) Path(path string) *TermsLookup {
t.path = path
return t
}
// Routing value.
func (t *TermsLookup) Routing(routing string) *TermsLookup {
t.routing = routing
return t
}
// Source creates the JSON source of the builder.
func (t *TermsLookup) Source() (interface{}, error) {
src := make(map[string]interface{})
if t.index != "" {
src["index"] = t.index
}
if t.typ != "" {
src["type"] = t.typ
}
if t.id != "" {
src["id"] = t.id
}
if t.path != "" {
src["path"] = t.path
}
if t.routing != "" {
src["routing"] = t.routing
}
return src, nil
}
================================================
FILE: search_terms_lookup_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestTermsLookup(t *testing.T) {
tl := NewTermsLookup().Index("users").Type("user").Id("2").Path("followers")
src, err := tl.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"id":"2","index":"users","path":"followers","type":"user"}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: search_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"reflect"
"testing"
"time"
)
func TestSearchMatchAll(t *testing.T) {
//client := setupTestClientAndCreateIndexAndAddDocs(t, SetTraceLog(log.New(os.Stdout, "", log.LstdFlags)))
client := setupTestClientAndCreateIndexAndAddDocs(t)
// Match all should return all documents
searchResult, err := client.Search().
Index(testIndexName).
Query(NewMatchAllQuery()).
Size(100).
Pretty(true).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.Hits == nil {
t.Errorf("expected SearchResult.Hits != nil; got nil")
}
if got, want := searchResult.TotalHits(), int64(3); got != want {
t.Errorf("expected SearchResult.TotalHits() = %d; got %d", want, got)
}
if got, want := len(searchResult.Hits.Hits), 3; got != want {
t.Errorf("expected len(SearchResult.Hits.Hits) = %d; got %d", want, got)
}
for _, hit := range searchResult.Hits.Hits {
if hit.Index != testIndexName {
t.Errorf("expected SearchResult.Hits.Hit.Index = %q; got %q", testIndexName, hit.Index)
}
item := make(map[string]interface{})
err := json.Unmarshal(hit.Source, &item)
if err != nil {
t.Fatal(err)
}
}
}
func TestSearchWithCustomHTTPHeaders(t *testing.T) {
//client := setupTestClientAndCreateIndexAndAddDocs(t, SetTraceLog(log.New(os.Stdout, "", log.LstdFlags)))
client := setupTestClientAndCreateIndexAndAddDocs(t)
// Match all should return all documents
res, err := client.Search().
Index(testIndexName).
Query(NewMatchAllQuery()).
Size(100).
Pretty(true).
Headers(http.Header{
"X-ID": []string{"A", "B"},
"Custom-ID": []string{"olivere"},
}).
Header("X-ID", "12345").
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if got, want := res.TotalHits(), int64(3); got != want {
t.Errorf("expected SearchResult.TotalHits() = %d; got %d", want, got)
}
if got, want := res.Header.Get("Content-Type"), "application/json; charset=UTF-8"; got != want {
t.Errorf("expected SearchResult.Header(%q) = %q; got %q", "Content-Type", want, got)
}
}
func TestSearchMatchAllWithRequestCacheDisabled(t *testing.T) {
//client := setupTestClientAndCreateIndexAndAddDocs(t, SetTraceLog(log.New(os.Stdout, "", log.LstdFlags)))
client := setupTestClientAndCreateIndexAndAddDocs(t)
// Match all should return all documents, with request cache disabled
searchResult, err := client.Search().
Index(testIndexName).
Query(NewMatchAllQuery()).
Size(100).
Pretty(true).
RequestCache(false).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.Hits == nil {
t.Errorf("expected SearchResult.Hits != nil; got nil")
}
if got, want := searchResult.TotalHits(), int64(3); got != want {
t.Errorf("expected SearchResult.TotalHits() = %d; got %d", want, got)
}
if got, want := len(searchResult.Hits.Hits), 3; got != want {
t.Errorf("expected len(SearchResult.Hits.Hits) = %d; got %d", want, got)
}
}
func TestSearchTotalHits(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t) //, SetTraceLog(log.New(os.Stdout, "", log.LstdFlags)))
count, err := client.Count(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if count == 0 {
t.Fatalf("expected more than %d documents", count)
}
// RestTotalHitsAsInt(false) (default)
{
res, err := client.Search().Index(testIndexName).Query(NewMatchAllQuery()).RestTotalHitsAsInt(false).Pretty(true).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected SearchResult != nil; got nil")
}
if want, have := count, res.TotalHits(); want != have {
t.Errorf("expected SearchResult.TotalHits() = %d; got %d", want, have)
}
if res.Hits == nil || res.Hits.TotalHits == nil {
t.Fatal("expected SearchResult.Hits._ != nil; got nil")
}
if want, have := count, res.Hits.TotalHits.Value; want != have {
t.Errorf("expected SearchResult.TotalHits.Value = %d; got %d", want, have)
}
if want, have := "eq", res.Hits.TotalHits.Relation; want != have {
t.Errorf("expected SearchResult.TotalHits.Relation = %q; got %q", want, have)
}
}
// RestTotalHitsAsInt(true)
{
res, err := client.Search().Index(testIndexName).Query(NewMatchAllQuery()).RestTotalHitsAsInt(true).Pretty(true).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected SearchResult != nil; got nil")
}
if want, have := count, res.TotalHits(); want != have {
t.Errorf("expected SearchResult.TotalHits() = %d; got %d", want, have)
}
if res.Hits == nil || res.Hits.TotalHits == nil {
t.Fatal("expected SearchResult.Hits._ != nil; got nil")
}
if want, have := count, res.Hits.TotalHits.Value; want != have {
t.Errorf("expected SearchResult.TotalHits.Value = %d; got %d", want, have)
}
if want, have := "eq", res.Hits.TotalHits.Relation; want != have {
t.Errorf("expected SearchResult.TotalHits.Relation = %q; got %q", want, have)
}
}
}
func BenchmarkSearchMatchAll(b *testing.B) {
client := setupTestClientAndCreateIndexAndAddDocs(b)
for n := 0; n < b.N; n++ {
// Match all should return all documents
all := NewMatchAllQuery()
searchResult, err := client.Search().Index(testIndexName).Query(all).Do(context.TODO())
if err != nil {
b.Fatal(err)
}
if searchResult.Hits == nil {
b.Errorf("expected SearchResult.Hits != nil; got nil")
}
if searchResult.TotalHits() == 0 {
b.Errorf("expected SearchResult.TotalHits() > %d; got %d", 0, searchResult.TotalHits())
}
}
}
func TestSearchResultTotalHits(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t)
count, err := client.Count(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
all := NewMatchAllQuery()
searchResult, err := client.Search().Index(testIndexName).Query(all).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
got := searchResult.TotalHits()
if got != count {
t.Fatalf("expected %d hits; got: %d", count, got)
}
// No hits
searchResult = &SearchResult{}
got = searchResult.TotalHits()
if got != 0 {
t.Errorf("expected %d hits; got: %d", 0, got)
}
}
func TestSearchResultWithProfiling(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t) //, SetTraceLog(log.New(os.Stdout, "", 0)))
all := NewMatchAllQuery()
searchResult, err := client.Search().Index(testIndexName).Query(all).Profile(true).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.Profile == nil {
t.Fatal("Profiled MatchAll query did not return profiling data with results")
}
}
func TestSearchResultEach(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t)
all := NewMatchAllQuery()
searchResult, err := client.Search().Index(testIndexName).Query(all).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Iterate over non-ptr type
var aTweet tweet
count := 0
for _, item := range searchResult.Each(reflect.TypeOf(aTweet)) {
count++
_, ok := item.(tweet)
if !ok {
t.Fatalf("expected hit to be serialized as tweet; got: %v", reflect.ValueOf(item))
}
}
if count == 0 {
t.Errorf("expected to find some hits; got: %d", count)
}
// Iterate over ptr-type
count = 0
var aTweetPtr *tweet
for _, item := range searchResult.Each(reflect.TypeOf(aTweetPtr)) {
count++
tw, ok := item.(*tweet)
if !ok {
t.Fatalf("expected hit to be serialized as tweet; got: %v", reflect.ValueOf(item))
}
if tw == nil {
t.Fatal("expected hit to not be nil")
}
}
if count == 0 {
t.Errorf("expected to find some hits; got: %d", count)
}
// Does not iterate when no hits are found
searchResult = &SearchResult{Hits: nil}
count = 0
for _, item := range searchResult.Each(reflect.TypeOf(aTweet)) {
count++
_ = item
}
if count != 0 {
t.Errorf("expected to not find any hits; got: %d", count)
}
searchResult = &SearchResult{Hits: &SearchHits{Hits: make([]*SearchHit, 0)}}
count = 0
for _, item := range searchResult.Each(reflect.TypeOf(aTweet)) {
count++
_ = item
}
if count != 0 {
t.Errorf("expected to not find any hits; got: %d", count)
}
}
func TestSearchResultEachNoSource(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocsNoSource(t)
all := NewMatchAllQuery()
searchResult, err := client.Search().Index(testNoSourceIndexName).Query(all).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Iterate over non-ptr type
var aTweet tweet
count := 0
for _, item := range searchResult.Each(reflect.TypeOf(aTweet)) {
count++
tw, ok := item.(tweet)
if !ok {
t.Fatalf("expected hit to be serialized as tweet; got: %v", reflect.ValueOf(item))
}
if tw.User != "" {
t.Fatalf("expected no _source hit to be empty tweet; got: %v", reflect.ValueOf(item))
}
}
if count != 2 {
t.Errorf("expected to find 2 hits; got: %d", count)
}
// Iterate over ptr-type
count = 0
var aTweetPtr *tweet
for _, item := range searchResult.Each(reflect.TypeOf(aTweetPtr)) {
count++
tw, ok := item.(*tweet)
if !ok {
t.Fatalf("expected hit to be serialized as tweet; got: %v", reflect.ValueOf(item))
}
if tw != nil {
t.Fatal("expected hit to be nil")
}
}
if count != 2 {
t.Errorf("expected to find 2 hits; got: %d", count)
}
}
func TestSearchSorting(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tweet1 := tweet{
User: "olivere", Retweets: 108,
Message: "Welcome to Golang and Elasticsearch.",
Created: time.Date(2012, 12, 12, 17, 38, 34, 0, time.UTC),
}
tweet2 := tweet{
User: "olivere", Retweets: 0,
Message: "Another unrelated topic.",
Created: time.Date(2012, 10, 10, 8, 12, 03, 0, time.UTC),
}
tweet3 := tweet{
User: "sandrae", Retweets: 12,
Message: "Cycling is fun.",
Created: time.Date(2011, 11, 11, 10, 58, 12, 0, time.UTC),
}
// Add all documents
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("3").BodyJson(&tweet3).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Match all should return all documents
all := NewMatchAllQuery()
searchResult, err := client.Search().
Index(testIndexName).
Query(all).
Sort("created", false).
Timeout("1s").
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.Hits == nil {
t.Errorf("expected SearchResult.Hits != nil; got nil")
}
if searchResult.TotalHits() != 3 {
t.Errorf("expected SearchResult.TotalHits() = %d; got %d", 3, searchResult.TotalHits())
}
if len(searchResult.Hits.Hits) != 3 {
t.Errorf("expected len(SearchResult.Hits.Hits) = %d; got %d", 3, len(searchResult.Hits.Hits))
}
for _, hit := range searchResult.Hits.Hits {
if hit.Index != testIndexName {
t.Errorf("expected SearchResult.Hits.Hit.Index = %q; got %q", testIndexName, hit.Index)
}
item := make(map[string]interface{})
err := json.Unmarshal(hit.Source, &item)
if err != nil {
t.Fatal(err)
}
}
}
func TestSearchSortingBySorters(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tweet1 := tweet{
User: "olivere", Retweets: 108,
Message: "Welcome to Golang and Elasticsearch.",
Created: time.Date(2012, 12, 12, 17, 38, 34, 0, time.UTC),
}
tweet2 := tweet{
User: "olivere", Retweets: 0,
Message: "Another unrelated topic.",
Created: time.Date(2012, 10, 10, 8, 12, 03, 0, time.UTC),
}
tweet3 := tweet{
User: "sandrae", Retweets: 12,
Message: "Cycling is fun.",
Created: time.Date(2011, 11, 11, 10, 58, 12, 0, time.UTC),
}
// Add all documents
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("3").BodyJson(&tweet3).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Match all should return all documents
all := NewMatchAllQuery()
searchResult, err := client.Search().
Index(testIndexName).
Query(all).
SortBy(NewFieldSort("created").Desc(), NewScoreSort()).
Timeout("1s").
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.Hits == nil {
t.Errorf("expected SearchResult.Hits != nil; got nil")
}
if searchResult.TotalHits() != 3 {
t.Errorf("expected SearchResult.TotalHits() = %d; got %d", 3, searchResult.TotalHits())
}
if len(searchResult.Hits.Hits) != 3 {
t.Errorf("expected len(SearchResult.Hits.Hits) = %d; got %d", 3, len(searchResult.Hits.Hits))
}
for _, hit := range searchResult.Hits.Hits {
if hit.Index != testIndexName {
t.Errorf("expected SearchResult.Hits.Hit.Index = %q; got %q", testIndexName, hit.Index)
}
item := make(map[string]interface{})
err := json.Unmarshal(hit.Source, &item)
if err != nil {
t.Fatal(err)
}
}
}
func TestSearchSpecificFields(t *testing.T) {
// client := setupTestClientAndCreateIndexAndLog(t, SetTraceLog(log.New(os.Stdout, "", 0)))
client := setupTestClientAndCreateIndex(t)
tweet1 := tweet{User: "olivere", Retweets: 1, Message: "Welcome to Golang and Elasticsearch."}
tweet2 := tweet{User: "olivere", Retweets: 2, Message: "Another unrelated topic."}
tweet3 := tweet{User: "sandrae", Retweets: 3, Message: "Cycling is fun."}
tweets := []tweet{
tweet1,
tweet2,
tweet3,
}
// Add all documents
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("3").BodyJson(&tweet3).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Match all should return all documents
all := NewMatchAllQuery()
searchResult, err := client.Search().
Index(testIndexName).
Query(all).
StoredFields("message").
DocvalueFields("retweets").
Sort("created", true).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.Hits == nil {
t.Errorf("expected SearchResult.Hits != nil; got nil")
}
if searchResult.TotalHits() != 3 {
t.Errorf("expected SearchResult.TotalHits() = %d; got %d", 3, searchResult.TotalHits())
}
if len(searchResult.Hits.Hits) != 3 {
t.Errorf("expected len(SearchResult.Hits.Hits) = %d; got %d", 3, len(searchResult.Hits.Hits))
}
// Manually inspect the fields
for _, hit := range searchResult.Hits.Hits {
if hit.Index != testIndexName {
t.Errorf("expected SearchResult.Hits.Hit.Index = %q; got %q", testIndexName, hit.Index)
}
if hit.Source != nil {
t.Fatalf("expected SearchResult.Hits.Hit.Source to be nil; got: %v", hit.Source)
}
if hit.Fields == nil {
t.Fatal("expected SearchResult.Hits.Hit.Fields to be != nil")
}
field, found := hit.Fields["message"]
if !found {
t.Errorf("expected SearchResult.Hits.Hit.Fields[%s] to be found", "message")
}
fields, ok := field.([]interface{})
if !ok {
t.Errorf("expected []interface{}; got: %v", reflect.TypeOf(fields))
}
if len(fields) != 1 {
t.Errorf("expected a field with 1 entry; got: %d", len(fields))
}
message, ok := fields[0].(string)
if !ok {
t.Errorf("expected a string; got: %v", reflect.TypeOf(fields[0]))
}
if message == "" {
t.Errorf("expected a message; got: %q", message)
}
}
// With the new helper method for fields
for i, hit := range searchResult.Hits.Hits {
// Field: message
items, ok := hit.Fields.Strings("message")
if !ok {
t.Fatalf("expected SearchResult.Hits.Hit.Fields[%s] to be found", "message")
}
if want, have := 1, len(items); want != have {
t.Fatalf("expected a field with %d entries; got %d", want, have)
}
if want, have := tweets[i].Message, items[0]; want != have {
t.Fatalf("expected message[%d]=%q; got %q", i, want, have)
}
// Field: retweets
retweets, ok := hit.Fields.Float64s("retweets")
if !ok {
t.Fatalf("expected SearchResult.Hits.Hit.Fields[%s] to be found", "retweets")
}
if want, have := 1, len(retweets); want != have {
t.Fatalf("expected a field with %d entries; got %d", want, have)
}
if want, have := tweets[i].Retweets, int(retweets[0]); want != have {
t.Fatalf("expected retweets[%d]=%q; got %q", i, want, have)
}
// Field should not exist
numbers, ok := hit.Fields.Float64s("score")
if ok {
t.Fatalf("expected SearchResult.Hits.Hit.Fields[%s] to NOT be found", "numbers")
}
if numbers != nil {
t.Fatalf("expected no field %q; got %+v", "numbers", numbers)
}
}
}
func TestSearchExplain(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tweet1 := tweet{
User: "olivere", Retweets: 108,
Message: "Welcome to Golang and Elasticsearch.",
Created: time.Date(2012, 12, 12, 17, 38, 34, 0, time.UTC),
}
tweet2 := tweet{
User: "olivere", Retweets: 0,
Message: "Another unrelated topic.",
Created: time.Date(2012, 10, 10, 8, 12, 03, 0, time.UTC),
}
tweet3 := tweet{
User: "sandrae", Retweets: 12,
Message: "Cycling is fun.",
Created: time.Date(2011, 11, 11, 10, 58, 12, 0, time.UTC),
}
// Add all documents
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("3").BodyJson(&tweet3).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Match all should return all documents
all := NewMatchAllQuery()
searchResult, err := client.Search().
Index(testIndexName).
Query(all).
Explain(true).
Timeout("1s").
Pretty(true).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.Hits == nil {
t.Errorf("expected SearchResult.Hits != nil; got nil")
}
if searchResult.TotalHits() != 3 {
t.Errorf("expected SearchResult.TotalHits() = %d; got %d", 3, searchResult.TotalHits())
}
if len(searchResult.Hits.Hits) != 3 {
t.Errorf("expected len(SearchResult.Hits.Hits) = %d; got %d", 3, len(searchResult.Hits.Hits))
}
for _, hit := range searchResult.Hits.Hits {
if hit.Index != testIndexName {
t.Errorf("expected SearchResult.Hits.Hit.Index = %q; got %q", testIndexName, hit.Index)
}
if hit.Explanation == nil {
t.Fatal("expected search explanation")
}
if hit.Explanation.Value <= 0.0 {
t.Errorf("expected explanation value to be > 0.0; got: %v", hit.Explanation.Value)
}
if hit.Explanation.Description == "" {
t.Errorf("expected explanation description != %q; got: %q", "", hit.Explanation.Description)
}
}
}
func TestSearchSource(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tweet1 := tweet{
User: "olivere", Retweets: 108,
Message: "Welcome to Golang and Elasticsearch.",
Created: time.Date(2012, 12, 12, 17, 38, 34, 0, time.UTC),
}
tweet2 := tweet{
User: "olivere", Retweets: 0,
Message: "Another unrelated topic.",
Created: time.Date(2012, 10, 10, 8, 12, 03, 0, time.UTC),
}
tweet3 := tweet{
User: "sandrae", Retweets: 12,
Message: "Cycling is fun.",
Created: time.Date(2011, 11, 11, 10, 58, 12, 0, time.UTC),
}
// Add all documents
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("3").BodyJson(&tweet3).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Set up the request JSON manually to pass to the search service via Source()
source := map[string]interface{}{
"query": map[string]interface{}{
"match_all": map[string]interface{}{},
},
}
searchResult, err := client.Search().
Index(testIndexName).
Source(source). // sets the JSON request
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.Hits == nil {
t.Errorf("expected SearchResult.Hits != nil; got nil")
}
if searchResult.TotalHits() != 3 {
t.Errorf("expected SearchResult.TotalHits() = %d; got %d", 3, searchResult.TotalHits())
}
}
func TestSearchSourceWithString(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tweet1 := tweet{
User: "olivere", Retweets: 108,
Message: "Welcome to Golang and Elasticsearch.",
Created: time.Date(2012, 12, 12, 17, 38, 34, 0, time.UTC),
}
tweet2 := tweet{
User: "olivere", Retweets: 0,
Message: "Another unrelated topic.",
Created: time.Date(2012, 10, 10, 8, 12, 03, 0, time.UTC),
}
tweet3 := tweet{
User: "sandrae", Retweets: 12,
Message: "Cycling is fun.",
Created: time.Date(2011, 11, 11, 10, 58, 12, 0, time.UTC),
}
// Add all documents
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("3").BodyJson(&tweet3).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
searchResult, err := client.Search().
Index(testIndexName).
Source(`{"query":{"match_all":{}}}`). // sets the JSON request
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.Hits == nil {
t.Errorf("expected SearchResult.Hits != nil; got nil")
}
if searchResult.TotalHits() != 3 {
t.Errorf("expected SearchResult.TotalHits() = %d; got %d", 3, searchResult.TotalHits())
}
}
func TestSearchRawString(t *testing.T) {
// client := setupTestClientAndCreateIndexAndLog(t, SetTraceLog(log.New(os.Stdout, "", 0)))
client := setupTestClientAndCreateIndex(t)
tweet1 := tweet{
User: "olivere", Retweets: 108,
Message: "Welcome to Golang and Elasticsearch.",
Created: time.Date(2012, 12, 12, 17, 38, 34, 0, time.UTC),
}
tweet2 := tweet{
User: "olivere", Retweets: 0,
Message: "Another unrelated topic.",
Created: time.Date(2012, 10, 10, 8, 12, 03, 0, time.UTC),
}
tweet3 := tweet{
User: "sandrae", Retweets: 12,
Message: "Cycling is fun.",
Created: time.Date(2011, 11, 11, 10, 58, 12, 0, time.UTC),
}
// Add all documents
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("3").BodyJson(&tweet3).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
query := RawStringQuery(`{"match_all":{}}`)
searchResult, err := client.Search().
Index(testIndexName).
Query(query).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.Hits == nil {
t.Errorf("expected SearchResult.Hits != nil; got nil")
}
if searchResult.TotalHits() != 3 {
t.Errorf("expected SearchResult.TotalHits() = %d; got %d", 3, searchResult.TotalHits())
}
}
func TestSearchSearchSource(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tweet1 := tweet{
User: "olivere", Retweets: 108,
Message: "Welcome to Golang and Elasticsearch.",
Created: time.Date(2012, 12, 12, 17, 38, 34, 0, time.UTC),
}
tweet2 := tweet{
User: "olivere", Retweets: 0,
Message: "Another unrelated topic.",
Created: time.Date(2012, 10, 10, 8, 12, 03, 0, time.UTC),
}
tweet3 := tweet{
User: "sandrae", Retweets: 12,
Message: "Cycling is fun.",
Created: time.Date(2011, 11, 11, 10, 58, 12, 0, time.UTC),
}
// Add all documents
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("3").BodyJson(&tweet3).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Set up the search source manually and pass it to the search service via SearchSource()
ss := NewSearchSource().
Query(NewMatchAllQuery()).
IndexBoost(testIndexName, 1.0).
IndexBoosts(IndexBoost{Index: testIndexName2, Boost: 2.0}).
From(0).Size(2)
// One can use ss.Source() to get to the raw interface{} that will be used
// as the search request JSON by the SearchService.
searchResult, err := client.Search().
Index(testIndexName).
SearchSource(ss). // sets the SearchSource
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.Hits == nil {
t.Errorf("expected SearchResult.Hits != nil; got nil")
}
if searchResult.TotalHits() != 3 {
t.Errorf("expected SearchResult.TotalHits() = %d; got %d", 3, searchResult.TotalHits())
}
if len(searchResult.Hits.Hits) != 2 {
t.Errorf("expected len(SearchResult.Hits.Hits) = %d; got %d", 2, len(searchResult.Hits.Hits))
}
}
func TestSearchInnerHitsOnHasChild(t *testing.T) {
// client := setupTestClientAndCreateIndex(t, SetTraceLog(log.New(os.Stdout, "", 0)))
client := setupTestClientAndCreateIndex(t)
ctx := context.Background()
// Create join index
createIndex, err := client.CreateIndex(testJoinIndex).Body(testJoinMapping).Do(ctx)
if err != nil {
t.Fatal(err)
}
if createIndex == nil {
t.Errorf("expected result to be != nil; got: %v", createIndex)
}
// Add documents
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/parent-join.html for example code.
doc1 := joinDoc{
Message: "This is a question",
JoinField: &joinField{Name: "question"},
}
_, err = client.Index().Index(testJoinIndex).Id("1").BodyJson(&doc1).Refresh("true").Do(ctx)
if err != nil {
t.Fatal(err)
}
doc2 := joinDoc{
Message: "This is another question",
JoinField: "question",
}
_, err = client.Index().Index(testJoinIndex).Id("2").BodyJson(&doc2).Refresh("true").Do(ctx)
if err != nil {
t.Fatal(err)
}
doc3 := joinDoc{
Message: "This is an answer",
JoinField: &joinField{
Name: "answer",
Parent: "1",
},
}
_, err = client.Index().Index(testJoinIndex).Id("3").BodyJson(&doc3).Routing("1").Refresh("true").Do(ctx)
if err != nil {
t.Fatal(err)
}
doc4 := joinDoc{
Message: "This is another answer",
JoinField: &joinField{
Name: "answer",
Parent: "1",
},
}
_, err = client.Index().Index(testJoinIndex).Id("4").BodyJson(&doc4).Routing("1").Refresh("true").Do(ctx)
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testJoinIndex).Do(ctx)
if err != nil {
t.Fatal(err)
}
// Search for all documents that have an answer, and return those answers as inner hits
bq := NewBoolQuery()
bq = bq.Must(NewMatchAllQuery())
bq = bq.Filter(NewHasChildQuery("answer", NewMatchAllQuery()).
InnerHit(NewInnerHit().Name("answers")))
searchResult, err := client.Search().
Index(testJoinIndex).
Query(bq).
Pretty(true).
Do(ctx)
if err != nil {
t.Fatal(err)
}
if searchResult.Hits == nil {
t.Errorf("expected SearchResult.Hits != nil; got nil")
}
if searchResult.TotalHits() != 1 {
t.Errorf("expected SearchResult.TotalHits() = %d; got %d", 2, searchResult.TotalHits())
}
if len(searchResult.Hits.Hits) != 1 {
t.Fatalf("expected len(SearchResult.Hits.Hits) = %d; got %d", 2, len(searchResult.Hits.Hits))
}
hit := searchResult.Hits.Hits[0]
if want, have := "1", hit.Id; want != have {
t.Fatalf("expected tweet %q; got: %q", want, have)
}
if hit.InnerHits == nil {
t.Fatalf("expected inner hits; got: %v", hit.InnerHits)
}
if want, have := 1, len(hit.InnerHits); want != have {
t.Fatalf("expected %d inner hits; got: %d", want, have)
}
innerHits, found := hit.InnerHits["answers"]
if !found {
t.Fatalf("expected inner hits for name %q", "answers")
}
if innerHits == nil || innerHits.Hits == nil {
t.Fatal("expected inner hits != nil")
}
if want, have := 2, len(innerHits.Hits.Hits); want != have {
t.Fatalf("expected %d inner hits; got: %d", want, have)
}
if want, have := "3", innerHits.Hits.Hits[0].Id; want != have {
t.Fatalf("expected inner hit with id %q; got: %q", want, have)
}
if want, have := "4", innerHits.Hits.Hits[1].Id; want != have {
t.Fatalf("expected inner hit with id %q; got: %q", want, have)
}
}
func TestSearchInnerHitsOnHasParent(t *testing.T) {
// client := setupTestClientAndCreateIndex(t, SetTraceLog(log.New(os.Stdout, "", 0)))
client := setupTestClientAndCreateIndex(t)
ctx := context.Background()
// Create join index
createIndex, err := client.CreateIndex(testJoinIndex).Body(testJoinMapping).Do(ctx)
if err != nil {
t.Fatal(err)
}
if createIndex == nil {
t.Errorf("expected result to be != nil; got: %v", createIndex)
}
// Add documents
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/parent-join.html for example code.
doc1 := joinDoc{
Message: "This is a question",
JoinField: &joinField{Name: "question"},
}
_, err = client.Index().Index(testJoinIndex).Id("1").BodyJson(&doc1).Refresh("true").Do(ctx)
if err != nil {
t.Fatal(err)
}
doc2 := joinDoc{
Message: "This is another question",
JoinField: "question",
}
_, err = client.Index().Index(testJoinIndex).Id("2").BodyJson(&doc2).Refresh("true").Do(ctx)
if err != nil {
t.Fatal(err)
}
doc3 := joinDoc{
Message: "This is an answer",
JoinField: &joinField{
Name: "answer",
Parent: "1",
},
}
_, err = client.Index().Index(testJoinIndex).Id("3").BodyJson(&doc3).Routing("1").Refresh("true").Do(ctx)
if err != nil {
t.Fatal(err)
}
doc4 := joinDoc{
Message: "This is another answer",
JoinField: &joinField{
Name: "answer",
Parent: "1",
},
}
_, err = client.Index().Index(testJoinIndex).Id("4").BodyJson(&doc4).Routing("1").Refresh("true").Do(ctx)
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testJoinIndex).Do(ctx)
if err != nil {
t.Fatal(err)
}
// Search for all documents that have an answer, and return those answers as inner hits
bq := NewBoolQuery()
bq = bq.Must(NewMatchAllQuery())
bq = bq.Filter(NewHasParentQuery("question", NewMatchAllQuery()).
InnerHit(NewInnerHit().Name("answers")))
searchResult, err := client.Search().
Index(testJoinIndex).
Query(bq).
Pretty(true).
Do(ctx)
if err != nil {
t.Fatal(err)
}
if searchResult.Hits == nil {
t.Errorf("expected SearchResult.Hits != nil; got nil")
}
if want, have := int64(2), searchResult.TotalHits(); want != have {
t.Errorf("expected SearchResult.TotalHits() = %d; got %d", want, have)
}
if want, have := 2, len(searchResult.Hits.Hits); want != have {
t.Fatalf("expected len(SearchResult.Hits.Hits) = %d; got %d", want, have)
}
hit := searchResult.Hits.Hits[0]
if want, have := "3", hit.Id; want != have {
t.Fatalf("expected tweet %q; got: %q", want, have)
}
if hit.InnerHits == nil {
t.Fatalf("expected inner hits; got: %v", hit.InnerHits)
}
if want, have := 1, len(hit.InnerHits); want != have {
t.Fatalf("expected %d inner hits; got: %d", want, have)
}
innerHits, found := hit.InnerHits["answers"]
if !found {
t.Fatalf("expected inner hits for name %q", "tweets")
}
if innerHits == nil || innerHits.Hits == nil {
t.Fatal("expected inner hits != nil")
}
if want, have := 1, len(innerHits.Hits.Hits); want != have {
t.Fatalf("expected %d inner hits; got: %d", want, have)
}
if want, have := "1", innerHits.Hits.Hits[0].Id; want != have {
t.Fatalf("expected inner hit with id %q; got: %q", want, have)
}
hit = searchResult.Hits.Hits[1]
if want, have := "4", hit.Id; want != have {
t.Fatalf("expected tweet %q; got: %q", want, have)
}
if hit.InnerHits == nil {
t.Fatalf("expected inner hits; got: %v", hit.InnerHits)
}
if want, have := 1, len(hit.InnerHits); want != have {
t.Fatalf("expected %d inner hits; got: %d", want, have)
}
innerHits, found = hit.InnerHits["answers"]
if !found {
t.Fatalf("expected inner hits for name %q", "tweets")
}
if innerHits == nil || innerHits.Hits == nil {
t.Fatal("expected inner hits != nil")
}
if want, have := 1, len(innerHits.Hits.Hits); want != have {
t.Fatalf("expected %d inner hits; got: %d", want, have)
}
if want, have := "1", innerHits.Hits.Hits[0].Id; want != have {
t.Fatalf("expected inner hit with id %q; got: %q", want, have)
}
}
func TestSearchInnerHitsOnNested(t *testing.T) {
//client := setupTestClientAndCreateIndexAndLog(t)
client := setupTestClientAndCreateIndex(t)
ctx := context.Background()
// Create index
createIndex, err := client.CreateIndex(testIndexName5).Body(`{
"settings":{
"number_of_shards":1,
"number_of_replicas":0
},
"mappings": {
"properties": {
"comments": {
"type": "nested"
}
}
}
}`).Do(ctx)
if err != nil {
t.Fatal(err)
}
if createIndex == nil {
t.Errorf("expected result to be != nil; got: %v", createIndex)
}
// Add documents
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.9/inner-hits.html#nested-inner-hits for example code.
type comment struct {
Author string `json:"author"`
Number int `json:"number"`
}
type doc struct {
Title string `json:"title"`
Comments []comment `json:"comments"`
}
doc1 := doc{
Title: "Test title",
Comments: []comment{
{Author: "kimchy", Number: 1},
{Author: "nik9000", Number: 2},
},
}
_, err = client.Index().Index(testIndexName5).Id("1").BodyJson(&doc1).Refresh("true").Do(ctx)
if err != nil {
t.Fatal(err)
}
// Search for all documents that have an answer, and return those answers as inner hits
q := NewNestedQuery("comments", NewMatchQuery("comments.number", 2)).InnerHit(NewInnerHit())
searchResult, err := client.Search().
Index(testIndexName5).
Query(q).
Pretty(true).
Do(ctx)
if err != nil {
t.Fatal(err)
}
if searchResult.Hits == nil {
t.Errorf("expected SearchResult.Hits != nil; got nil")
}
if searchResult.TotalHits() != 1 {
t.Errorf("expected SearchResult.TotalHits() = %d; got %d", 2, searchResult.TotalHits())
}
if len(searchResult.Hits.Hits) != 1 {
t.Fatalf("expected len(SearchResult.Hits.Hits) = %d; got %d", 2, len(searchResult.Hits.Hits))
}
hit := searchResult.Hits.Hits[0]
if want, have := "1", hit.Id; want != have {
t.Fatalf("expected tweet %q; got: %q", want, have)
}
if hit.InnerHits == nil {
t.Fatalf("expected inner hits; got: %v", hit.InnerHits)
}
if want, have := 1, len(hit.InnerHits); want != have {
t.Fatalf("expected %d inner hits; got: %d", want, have)
}
innerHits, found := hit.InnerHits["comments"]
if !found {
t.Fatalf("expected inner hits for name %q", "comments")
}
if innerHits == nil || innerHits.Hits == nil {
t.Fatal("expected inner hits != nil")
}
if want, have := 1, len(innerHits.Hits.Hits); want != have {
t.Fatalf("expected %d inner hits; got: %d", want, have)
}
if want, have := "1", innerHits.Hits.Hits[0].Id; want != have {
t.Fatalf("expected inner hit with id %q; got: %q", want, have)
}
}
func TestSearchInnerHitsOnNestedHierarchy(t *testing.T) {
// client := setupTestClientAndCreateIndexAndLog(t)
client := setupTestClientAndCreateIndex(t)
ctx := context.Background()
// Create index
createIndex, err := client.CreateIndex(testIndexName5).Body(`{
"settings":{
"number_of_shards":1,
"number_of_replicas":0
},
"mappings": {
"properties": {
"comments": {
"type": "nested",
"properties": {
"votes": {
"type": "nested"
}
}
}
}
}
}`).Do(ctx)
if err != nil {
t.Fatal(err)
}
if createIndex == nil {
t.Errorf("expected result to be != nil; got: %v", createIndex)
}
// Add documents
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.9/inner-hits.html#hierarchical-nested-inner-hits for example code.
type vote struct {
Voter string `json:"voter"`
Value int `json:"value"`
}
type comment struct {
Author string `json:"author"`
Text string `json:"text"`
Votes []vote `json:"votes"`
}
type doc struct {
Title string `json:"title"`
Comments []comment `json:"comments"`
}
doc1 := doc{
Title: "Test title",
Comments: []comment{
{Author: "kimchy", Text: "words words words", Votes: []vote{}},
{Author: "nik9000", Text: "words words words", Votes: []vote{{Voter: "kimchy", Value: 1}, {Voter: "other", Value: -1}}},
},
}
_, err = client.Index().Index(testIndexName5).Id("1").BodyJson(&doc1).Refresh("true").Do(ctx)
if err != nil {
t.Fatal(err)
}
// Search for all documents that have an answer, and return those answers as inner hits
q := NewNestedQuery("comments.votes", NewMatchQuery("comments.votes.voter", "kimchy")).InnerHit(NewInnerHit())
searchResult, err := client.Search().
Index(testIndexName5).
Query(q).
Pretty(true).
Do(ctx)
if err != nil {
t.Fatal(err)
}
if searchResult.Hits == nil {
t.Errorf("expected SearchResult.Hits != nil; got nil")
}
if searchResult.TotalHits() != 1 {
t.Errorf("expected SearchResult.TotalHits() = %d; got %d", 2, searchResult.TotalHits())
}
if len(searchResult.Hits.Hits) != 1 {
t.Fatalf("expected len(SearchResult.Hits.Hits) = %d; got %d", 2, len(searchResult.Hits.Hits))
}
hit := searchResult.Hits.Hits[0]
if want, have := "1", hit.Id; want != have {
t.Fatalf("expected tweet %q; got: %q", want, have)
}
if hit.InnerHits == nil {
t.Fatalf("expected inner hits; got: %v", hit.InnerHits)
}
if want, have := 1, len(hit.InnerHits); want != have {
t.Fatalf("expected %d inner hits; got: %d", want, have)
}
innerHits, found := hit.InnerHits["comments.votes"]
if !found {
t.Fatalf("expected inner hits for name %q", "comments.votes")
}
if innerHits == nil || innerHits.Hits == nil {
t.Fatal("expected inner hits != nil")
}
if want, have := 1, len(innerHits.Hits.Hits); want != have {
t.Fatalf("expected %d inner hits; got: %d", want, have)
}
if want, have := "1", innerHits.Hits.Hits[0].Id; want != have {
t.Fatalf("expected inner hit with id %q; got: %q", want, have)
}
}
func TestSearchBuildURL(t *testing.T) {
client := setupTestClient(t)
tests := []struct {
Indices []string
Types []string
Expected string
}{
{
[]string{},
[]string{},
"/_search",
},
{
[]string{"index1"},
[]string{},
"/index1/_search",
},
{
[]string{"index1", "index2"},
[]string{},
"/index1%2Cindex2/_search",
},
{
[]string{},
[]string{"type1"},
"/_all/type1/_search",
},
{
[]string{"index1"},
[]string{"type1"},
"/index1/type1/_search",
},
{
[]string{"index1", "index2"},
[]string{"type1", "type2"},
"/index1%2Cindex2/type1%2Ctype2/_search",
},
{
[]string{},
[]string{"type1", "type2"},
"/_all/type1%2Ctype2/_search",
},
}
for i, test := range tests {
path, _, err := client.Search().Index(test.Indices...).Type(test.Types...).buildURL()
if err != nil {
t.Errorf("case #%d: %v", i+1, err)
continue
}
if path != test.Expected {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.Expected, path)
}
}
}
func TestSearchFilterPath(t *testing.T) {
// client := setupTestClientAndCreateIndexAndAddDocs(t, SetTraceLog(log.New(os.Stdout, "", log.LstdFlags)))
client := setupTestClientAndCreateIndexAndAddDocs(t)
// Match all should return all documents
all := NewMatchAllQuery()
searchResult, err := client.Search().
Index(testIndexName).
Query(all).
FilterPath(
"took",
"hits.hits._id",
"hits.hits._source.user",
"hits.hits._source.message",
).
Timeout("1s").
Pretty(true).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.Hits == nil {
t.Fatalf("expected SearchResult.Hits != nil; got nil")
}
// 0 because it was filtered out
if want, got := int64(0), searchResult.TotalHits(); want != got {
t.Errorf("expected SearchResult.TotalHits() = %d; got %d", want, got)
}
if want, got := 3, len(searchResult.Hits.Hits); want != got {
t.Fatalf("expected len(SearchResult.Hits.Hits) = %d; got %d", want, got)
}
for _, hit := range searchResult.Hits.Hits {
if want, got := "", hit.Index; want != got {
t.Fatalf("expected index %q, got %q", want, got)
}
item := make(map[string]interface{})
err := json.Unmarshal(hit.Source, &item)
if err != nil {
t.Fatal(err)
}
// user field
v, found := item["user"]
if !found {
t.Fatalf("expected SearchResult.Hits.Hit[%q] to be found", "user")
}
if v == "" {
t.Fatalf("expected user field, got %v (%T)", v, v)
}
// No retweets field
v, found = item["retweets"]
if found {
t.Fatalf("expected SearchResult.Hits.Hit[%q] to not be found, got %v", "retweets", v)
}
if v == "" {
t.Fatalf("expected user field, got %v (%T)", v, v)
}
}
}
func TestSearchAfter(t *testing.T) {
// client := setupTestClientAndCreateIndexAndLog(t, SetTraceLog(log.New(os.Stdout, "", 0)))
client := setupTestClientAndCreateIndex(t)
tweet1 := tweet{
User: "olivere", Retweets: 108,
Message: "Welcome to Golang and Elasticsearch.",
Created: time.Date(2012, 12, 12, 17, 38, 34, 0, time.UTC),
}
tweet2 := tweet{
User: "olivere", Retweets: 0,
Message: "Another unrelated topic.",
Created: time.Date(2012, 10, 10, 8, 12, 03, 0, time.UTC),
}
tweet3 := tweet{
User: "sandrae", Retweets: 12,
Message: "Cycling is fun.",
Created: time.Date(2011, 11, 11, 10, 58, 12, 0, time.UTC),
}
// Add all documents
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("3").BodyJson(&tweet3).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Refresh().Index(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
searchResult, err := client.Search().
Index(testIndexName).
Query(NewMatchAllQuery()).
SearchAfter("olivere").
Sort("user", true).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.Hits == nil {
t.Errorf("expected SearchResult.Hits != nil; got nil")
}
if searchResult.TotalHits() != 3 {
t.Errorf("expected SearchResult.TotalHits() = %d; got %d", 3, searchResult.TotalHits())
}
if want, got := 1, len(searchResult.Hits.Hits); want != got {
t.Fatalf("expected len(SearchResult.Hits.Hits) = %d; got: %d", want, got)
}
hit := searchResult.Hits.Hits[0]
if want, got := "3", hit.Id; want != got {
t.Fatalf("expected tweet %q; got: %q", want, got)
}
}
func TestSearchResultWithFieldCollapsing(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t) // , SetTraceLog(log.New(os.Stdout, "", 0)))
searchResult, err := client.Search().
Index(testIndexName).
Query(NewMatchAllQuery()).
Collapse(NewCollapseBuilder("user")).
Pretty(true).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.Hits == nil {
t.Fatalf("expected SearchResult.Hits != nil; got nil")
}
if got := searchResult.TotalHits(); got == 0 {
t.Fatalf("expected SearchResult.TotalHits() > 0; got %d", got)
}
for _, hit := range searchResult.Hits.Hits {
if hit.Index != testIndexName {
t.Fatalf("expected SearchResult.Hits.Hit.Index = %q; got %q", testIndexName, hit.Index)
}
item := make(map[string]interface{})
err := json.Unmarshal(hit.Source, &item)
if err != nil {
t.Fatal(err)
}
if len(hit.Fields) == 0 {
t.Fatal("expected fields in SearchResult")
}
usersVal, ok := hit.Fields["user"]
if !ok {
t.Fatalf("expected %q field in fields of SearchResult", "user")
}
users, ok := usersVal.([]interface{})
if !ok {
t.Fatalf("expected slice of strings in field of SearchResult, got %T", usersVal)
}
if len(users) != 1 {
t.Fatalf("expected 1 entry in users slice, got %d", len(users))
}
}
}
func TestSearchResultWithFieldCollapsingAndInnerHits(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t) // , SetTraceLog(log.New(os.Stdout, "", 0)))
searchResult, err := client.Search().
Index(testIndexName).
Query(NewMatchAllQuery()).
Collapse(
NewCollapseBuilder("user").
InnerHit(
NewInnerHit().Name("last_tweets").Size(5).Sort("created", true),
).
MaxConcurrentGroupRequests(4)).
Pretty(true).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.Hits == nil {
t.Fatalf("expected SearchResult.Hits != nil; got nil")
}
if got := searchResult.TotalHits(); got == 0 {
t.Fatalf("expected SearchResult.TotalHits() > 0; got %d", got)
}
for _, hit := range searchResult.Hits.Hits {
if hit.Index != testIndexName {
t.Fatalf("expected SearchResult.Hits.Hit.Index = %q; got %q", testIndexName, hit.Index)
}
item := make(map[string]interface{})
err := json.Unmarshal(hit.Source, &item)
if err != nil {
t.Fatal(err)
}
if len(hit.Fields) == 0 {
t.Fatal("expected fields in SearchResult")
}
usersVal, ok := hit.Fields["user"]
if !ok {
t.Fatalf("expected %q field in fields of SearchResult", "user")
}
users, ok := usersVal.([]interface{})
if !ok {
t.Fatalf("expected slice of strings in field of SearchResult, got %T", usersVal)
}
if len(users) != 1 {
t.Fatalf("expected 1 entry in users slice, got %d", len(users))
}
lastTweets, ok := hit.InnerHits["last_tweets"]
if !ok {
t.Fatalf("expected inner_hits named %q in SearchResult", "last_tweets")
}
if lastTweets == nil {
t.Fatal("expected inner_hits in SearchResult")
}
}
}
func TestSearchScriptQuery(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t) //, SetTraceLog(log.New(os.Stdout, "", 0)))
// ES uses Painless as default scripting engine in 6.x
// Another example of using painless would be:
//
// script := NewScript(`
// String username = doc['user'].value;
// return username == 'olivere'
//`)
// See https://www.elastic.co/guide/en/elasticsearch/painless/6.7/painless-examples.html
script := NewScript("doc['user'].value == 'olivere'")
query := NewScriptQuery(script)
searchResult, err := client.Search().
Index(testIndexName).
Query(query).
Pretty(true).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.Hits == nil {
t.Errorf("expected SearchResult.Hits != nil; got nil")
}
if want, have := int64(2), searchResult.TotalHits(); want != have {
t.Errorf("expected SearchResult.TotalHits() = %d; got %d", want, have)
}
if want, have := 2, len(searchResult.Hits.Hits); want != have {
t.Errorf("expected len(SearchResult.Hits.Hits) = %d; got %d", want, have)
}
}
func TestSearchWithDocvalueFields(t *testing.T) {
// client := setupTestClientAndCreateIndexAndAddDocs(t, SetTraceLog(log.New(os.Stdout, "", log.LstdFlags)))
client := setupTestClientAndCreateIndexAndAddDocs(t)
// Match all should return all documents
searchResult, err := client.Search().
Index(testIndexName).
Query(NewMatchAllQuery()).
DocvalueFields("user", "retweets").
DocvalueFieldsWithFormat(
// DocvalueField{Field: "user"},
// DocvalueField{Field: "retweets", Format: "long"},
DocvalueField{Field: "created", Format: "epoch_millis"},
).
Pretty(true).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if searchResult.Hits == nil {
t.Errorf("expected SearchResult.Hits != nil; got nil")
}
if got, want := searchResult.TotalHits(), int64(3); got != want {
t.Errorf("expected SearchResult.TotalHits() = %d; got %d", want, got)
}
if got, want := len(searchResult.Hits.Hits), 3; got != want {
t.Errorf("expected len(SearchResult.Hits.Hits) = %d; got %d", want, got)
}
for _, hit := range searchResult.Hits.Hits {
if hit.Index != testIndexName {
t.Errorf("expected SearchResult.Hits.Hit.Index = %q; got %q", testIndexName, hit.Index)
}
item := make(map[string]interface{})
err := json.Unmarshal(hit.Source, &item)
if err != nil {
t.Fatal(err)
}
}
}
func TestSearchWithDateMathIndices(t *testing.T) {
client := setupTestClient(t) //, SetTraceLog(log.New(os.Stdout, "", log.LstdFlags)))
ctx := context.Background()
now := time.Now().UTC()
indexNameToday := fmt.Sprintf("elastic-trail-%s", now.Format("2006.01.02"))
indexNameYesterday := fmt.Sprintf("elastic-trail-%s", now.AddDate(0, 0, -1).Format("2006.01.02"))
indexNameTomorrow := fmt.Sprintf("elastic-trail-%s", now.AddDate(0, 0, +1).Format("2006.01.02"))
const mapping = `{
"settings":{
"number_of_shards":1,
"number_of_replicas":0
}
}`
// Create indices
for i, indexName := range []string{indexNameToday, indexNameTomorrow, indexNameYesterday} {
_, err := client.CreateIndex(indexName).Body(mapping).Do(ctx)
if err != nil {
t.Fatal(err)
}
defer client.DeleteIndex(indexName).Do(ctx)
// Add a document
id := fmt.Sprintf("%d", i+1)
_, err = client.Index().Index(indexName).Id(id).BodyJson(map[string]interface{}{
"index": indexName,
}).Refresh("wait_for").Do(ctx)
if err != nil {
t.Fatal(err)
}
}
// Count total
cnt, err := client.
Count(indexNameYesterday, indexNameToday, indexNameTomorrow).
Do(ctx)
if err != nil {
t.Fatal(err)
}
if cnt != 3 {
t.Fatalf("expected Count=%d; got %d", 3, cnt)
}
// Match all should return all documents
res, err := client.Search().
Index("", "").
Query(NewMatchAllQuery()).
Pretty(true).
Do(ctx)
if err != nil {
t.Fatal(err)
}
if res.Hits == nil {
t.Errorf("expected SearchResult.Hits != nil; got nil")
}
if got, want := res.TotalHits(), int64(2); got != want {
t.Errorf("expected SearchResult.TotalHits() = %d; got %d", want, got)
}
if got, want := len(res.Hits.Hits), 2; got != want {
t.Errorf("expected len(SearchResult.Hits.Hits) = %d; got %d", want, got)
}
}
func TestSearchResultDecode(t *testing.T) {
tests := []struct {
Body string
}{
// #0 With _shards.failures
{
Body: `{
"took":1146,
"timed_out":false,
"_shards":{
"total":8,
"successful":6,
"skipped":0,
"failed":2,
"failures":[
{
"shard":1,
"index":"l9leakip-0000001",
"node":"AsQq1Dh2QxCSTRSLTg0vFw",
"reason":{
"type":"illegal_argument_exception",
"reason":"The length [1119437] of field [events.summary] in doc[2524900]/index[l9leakip-0000001] exceeds the [index.highlight.max_analyzed_offset] limit [1000000]. To avoid this error, set the query parameter [max_analyzed_offset] to a value less than index setting [1000000] and this will tolerate long field values by truncating them."
}
},
{
"shard":3,
"index":"l9leakip-0000001",
"node":"AsQq1Dh2QxCSTRSLTg0vFw",
"reason":{
"type":"illegal_argument_exception",
"reason":"The length [1023566] of field [events.summary] in doc[2168434]/index[l9leakip-0000001] exceeds the [index.highlight.max_analyzed_offset] limit [1000000]. To avoid this error, set the query parameter [max_analyzed_offset] to a value less than index setting [1000000] and this will tolerate long field values by truncating them."
}
}
]
},
"hits":{}
}`,
},
}
for i, tt := range tests {
var resp SearchResult
if err := json.Unmarshal([]byte(tt.Body), &resp); err != nil {
t.Fatalf("case #%d: expected no error, got %v", i, err)
}
}
}
================================================
FILE: setup_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"bytes"
"context"
"encoding/json"
"flag"
"fmt"
"log"
"math/rand"
"net/http"
"os"
"strings"
"time"
)
const (
testIndexName = "elastic-test"
testIndexName2 = "elastic-test2"
testIndexName3 = "elastic-test3"
testIndexName4 = "elastic-test4"
testIndexName5 = "elastic-test5"
testIndexNameEmpty = "elastic-test-empty"
testMapping = `
{
"settings":{
"number_of_shards":1,
"number_of_replicas":0
},
"mappings":{
"properties":{
"user":{
"type":"keyword"
},
"message":{
"type":"text",
"store": true,
"fielddata": true
},
"tags":{
"type":"keyword"
},
"location":{
"type":"geo_point"
},
"suggest_field":{
"type":"completion"
}
}
}
}
`
testMappingWithContext = `
{
"settings":{
"number_of_shards":1,
"number_of_replicas":0
},
"mappings":{
"properties":{
"user":{
"type":"keyword"
},
"message":{
"type":"text",
"store": true,
"fielddata": true
},
"tags":{
"type":"keyword"
},
"location":{
"type":"geo_point"
},
"suggest_field":{
"type":"completion",
"contexts":[
{
"name":"user_name",
"type":"category"
}
]
}
}
}
}
`
testNoSourceIndexName = "elastic-nosource-test"
testNoSourceMapping = `
{
"settings":{
"number_of_shards":1,
"number_of_replicas":0
},
"mappings":{
"_source": {
"enabled": false
},
"properties":{
"user":{
"type":"keyword"
},
"message":{
"type":"text",
"store": true,
"fielddata": true
},
"tags":{
"type":"keyword"
},
"location":{
"type":"geo_point"
},
"suggest_field":{
"type":"completion",
"contexts":[
{
"name":"user_name",
"type":"category"
}
]
}
}
}
}
`
testJoinIndex = "elastic-joins"
testJoinMapping = `
{
"settings":{
"number_of_shards":1,
"number_of_replicas":0
},
"mappings":{
"properties":{
"message":{
"type":"text"
},
"my_join_field": {
"type": "join",
"relations": {
"question": "answer"
}
}
}
}
}
`
testOrderIndex = "elastic-orders"
testOrderMapping = `
{
"settings":{
"number_of_shards":1,
"number_of_replicas":0
},
"mappings":{
"properties":{
"article":{
"type":"text"
},
"manufacturer":{
"type":"keyword"
},
"price":{
"type":"float"
},
"time":{
"type":"date",
"format": "yyyy-MM-dd"
}
}
}
}
`
/*
testDoctypeIndex = "elastic-doctypes"
testDoctypeMapping = `
{
"settings":{
"number_of_shards":1,
"number_of_replicas":0
},
"mappings":{
"properties":{
"message":{
"type":"text",
"store": true,
"fielddata": true
}
}
}
}
`
*/
testQueryIndex = "elastic-queries"
testQueryMapping = `
{
"settings":{
"number_of_shards":1,
"number_of_replicas":0
},
"mappings":{
"properties":{
"message":{
"type":"text",
"store": true,
"fielddata": true
},
"query": {
"type": "percolator"
}
}
}
}
`
)
type tweet struct {
User string `json:"user"`
Message string `json:"message"`
Retweets int `json:"retweets"`
Image string `json:"image,omitempty"`
Created time.Time `json:"created,omitempty"`
Tags []string `json:"tags,omitempty"`
Location string `json:"location,omitempty"`
Suggest *SuggestField `json:"suggest_field,omitempty"`
}
func (t tweet) String() string {
return fmt.Sprintf("tweet{User:%q,Message:%q,Retweets:%d}", t.User, t.Message, t.Retweets)
}
type joinDoc struct {
Message string `json:"message"`
JoinField interface{} `json:"my_join_field,omitempty"`
}
type joinField struct {
Name string `json:"name"`
Parent string `json:"parent,omitempty"`
}
type order struct {
Article string `json:"article"`
Manufacturer string `json:"manufacturer"`
Price float64 `json:"price"`
Time string `json:"time,omitempty"`
}
func (o order) String() string {
return fmt.Sprintf("order{Article:%q,Manufacturer:%q,Price:%v,Time:%v}", o.Article, o.Manufacturer, o.Price, o.Time)
}
// doctype is required for Percolate tests.
type doctype struct {
Message string `json:"message"`
}
func isCI() bool {
return os.Getenv("TRAVIS") != "" || os.Getenv("CI") != "" || os.Getenv("GITHUB_ACTIONS") != ""
}
type logger interface {
Error(args ...interface{})
Errorf(format string, args ...interface{})
Fatal(args ...interface{})
Fatalf(format string, args ...interface{})
Fail()
FailNow()
Log(args ...interface{})
Logf(format string, args ...interface{})
}
func boolPtr(b bool) *bool { return &b }
// strictDecoder returns an error if any JSON fields aren't decoded.
type strictDecoder struct{}
func (d *strictDecoder) Decode(data []byte, v interface{}) error {
dec := json.NewDecoder(bytes.NewReader(data))
dec.DisallowUnknownFields()
return dec.Decode(v)
}
var (
logDeprecations = flag.String("deprecations", "off", "log or fail on deprecation warnings")
logTypesRemoval = flag.Bool("types-removal", false, "log deprecation warnings regarding types removal")
strict = flag.Bool("strict-decoder", false, "treat missing unknown fields in response as errors")
noSniff = flag.Bool("no-sniff", false, "allows to disable sniffing globally")
noHealthcheck = flag.Bool("no-healthcheck", false, "allows to disable healthchecks globally")
)
func setupTestClient(t logger, options ...ClientOptionFunc) (client *Client) {
var err error
if *noSniff {
options = append(options, SetSniff(false))
}
if *noHealthcheck {
options = append(options, SetHealthcheck(false))
}
client, err = NewClient(options...)
if err != nil {
t.Fatal(err)
}
// Use strict JSON decoder (unless a specific decoder has been specified already)
if *strict {
if client.decoder == nil {
client.decoder = &strictDecoder{}
} else if _, ok := client.decoder.(*DefaultDecoder); ok {
client.decoder = &strictDecoder{}
}
}
// Log deprecations during tests
if loglevel := *logDeprecations; loglevel != "off" {
client.deprecationlog = func(req *http.Request, res *http.Response) {
for _, warning := range res.Header["Warning"] {
if !*logTypesRemoval && strings.Contains(warning, "[types removal]") {
continue
}
switch loglevel {
default:
t.Logf("[%s] Deprecation warning: %s", req.URL, warning)
case "fail", "error":
t.Errorf("[%s] Deprecation warning: %s", req.URL, warning)
}
}
}
}
client.DeleteIndex(testIndexName).Do(context.TODO())
client.DeleteIndex(testIndexName2).Do(context.TODO())
client.DeleteIndex(testIndexName3).Do(context.TODO())
client.DeleteIndex(testIndexName4).Do(context.TODO())
client.DeleteIndex(testIndexName5).Do(context.TODO())
client.DeleteIndex(testIndexNameEmpty).Do(context.TODO())
client.DeleteIndex(testOrderIndex).Do(context.TODO())
client.DeleteIndex(testNoSourceIndexName).Do(context.TODO())
//client.DeleteIndex(testDoctypeIndex).Do(context.TODO())
client.DeleteIndex(testQueryIndex).Do(context.TODO())
client.DeleteIndex(testJoinIndex).Do(context.TODO())
return client
}
func setupTestClientAndCreateIndex(t logger, options ...ClientOptionFunc) *Client {
client := setupTestClient(t, options...)
// Create index
createIndex, err := client.CreateIndex(testIndexName).Body(testMapping).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if createIndex == nil {
t.Errorf("expected result to be != nil; got: %v", createIndex)
}
// Create second index
createIndex2, err := client.CreateIndex(testIndexName2).Body(testMappingWithContext).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if createIndex2 == nil {
t.Errorf("expected result to be != nil; got: %v", createIndex2)
}
// Create no source index
createNoSourceIndex, err := client.CreateIndex(testNoSourceIndexName).Body(testNoSourceMapping).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if createNoSourceIndex == nil {
t.Errorf("expected result to be != nil; got: %v", createNoSourceIndex)
}
// Create order index
createOrderIndex, err := client.CreateIndex(testOrderIndex).Body(testOrderMapping).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if createOrderIndex == nil {
t.Errorf("expected result to be != nil; got: %v", createOrderIndex)
}
// Create empty index
createIndexEmpty, err := client.CreateIndex(testIndexNameEmpty).Body(testMapping).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if createIndexEmpty == nil {
t.Errorf("expected result to be != nil; got: %v", createIndexEmpty)
}
return client
}
func setupTestClientAndCreateIndexAndLog(t logger, options ...ClientOptionFunc) *Client {
return setupTestClientAndCreateIndex(t, SetTraceLog(log.New(os.Stdout, "", 0)))
}
var _ = setupTestClientAndCreateIndexAndLog // remove unused warning in staticcheck
func setupTestClientAndCreateIndexAndAddDocs(t logger, options ...ClientOptionFunc) *Client {
client := setupTestClientAndCreateIndex(t, options...)
// Add tweets
tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch.", Retweets: 108, Tags: []string{"golang", "elasticsearch"}}
tweet2 := tweet{User: "olivere", Message: "Another unrelated topic.", Retweets: 0, Tags: []string{"golang"}}
tweet3 := tweet{User: "sandrae", Message: "Cycling is fun.", Retweets: 12, Tags: []string{"sports", "cycling"}}
_, err := client.Index().Index(testIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testIndexName).Id("3").Routing("someroutingkey").BodyJson(&tweet3).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Add orders
var orders []order
orders = append(orders, order{Article: "Apple MacBook", Manufacturer: "Apple", Price: 1290, Time: "2015-01-18"})
orders = append(orders, order{Article: "Paper", Manufacturer: "Canon", Price: 100, Time: "2015-03-01"})
orders = append(orders, order{Article: "Apple iPad", Manufacturer: "Apple", Price: 499, Time: "2015-04-12"})
orders = append(orders, order{Article: "Dell XPS 13", Manufacturer: "Dell", Price: 1600, Time: "2015-04-18"})
orders = append(orders, order{Article: "Apple Watch", Manufacturer: "Apple", Price: 349, Time: "2015-04-29"})
orders = append(orders, order{Article: "Samsung TV", Manufacturer: "Samsung", Price: 790, Time: "2015-05-03"})
orders = append(orders, order{Article: "Hoodie", Manufacturer: "h&m", Price: 49, Time: "2015-06-03"})
orders = append(orders, order{Article: "T-Shirt", Manufacturer: "h&m", Price: 19, Time: "2015-06-18"})
for i, o := range orders {
id := fmt.Sprintf("%d", i)
_, err = client.Index().Index(testOrderIndex).Id(id).BodyJson(&o).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
}
// Refresh
_, err = client.Refresh().Index(testIndexName, testOrderIndex).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
return client
}
func setupTestClientAndCreateIndexAndAddDocsNoSource(t logger, options ...ClientOptionFunc) *Client {
client := setupTestClientAndCreateIndex(t, options...)
// Add tweets
tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
tweet2 := tweet{User: "olivere", Message: "Another unrelated topic."}
_, err := client.Index().Index(testNoSourceIndexName).Id("1").BodyJson(&tweet1).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
_, err = client.Index().Index(testNoSourceIndexName).Id("2").BodyJson(&tweet2).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
// Refresh
_, err = client.Refresh().Index(testNoSourceIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
return client
}
func setupTestClientForXpackSecurity(t logger) (client *Client) {
var err error
// Set URL and Auth to use the platinum ES cluster
options := []ClientOptionFunc{SetURL("http://127.0.0.1:9210"), SetBasicAuth("elastic", "elastic")}
client, err = NewClient(options...)
if err != nil {
t.Fatal(err)
}
return client
}
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
func randomString(n int) string {
b := make([]rune, n)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}
type lexicographically struct {
strings []string
}
func (l lexicographically) Len() int {
return len(l.strings)
}
func (l lexicographically) Less(i, j int) bool {
return l.strings[i] < l.strings[j]
}
func (l lexicographically) Swap(i, j int) {
l.strings[i], l.strings[j] = l.strings[j], l.strings[i]
}
================================================
FILE: snapshot_create.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// SnapshotCreateService is documented at https://www.elastic.co/guide/en/elasticsearch/reference/7.0/modules-snapshots.html.
type SnapshotCreateService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
repository string
snapshot string
masterTimeout string
waitForCompletion *bool
bodyJson interface{}
bodyString string
}
// NewSnapshotCreateService creates a new SnapshotCreateService.
func NewSnapshotCreateService(client *Client) *SnapshotCreateService {
return &SnapshotCreateService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *SnapshotCreateService) Pretty(pretty bool) *SnapshotCreateService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *SnapshotCreateService) Human(human bool) *SnapshotCreateService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *SnapshotCreateService) ErrorTrace(errorTrace bool) *SnapshotCreateService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *SnapshotCreateService) FilterPath(filterPath ...string) *SnapshotCreateService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *SnapshotCreateService) Header(name string, value string) *SnapshotCreateService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *SnapshotCreateService) Headers(headers http.Header) *SnapshotCreateService {
s.headers = headers
return s
}
// Repository is the repository name.
func (s *SnapshotCreateService) Repository(repository string) *SnapshotCreateService {
s.repository = repository
return s
}
// Snapshot is the snapshot name.
func (s *SnapshotCreateService) Snapshot(snapshot string) *SnapshotCreateService {
s.snapshot = snapshot
return s
}
// MasterTimeout is documented as: Explicit operation timeout for connection to master node.
func (s *SnapshotCreateService) MasterTimeout(masterTimeout string) *SnapshotCreateService {
s.masterTimeout = masterTimeout
return s
}
// WaitForCompletion is documented as: Should this request wait until the operation has completed before returning.
func (s *SnapshotCreateService) WaitForCompletion(waitForCompletion bool) *SnapshotCreateService {
s.waitForCompletion = &waitForCompletion
return s
}
// BodyJson is documented as: The snapshot definition.
func (s *SnapshotCreateService) BodyJson(body interface{}) *SnapshotCreateService {
s.bodyJson = body
return s
}
// BodyString is documented as: The snapshot definition.
func (s *SnapshotCreateService) BodyString(body string) *SnapshotCreateService {
s.bodyString = body
return s
}
// buildURL builds the URL for the operation.
func (s *SnapshotCreateService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/_snapshot/{repository}/{snapshot}", map[string]string{
"snapshot": s.snapshot,
"repository": s.repository,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
if v := s.waitForCompletion; v != nil {
params.Set("wait_for_completion", fmt.Sprint(*v))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *SnapshotCreateService) Validate() error {
var invalid []string
if s.repository == "" {
invalid = append(invalid, "Repository")
}
if s.snapshot == "" {
invalid = append(invalid, "Snapshot")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *SnapshotCreateService) Do(ctx context.Context) (*SnapshotCreateResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Setup HTTP request body
var body interface{}
if s.bodyJson != nil {
body = s.bodyJson
} else {
body = s.bodyString
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "PUT",
Path: path,
Params: params,
Body: body,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(SnapshotCreateResponse)
if err := json.Unmarshal(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// SnapshotShardFailure stores information about failures that occurred during shard snapshotting process.
type SnapshotShardFailure struct {
Index string `json:"index"`
IndexUUID string `json:"index_uuid"`
ShardID int `json:"shard_id"`
Reason string `json:"reason"`
NodeID string `json:"node_id"`
Status string `json:"status"`
}
// SnapshotCreateResponse is the response of SnapshotCreateService.Do.
type SnapshotCreateResponse struct {
// Accepted indicates whether the request was accepted by elasticsearch.
// It's available when waitForCompletion is false.
Accepted *bool `json:"accepted"`
// Snapshot is available when waitForCompletion is true.
Snapshot *Snapshot `json:"snapshot"`
}
================================================
FILE: snapshot_create_repository.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// SnapshotCreateRepositoryService creates a snapshot repository.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/modules-snapshots.html
// for details.
type SnapshotCreateRepositoryService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
repository string
masterTimeout string
timeout string
verify *bool
typ string
settings map[string]interface{}
bodyJson interface{}
bodyString string
}
// NewSnapshotCreateRepositoryService creates a new SnapshotCreateRepositoryService.
func NewSnapshotCreateRepositoryService(client *Client) *SnapshotCreateRepositoryService {
return &SnapshotCreateRepositoryService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *SnapshotCreateRepositoryService) Pretty(pretty bool) *SnapshotCreateRepositoryService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *SnapshotCreateRepositoryService) Human(human bool) *SnapshotCreateRepositoryService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *SnapshotCreateRepositoryService) ErrorTrace(errorTrace bool) *SnapshotCreateRepositoryService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *SnapshotCreateRepositoryService) FilterPath(filterPath ...string) *SnapshotCreateRepositoryService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *SnapshotCreateRepositoryService) Header(name string, value string) *SnapshotCreateRepositoryService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *SnapshotCreateRepositoryService) Headers(headers http.Header) *SnapshotCreateRepositoryService {
s.headers = headers
return s
}
// Repository is the repository name.
func (s *SnapshotCreateRepositoryService) Repository(repository string) *SnapshotCreateRepositoryService {
s.repository = repository
return s
}
// MasterTimeout specifies an explicit operation timeout for connection to master node.
func (s *SnapshotCreateRepositoryService) MasterTimeout(masterTimeout string) *SnapshotCreateRepositoryService {
s.masterTimeout = masterTimeout
return s
}
// Timeout is an explicit operation timeout.
func (s *SnapshotCreateRepositoryService) Timeout(timeout string) *SnapshotCreateRepositoryService {
s.timeout = timeout
return s
}
// Verify indicates whether to verify the repository after creation.
func (s *SnapshotCreateRepositoryService) Verify(verify bool) *SnapshotCreateRepositoryService {
s.verify = &verify
return s
}
// Type sets the snapshot repository type, e.g. "fs".
func (s *SnapshotCreateRepositoryService) Type(typ string) *SnapshotCreateRepositoryService {
s.typ = typ
return s
}
// Settings sets all settings of the snapshot repository.
func (s *SnapshotCreateRepositoryService) Settings(settings map[string]interface{}) *SnapshotCreateRepositoryService {
s.settings = settings
return s
}
// Setting sets a single settings of the snapshot repository.
func (s *SnapshotCreateRepositoryService) Setting(name string, value interface{}) *SnapshotCreateRepositoryService {
if s.settings == nil {
s.settings = make(map[string]interface{})
}
s.settings[name] = value
return s
}
// BodyJson is documented as: The repository definition.
func (s *SnapshotCreateRepositoryService) BodyJson(body interface{}) *SnapshotCreateRepositoryService {
s.bodyJson = body
return s
}
// BodyString is documented as: The repository definition.
func (s *SnapshotCreateRepositoryService) BodyString(body string) *SnapshotCreateRepositoryService {
s.bodyString = body
return s
}
// buildURL builds the URL for the operation.
func (s *SnapshotCreateRepositoryService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/_snapshot/{repository}", map[string]string{
"repository": s.repository,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
if s.timeout != "" {
params.Set("timeout", s.timeout)
}
if v := s.verify; v != nil {
params.Set("verify", fmt.Sprint(*v))
}
return path, params, nil
}
// buildBody builds the body for the operation.
func (s *SnapshotCreateRepositoryService) buildBody() (interface{}, error) {
if s.bodyJson != nil {
return s.bodyJson, nil
}
if s.bodyString != "" {
return s.bodyString, nil
}
body := map[string]interface{}{
"type": s.typ,
}
if len(s.settings) > 0 {
body["settings"] = s.settings
}
return body, nil
}
// Validate checks if the operation is valid.
func (s *SnapshotCreateRepositoryService) Validate() error {
var invalid []string
if s.repository == "" {
invalid = append(invalid, "Repository")
}
if s.bodyString == "" && s.bodyJson == nil && len(s.settings) == 0 {
invalid = append(invalid, "BodyJson")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *SnapshotCreateRepositoryService) Do(ctx context.Context) (*SnapshotCreateRepositoryResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Setup HTTP request body
body, err := s.buildBody()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "PUT",
Path: path,
Params: params,
Body: body,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(SnapshotCreateRepositoryResponse)
if err := json.Unmarshal(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// SnapshotCreateRepositoryResponse is the response of SnapshotCreateRepositoryService.Do.
type SnapshotCreateRepositoryResponse struct {
Acknowledged bool `json:"acknowledged"`
ShardsAcknowledged bool `json:"shards_acknowledged"`
Index string `json:"index,omitempty"`
}
================================================
FILE: snapshot_create_repository_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestSnapshotPutRepositoryURL(t *testing.T) {
client := setupTestClient(t)
tests := []struct {
Repository string
Expected string
}{
{
"repo",
"/_snapshot/repo",
},
}
for _, test := range tests {
path, _, err := client.SnapshotCreateRepository(test.Repository).buildURL()
if err != nil {
t.Fatal(err)
}
if path != test.Expected {
t.Errorf("expected %q; got: %q", test.Expected, path)
}
}
}
func TestSnapshotPutRepositoryBodyWithSettings(t *testing.T) {
client := setupTestClient(t)
service := client.SnapshotCreateRepository("my_backup")
service = service.Type("fs").
Settings(map[string]interface{}{
"location": "my_backup_location",
"compress": false,
}).
Setting("compress", true).
Setting("chunk_size", 16*1024*1024)
if err := service.Validate(); err != nil {
t.Fatal(err)
}
src, err := service.buildBody()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"settings":{"chunk_size":16777216,"compress":true,"location":"my_backup_location"},"type":"fs"}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: snapshot_create_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"net/url"
"reflect"
"testing"
)
func TestSnapshotValidate(t *testing.T) {
var client *Client
err := NewSnapshotCreateService(client).Validate()
got := err.Error()
expected := "missing required fields: [Repository Snapshot]"
if got != expected {
t.Errorf("expected %q; got: %q", expected, got)
}
}
func TestSnapshotPutURL(t *testing.T) {
client := setupTestClient(t)
tests := []struct {
Repository string
Snapshot string
Pretty bool
MasterTimeout string
WaitForCompletion bool
ExpectedPath string
ExpectedParams url.Values
}{
{
Repository: "repo",
Snapshot: "snapshot_of_sunday",
Pretty: true,
MasterTimeout: "60s",
WaitForCompletion: true,
ExpectedPath: "/_snapshot/repo/snapshot_of_sunday",
ExpectedParams: url.Values{
"pretty": []string{"true"},
"master_timeout": []string{"60s"},
"wait_for_completion": []string{"true"},
},
},
}
for _, test := range tests {
path, params, err := client.SnapshotCreate(test.Repository, test.Snapshot).
Pretty(test.Pretty).
MasterTimeout(test.MasterTimeout).
WaitForCompletion(test.WaitForCompletion).
buildURL()
if err != nil {
t.Fatal(err)
}
if path != test.ExpectedPath {
t.Errorf("expected %q; got: %q", test.ExpectedPath, path)
}
if !reflect.DeepEqual(params, test.ExpectedParams) {
t.Errorf("expected %q; got: %q", test.ExpectedParams, params)
}
}
}
================================================
FILE: snapshot_delete.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"github.com/olivere/elastic/v7/uritemplates"
)
// SnapshotDeleteService deletes a snapshot from a snapshot repository.
// It is documented at
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/modules-snapshots.html.
type SnapshotDeleteService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
repository string
snapshot string
}
// NewSnapshotDeleteService creates a new SnapshotDeleteService.
func NewSnapshotDeleteService(client *Client) *SnapshotDeleteService {
return &SnapshotDeleteService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *SnapshotDeleteService) Pretty(pretty bool) *SnapshotDeleteService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *SnapshotDeleteService) Human(human bool) *SnapshotDeleteService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *SnapshotDeleteService) ErrorTrace(errorTrace bool) *SnapshotDeleteService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *SnapshotDeleteService) FilterPath(filterPath ...string) *SnapshotDeleteService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *SnapshotDeleteService) Header(name string, value string) *SnapshotDeleteService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *SnapshotDeleteService) Headers(headers http.Header) *SnapshotDeleteService {
s.headers = headers
return s
}
// Repository is the repository name.
func (s *SnapshotDeleteService) Repository(repository string) *SnapshotDeleteService {
s.repository = repository
return s
}
// Snapshot is the snapshot name.
func (s *SnapshotDeleteService) Snapshot(snapshot string) *SnapshotDeleteService {
s.snapshot = snapshot
return s
}
// buildURL builds the URL for the operation.
func (s *SnapshotDeleteService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/_snapshot/{repository}/{snapshot}", map[string]string{
"repository": s.repository,
"snapshot": s.snapshot,
})
if err != nil {
return "", url.Values{}, err
}
return path, url.Values{}, nil
}
// Validate checks if the operation is valid.
func (s *SnapshotDeleteService) Validate() error {
var invalid []string
if s.repository == "" {
invalid = append(invalid, "Repository")
}
if s.snapshot == "" {
invalid = append(invalid, "Snapshot")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *SnapshotDeleteService) Do(ctx context.Context) (*SnapshotDeleteResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "DELETE",
Path: path,
Params: params,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(SnapshotDeleteResponse)
if err := json.Unmarshal(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// SnapshotDeleteResponse is the response of SnapshotDeleteService.Do.
type SnapshotDeleteResponse struct {
Acknowledged bool `json:"acknowledged"`
}
================================================
FILE: snapshot_delete_repository.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// SnapshotDeleteRepositoryService deletes a snapshot repository.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/modules-snapshots.html
// for details.
type SnapshotDeleteRepositoryService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
repository []string
masterTimeout string
timeout string
}
// NewSnapshotDeleteRepositoryService creates a new SnapshotDeleteRepositoryService.
func NewSnapshotDeleteRepositoryService(client *Client) *SnapshotDeleteRepositoryService {
return &SnapshotDeleteRepositoryService{
client: client,
repository: make([]string, 0),
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *SnapshotDeleteRepositoryService) Pretty(pretty bool) *SnapshotDeleteRepositoryService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *SnapshotDeleteRepositoryService) Human(human bool) *SnapshotDeleteRepositoryService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *SnapshotDeleteRepositoryService) ErrorTrace(errorTrace bool) *SnapshotDeleteRepositoryService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *SnapshotDeleteRepositoryService) FilterPath(filterPath ...string) *SnapshotDeleteRepositoryService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *SnapshotDeleteRepositoryService) Header(name string, value string) *SnapshotDeleteRepositoryService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *SnapshotDeleteRepositoryService) Headers(headers http.Header) *SnapshotDeleteRepositoryService {
s.headers = headers
return s
}
// Repository is the list of repository names.
func (s *SnapshotDeleteRepositoryService) Repository(repositories ...string) *SnapshotDeleteRepositoryService {
s.repository = append(s.repository, repositories...)
return s
}
// MasterTimeout specifies an explicit operation timeout for connection to master node.
func (s *SnapshotDeleteRepositoryService) MasterTimeout(masterTimeout string) *SnapshotDeleteRepositoryService {
s.masterTimeout = masterTimeout
return s
}
// Timeout is an explicit operation timeout.
func (s *SnapshotDeleteRepositoryService) Timeout(timeout string) *SnapshotDeleteRepositoryService {
s.timeout = timeout
return s
}
// buildURL builds the URL for the operation.
func (s *SnapshotDeleteRepositoryService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/_snapshot/{repository}", map[string]string{
"repository": strings.Join(s.repository, ","),
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
if s.timeout != "" {
params.Set("timeout", s.timeout)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *SnapshotDeleteRepositoryService) Validate() error {
var invalid []string
if len(s.repository) == 0 {
invalid = append(invalid, "Repository")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *SnapshotDeleteRepositoryService) Do(ctx context.Context) (*SnapshotDeleteRepositoryResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "DELETE",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(SnapshotDeleteRepositoryResponse)
if err := json.Unmarshal(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// SnapshotDeleteRepositoryResponse is the response of SnapshotDeleteRepositoryService.Do.
type SnapshotDeleteRepositoryResponse struct {
Acknowledged bool `json:"acknowledged"`
ShardsAcknowledged bool `json:"shards_acknowledged"`
Index string `json:"index,omitempty"`
}
================================================
FILE: snapshot_delete_repository_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import "testing"
func TestSnapshotDeleteRepositoryURL(t *testing.T) {
client := setupTestClient(t)
tests := []struct {
Repository []string
Expected string
}{
{
[]string{"repo1"},
"/_snapshot/repo1",
},
{
[]string{"repo1", "repo2"},
"/_snapshot/repo1%2Crepo2",
},
}
for _, test := range tests {
path, _, err := client.SnapshotDeleteRepository(test.Repository...).buildURL()
if err != nil {
t.Fatal(err)
}
if path != test.Expected {
t.Errorf("expected %q; got: %q", test.Expected, path)
}
}
}
================================================
FILE: snapshot_delete_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"net/url"
"testing"
)
func TestSnapshotRepoValidate(t *testing.T) {
var client *Client
err := NewSnapshotDeleteService(client).Validate()
got := err.Error()
expected := "missing required fields: [Repository Snapshot]"
if got != expected {
t.Errorf("expected %q; got: %q", expected, got)
}
}
func TestSnapshotDeleteURL(t *testing.T) {
client := setupTestClient(t)
tests := []struct {
Repository string
Snapshot string
ExpectedPath string
ExpectedParams url.Values
}{
{
Repository: "repo",
Snapshot: "snapshot_of_sunday",
ExpectedPath: "/_snapshot/repo/snapshot_of_sunday",
},
{
Repository: "repö",
Snapshot: "001",
ExpectedPath: "/_snapshot/rep%C3%B6/001",
},
}
for _, tt := range tests {
path, _, err := client.SnapshotDelete(tt.Repository, tt.Snapshot).buildURL()
if err != nil {
t.Fatal(err)
}
if path != tt.ExpectedPath {
t.Errorf("expected %q; got: %q", tt.ExpectedPath, path)
}
}
}
================================================
FILE: snapshot_get.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"time"
"github.com/olivere/elastic/v7/uritemplates"
)
// SnapshotGetService lists the snapshots on a repository
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/modules-snapshots.html
// for details.
type SnapshotGetService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
repository string
snapshot []string
masterTimeout string
ignoreUnavailable *bool
verbose *bool
}
// NewSnapshotGetService creates a new SnapshotGetService.
func NewSnapshotGetService(client *Client) *SnapshotGetService {
return &SnapshotGetService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *SnapshotGetService) Pretty(pretty bool) *SnapshotGetService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *SnapshotGetService) Human(human bool) *SnapshotGetService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *SnapshotGetService) ErrorTrace(errorTrace bool) *SnapshotGetService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *SnapshotGetService) FilterPath(filterPath ...string) *SnapshotGetService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *SnapshotGetService) Header(name string, value string) *SnapshotGetService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *SnapshotGetService) Headers(headers http.Header) *SnapshotGetService {
s.headers = headers
return s
}
// Repository is the repository name.
func (s *SnapshotGetService) Repository(repository string) *SnapshotGetService {
s.repository = repository
return s
}
// Snapshot is the list of snapshot names. If not set, defaults to all snapshots.
func (s *SnapshotGetService) Snapshot(snapshots ...string) *SnapshotGetService {
s.snapshot = append(s.snapshot, snapshots...)
return s
}
// MasterTimeout specifies an explicit operation timeout for connection to master node.
func (s *SnapshotGetService) MasterTimeout(masterTimeout string) *SnapshotGetService {
s.masterTimeout = masterTimeout
return s
}
// IgnoreUnavailable specifies whether to ignore unavailable snapshots, defaults to false
func (s *SnapshotGetService) IgnoreUnavailable(ignoreUnavailable bool) *SnapshotGetService {
s.ignoreUnavailable = &ignoreUnavailable
return s
}
// Verbose specifies whether to show verbose snapshot info or only show the basic info found in the repository index blob
func (s *SnapshotGetService) Verbose(verbose bool) *SnapshotGetService {
s.verbose = &verbose
return s
}
// buildURL builds the URL for the operation.
func (s *SnapshotGetService) buildURL() (string, url.Values, error) {
// Build URL
var err error
var path string
if len(s.snapshot) > 0 {
path, err = uritemplates.Expand("/_snapshot/{repository}/{snapshot}", map[string]string{
"repository": s.repository,
"snapshot": strings.Join(s.snapshot, ","),
})
} else {
path, err = uritemplates.Expand("/_snapshot/{repository}/_all", map[string]string{
"repository": s.repository,
})
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
if v := s.ignoreUnavailable; v != nil {
params.Set("ignore_unavailable", fmt.Sprint(*v))
}
if v := s.verbose; v != nil {
params.Set("verbose", fmt.Sprint(*v))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *SnapshotGetService) Validate() error {
var invalid []string
if s.repository == "" {
invalid = append(invalid, "Repository")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *SnapshotGetService) Do(ctx context.Context) (*SnapshotGetResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(SnapshotGetResponse)
if err := json.Unmarshal(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// SnapshotGetResponse is the response of SnapshotGetService.Do.
type SnapshotGetResponse struct {
Snapshots []*Snapshot `json:"snapshots"`
}
// Snapshot contains all information about a single snapshot
type Snapshot struct {
Snapshot string `json:"snapshot"`
UUID string `json:"uuid"`
VersionID int `json:"version_id"`
Version string `json:"version"`
Indices []string `json:"indices"`
State string `json:"state"`
Reason string `json:"reason"`
StartTime time.Time `json:"start_time"`
StartTimeInMillis int64 `json:"start_time_in_millis"`
EndTime time.Time `json:"end_time"`
EndTimeInMillis int64 `json:"end_time_in_millis"`
DurationInMillis int64 `json:"duration_in_millis"`
Failures []SnapshotShardFailure `json:"failures"`
Shards *ShardsInfo `json:"shards"`
}
================================================
FILE: snapshot_get_repository.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// SnapshotGetRepositoryService reads a snapshot repository.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/modules-snapshots.html
// for details.
type SnapshotGetRepositoryService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
repository []string
local *bool
masterTimeout string
}
// NewSnapshotGetRepositoryService creates a new SnapshotGetRepositoryService.
func NewSnapshotGetRepositoryService(client *Client) *SnapshotGetRepositoryService {
return &SnapshotGetRepositoryService{
client: client,
repository: make([]string, 0),
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *SnapshotGetRepositoryService) Pretty(pretty bool) *SnapshotGetRepositoryService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *SnapshotGetRepositoryService) Human(human bool) *SnapshotGetRepositoryService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *SnapshotGetRepositoryService) ErrorTrace(errorTrace bool) *SnapshotGetRepositoryService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *SnapshotGetRepositoryService) FilterPath(filterPath ...string) *SnapshotGetRepositoryService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *SnapshotGetRepositoryService) Header(name string, value string) *SnapshotGetRepositoryService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *SnapshotGetRepositoryService) Headers(headers http.Header) *SnapshotGetRepositoryService {
s.headers = headers
return s
}
// Repository is the list of repository names.
func (s *SnapshotGetRepositoryService) Repository(repositories ...string) *SnapshotGetRepositoryService {
s.repository = append(s.repository, repositories...)
return s
}
// Local indicates whether to return local information, i.e. do not retrieve the state from master node (default: false).
func (s *SnapshotGetRepositoryService) Local(local bool) *SnapshotGetRepositoryService {
s.local = &local
return s
}
// MasterTimeout specifies an explicit operation timeout for connection to master node.
func (s *SnapshotGetRepositoryService) MasterTimeout(masterTimeout string) *SnapshotGetRepositoryService {
s.masterTimeout = masterTimeout
return s
}
// buildURL builds the URL for the operation.
func (s *SnapshotGetRepositoryService) buildURL() (string, url.Values, error) {
// Build URL
var err error
var path string
if len(s.repository) > 0 {
path, err = uritemplates.Expand("/_snapshot/{repository}", map[string]string{
"repository": strings.Join(s.repository, ","),
})
} else {
path = "/_snapshot"
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if v := s.local; v != nil {
params.Set("local", fmt.Sprint(*v))
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *SnapshotGetRepositoryService) Validate() error {
return nil
}
// Do executes the operation.
func (s *SnapshotGetRepositoryService) Do(ctx context.Context) (SnapshotGetRepositoryResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
var ret SnapshotGetRepositoryResponse
if err := json.Unmarshal(res.Body, &ret); err != nil {
return nil, err
}
return ret, nil
}
// SnapshotGetRepositoryResponse is the response of SnapshotGetRepositoryService.Do.
type SnapshotGetRepositoryResponse map[string]*SnapshotRepositoryMetaData
// SnapshotRepositoryMetaData contains all information about
// a single snapshot repository.
type SnapshotRepositoryMetaData struct {
Type string `json:"type"`
Settings map[string]interface{} `json:"settings,omitempty"`
}
================================================
FILE: snapshot_get_repository_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import "testing"
func TestSnapshotGetRepositoryURL(t *testing.T) {
client := setupTestClient(t)
tests := []struct {
Repository []string
Expected string
}{
{
[]string{},
"/_snapshot",
},
{
[]string{"repo1"},
"/_snapshot/repo1",
},
{
[]string{"repo1", "repo2"},
"/_snapshot/repo1%2Crepo2",
},
}
for _, test := range tests {
path, _, err := client.SnapshotGetRepository(test.Repository...).buildURL()
if err != nil {
t.Fatal(err)
}
if path != test.Expected {
t.Errorf("expected %q; got: %q", test.Expected, path)
}
}
}
================================================
FILE: snapshot_get_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"net/url"
"reflect"
"testing"
)
func TestSnapshotGetValidate(t *testing.T) {
var client *Client
err := NewSnapshotGetService(client).Validate()
got := err.Error()
expected := "missing required fields: [Repository]"
if got != expected {
t.Errorf("expected %q; got: %q", expected, got)
}
}
func TestSnapshotGetURL(t *testing.T) {
client := setupTestClient(t)
tests := []struct {
Repository string
Snapshot []string
MasterTimeout string
IgnoreUnavailable bool
Verbose bool
ExpectedPath string
ExpectedParams url.Values
}{
{
Repository: "repo",
Snapshot: []string{},
MasterTimeout: "60s",
IgnoreUnavailable: true,
Verbose: true,
ExpectedPath: "/_snapshot/repo/_all",
ExpectedParams: url.Values{
"master_timeout": []string{"60s"},
"ignore_unavailable": []string{"true"},
"verbose": []string{"true"},
},
},
{
Repository: "repo",
Snapshot: []string{"snapA", "snapB"},
MasterTimeout: "60s",
IgnoreUnavailable: true,
Verbose: true,
ExpectedPath: "/_snapshot/repo/snapA%2CsnapB",
ExpectedParams: url.Values{
"master_timeout": []string{"60s"},
"ignore_unavailable": []string{"true"},
"verbose": []string{"true"},
},
},
}
for _, test := range tests {
path, params, err := client.SnapshotGet(test.Repository).
MasterTimeout(test.MasterTimeout).
Snapshot(test.Snapshot...).
IgnoreUnavailable(true).
Verbose(true).
buildURL()
if err != nil {
t.Fatal(err)
}
if path != test.ExpectedPath {
t.Errorf("expected %q; got: %q", test.ExpectedPath, path)
}
if !reflect.DeepEqual(params, test.ExpectedParams) {
t.Errorf("expected %q; got: %q", test.ExpectedParams, params)
}
}
}
================================================
FILE: snapshot_restore.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// SnapshotRestoreService restores a snapshot from a snapshot repository.
//
// It is documented at
// https://www.elastic.co/guide/en/elasticsearch/reference/7.1/modules-snapshots.html#_restore.
type SnapshotRestoreService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
repository string
snapshot string
masterTimeout string
waitForCompletion *bool
ignoreUnavailable *bool
partial *bool
includeAliases *bool
includeGlobalState *bool
bodyString string
renamePattern string
renameReplacement string
indices []string
indexSettings map[string]interface{}
}
// NewSnapshotCreateService creates a new SnapshotRestoreService.
func NewSnapshotRestoreService(client *Client) *SnapshotRestoreService {
return &SnapshotRestoreService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *SnapshotRestoreService) Pretty(pretty bool) *SnapshotRestoreService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *SnapshotRestoreService) Human(human bool) *SnapshotRestoreService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *SnapshotRestoreService) ErrorTrace(errorTrace bool) *SnapshotRestoreService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *SnapshotRestoreService) FilterPath(filterPath ...string) *SnapshotRestoreService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *SnapshotRestoreService) Header(name string, value string) *SnapshotRestoreService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *SnapshotRestoreService) Headers(headers http.Header) *SnapshotRestoreService {
s.headers = headers
return s
}
// Repository name.
func (s *SnapshotRestoreService) Repository(repository string) *SnapshotRestoreService {
s.repository = repository
return s
}
// Snapshot name.
func (s *SnapshotRestoreService) Snapshot(snapshot string) *SnapshotRestoreService {
s.snapshot = snapshot
return s
}
// MasterTimeout specifies an explicit operation timeout for connection to master node.
func (s *SnapshotRestoreService) MasterTimeout(masterTimeout string) *SnapshotRestoreService {
s.masterTimeout = masterTimeout
return s
}
// WaitForCompletion indicates whether this request should wait until the operation has
// completed before returning.
func (s *SnapshotRestoreService) WaitForCompletion(waitForCompletion bool) *SnapshotRestoreService {
s.waitForCompletion = &waitForCompletion
return s
}
// Indices sets the name of the indices that should be restored from the snapshot.
func (s *SnapshotRestoreService) Indices(indices ...string) *SnapshotRestoreService {
s.indices = indices
return s
}
// IncludeGlobalState allows the global cluster state to be restored, defaults to false.
func (s *SnapshotRestoreService) IncludeGlobalState(includeGlobalState bool) *SnapshotRestoreService {
s.includeGlobalState = &includeGlobalState
return s
}
// RenamePattern helps rename indices on restore using regular expressions.
func (s *SnapshotRestoreService) RenamePattern(renamePattern string) *SnapshotRestoreService {
s.renamePattern = renamePattern
return s
}
// RenameReplacement as RenamePattern, helps rename indices on restore using regular expressions.
func (s *SnapshotRestoreService) RenameReplacement(renameReplacement string) *SnapshotRestoreService {
s.renameReplacement = renameReplacement
return s
}
// Partial indicates whether to restore indices that where partially snapshoted, defaults to false.
func (s *SnapshotRestoreService) Partial(partial bool) *SnapshotRestoreService {
s.partial = &partial
return s
}
// BodyString allows the user to specify the body of the HTTP request manually.
func (s *SnapshotRestoreService) BodyString(body string) *SnapshotRestoreService {
s.bodyString = body
return s
}
// IndexSettings sets the settings to be overwritten during the restore process
func (s *SnapshotRestoreService) IndexSettings(indexSettings map[string]interface{}) *SnapshotRestoreService {
s.indexSettings = indexSettings
return s
}
// IncludeAliases flags whether indices should be restored with their respective aliases,
// defaults to false.
func (s *SnapshotRestoreService) IncludeAliases(includeAliases bool) *SnapshotRestoreService {
s.includeAliases = &includeAliases
return s
}
// IgnoreUnavailable specifies whether to ignore unavailable snapshots, defaults to false.
func (s *SnapshotRestoreService) IgnoreUnavailable(ignoreUnavailable bool) *SnapshotRestoreService {
s.ignoreUnavailable = &ignoreUnavailable
return s
}
// Do executes the operation.
func (s *SnapshotRestoreService) Do(ctx context.Context) (*SnapshotRestoreResponse, error) {
if err := s.Validate(); err != nil {
return nil, err
}
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
var body interface{}
if len(s.bodyString) > 0 {
body = s.bodyString
} else {
body = s.buildBody()
}
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "POST",
Path: path,
Params: params,
Body: body,
Headers: s.headers,
})
if err != nil {
return nil, err
}
ret := new(SnapshotRestoreResponse)
if err := json.Unmarshal(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// Validate checks if the operation is valid.
func (s *SnapshotRestoreService) Validate() error {
var invalid []string
if s.repository == "" {
invalid = append(invalid, "Repository")
}
if s.snapshot == "" {
invalid = append(invalid, "Snapshot")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
func (s *SnapshotRestoreService) buildURL() (string, url.Values, error) {
path, err := uritemplates.Expand("/_snapshot/{repository}/{snapshot}/_restore", map[string]string{
"snapshot": s.snapshot,
"repository": s.repository,
})
if err != nil {
return "", url.Values{}, err
}
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
if v := s.waitForCompletion; v != nil {
params.Set("wait_for_completion", fmt.Sprint(*v))
}
if v := s.ignoreUnavailable; v != nil {
params.Set("ignore_unavailable", fmt.Sprint(*v))
}
return path, params, nil
}
func (s *SnapshotRestoreService) buildBody() interface{} {
body := map[string]interface{}{}
if s.includeGlobalState != nil {
body["include_global_state"] = *s.includeGlobalState
}
if s.partial != nil {
body["partial"] = *s.partial
}
if s.includeAliases != nil {
body["include_aliases"] = *s.includeAliases
}
if len(s.indices) > 0 {
body["indices"] = strings.Join(s.indices, ",")
}
if len(s.renamePattern) > 0 {
body["rename_pattern"] = s.renamePattern
}
if len(s.renamePattern) > 0 {
body["rename_replacement"] = s.renameReplacement
}
if len(s.indexSettings) > 0 {
body["index_settings"] = s.indexSettings
}
return body
}
// SnapshotRestoreResponse represents the response for SnapshotRestoreService.Do
type SnapshotRestoreResponse struct {
// Accepted indicates whether the request was accepted by Elasticsearch.
Accepted *bool `json:"accepted"`
// Snapshot information.
Snapshot *RestoreInfo `json:"snapshot"`
}
// RestoreInfo represents information about the restored snapshot.
type RestoreInfo struct {
Snapshot string `json:"snapshot"`
Indices []string `json:"indices"`
Shards ShardsInfo `json:"shards"`
}
================================================
FILE: snapshot_restore_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"net/url"
"testing"
"github.com/google/go-cmp/cmp"
)
func TestSnapshotRestoreValidate(t *testing.T) {
expected := "missing required fields: [Repository Snapshot]"
if got := NewSnapshotRestoreService(nil).Validate().Error(); got != expected {
t.Errorf("expected %q; got: %q", expected, got)
}
}
func TestSnapshotRestorePostURL(t *testing.T) {
client := setupTestClient(t)
tests := []struct {
Repository string
Snapshot string
Pretty bool
MasterTimeout string
WaitForCompletion bool
IgnoreUnavailable bool
ExpectedPath string
ExpectedParams url.Values
}{
{
Repository: "repo",
Snapshot: "snapshot_of_sunday",
Pretty: true,
MasterTimeout: "60s",
WaitForCompletion: true,
IgnoreUnavailable: true,
ExpectedPath: "/_snapshot/repo/snapshot_of_sunday/_restore",
ExpectedParams: url.Values{
"pretty": []string{"true"},
"master_timeout": []string{"60s"},
"wait_for_completion": []string{"true"},
"ignore_unavailable": []string{"true"},
},
},
}
for _, tt := range tests {
path, params, err := client.SnapshotRestore(tt.Repository, tt.Snapshot).
Pretty(tt.Pretty).
MasterTimeout(tt.MasterTimeout).
WaitForCompletion(tt.WaitForCompletion).
IgnoreUnavailable(tt.IgnoreUnavailable).
buildURL()
if err != nil {
t.Fatal(err)
}
if path != tt.ExpectedPath {
t.Errorf("expected Path=%q; got: %q", tt.ExpectedPath, path)
}
if want, have := tt.ExpectedParams, params; !cmp.Equal(want, have) {
t.Errorf("expected Params=%#v; got: %#v\ndiff: %s", want, have, cmp.Diff(want, have))
}
}
}
func TestSnapshotRestoreBuildBody(t *testing.T) {
client := setupTestClient(t)
tests := []struct {
Repository string
Snapshot string
Partial bool
IncludeAliases bool
IncludeGlobalState bool
RenamePattern string
RenameReplacement string
Indices []string
IndexSettings map[string]interface{}
ExpectedBody map[string]interface{}
}{
{
Repository: "repo",
Snapshot: "snapshot_of_sunday",
Partial: true,
IncludeAliases: true,
IncludeGlobalState: true,
RenamePattern: "index_(.+)",
RenameReplacement: "restored_index_$1",
Indices: []string{"index_1", "indexe_2", "index_3"},
IndexSettings: map[string]interface{}{
"index.number_of_replicas": 0,
},
ExpectedBody: map[string]interface{}{
"partial": true,
"include_aliases": true,
"include_global_state": true,
"rename_pattern": "index_(.+)",
"rename_replacement": "restored_index_$1",
"indices": "index_1,indexe_2,index_3",
"index_settings": map[string]interface{}{
"index.number_of_replicas": 0,
},
},
},
}
for _, tt := range tests {
body := client.SnapshotRestore(tt.Repository, tt.Snapshot).
Partial(tt.Partial).
IncludeAliases(tt.IncludeAliases).
IncludeGlobalState(tt.IncludeGlobalState).
RenamePattern(tt.RenamePattern).
RenameReplacement(tt.RenameReplacement).
Indices(tt.Indices...).
IndexSettings(tt.IndexSettings).
buildBody()
if want, have := tt.ExpectedBody, body; !cmp.Equal(want, have) {
t.Errorf("expected Body=%s; got: %s\ndiff: %s", want, have, cmp.Diff(want, have))
}
}
}
================================================
FILE: snapshot_status.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// SnapshotStatusService returns information about the status of a snapshot.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.5/modules-snapshots.html
// for details.
type SnapshotStatusService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
repository string
snapshot []string
masterTimeout string
ignoreUnavailable *bool
}
// NewSnapshotStatusService creates a new SnapshotStatusService.
func NewSnapshotStatusService(client *Client) *SnapshotStatusService {
return &SnapshotStatusService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *SnapshotStatusService) Pretty(pretty bool) *SnapshotStatusService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *SnapshotStatusService) Human(human bool) *SnapshotStatusService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *SnapshotStatusService) ErrorTrace(errorTrace bool) *SnapshotStatusService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *SnapshotStatusService) FilterPath(filterPath ...string) *SnapshotStatusService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *SnapshotStatusService) Header(name string, value string) *SnapshotStatusService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *SnapshotStatusService) Headers(headers http.Header) *SnapshotStatusService {
s.headers = headers
return s
}
// Repository is the repository name.
func (s *SnapshotStatusService) Repository(repository string) *SnapshotStatusService {
s.repository = repository
return s
}
// Snapshot is the list of snapshot names. If not set, defaults to all snapshots.
func (s *SnapshotStatusService) Snapshot(snapshots ...string) *SnapshotStatusService {
s.snapshot = append(s.snapshot, snapshots...)
return s
}
// MasterTimeout specifies an explicit operation timeout for connection to master node.
func (s *SnapshotStatusService) MasterTimeout(masterTimeout string) *SnapshotStatusService {
s.masterTimeout = masterTimeout
return s
}
// buildURL builds the URL for the operation.
func (s *SnapshotStatusService) buildURL() (string, url.Values, error) {
var err error
var path string
if s.repository != "" {
if len(s.snapshot) > 0 {
path, err = uritemplates.Expand("/_snapshot/{repository}/{snapshot}/_status", map[string]string{
"repository": s.repository,
"snapshot": strings.Join(s.snapshot, ","),
})
} else {
path, err = uritemplates.Expand("/_snapshot/{repository}/_status", map[string]string{
"repository": s.repository,
})
}
} else {
path, err = uritemplates.Expand("/_snapshot/_status", nil)
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
if v := s.ignoreUnavailable; v != nil {
params.Set("ignore_unavailable", fmt.Sprint(*v))
}
return path, params, nil
}
// Validate checks if the operation is valid.
//
// Validation only fails if snapshot names were provided but no repository was
// provided.
func (s *SnapshotStatusService) Validate() error {
if len(s.snapshot) > 0 && s.repository == "" {
return fmt.Errorf("snapshots were specified but repository is missing")
}
return nil
}
// Do executes the operation.
func (s *SnapshotStatusService) Do(ctx context.Context) (*SnapshotStatusResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(SnapshotStatusResponse)
if err := json.Unmarshal(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
type SnapshotStatusResponse struct {
Snapshots []SnapshotStatus `json:"snapshots"`
}
type SnapshotStatus struct {
Snapshot string `json:"snapshot"`
Repository string `json:"repository"`
UUID string `json:"uuid"`
State string `json:"state"`
IncludeGlobalState bool `json:"include_global_state"`
ShardsStats SnapshotShardsStats `json:"shards_stats"`
Stats SnapshotStats `json:"stats"`
Indices map[string]SnapshotIndexStatus `json:"indices"`
}
type SnapshotShardsStats struct {
Initializing int `json:"initializing"`
Started int `json:"started"`
Finalizing int `json:"finalizing"`
Done int `json:"done"`
Failed int `json:"failed"`
Total int `json:"total"`
}
type SnapshotStats struct {
Incremental struct {
FileCount int `json:"file_count"`
Size string `json:"size"`
SizeInBytes int64 `json:"size_in_bytes"`
} `json:"incremental"`
Processed struct {
FileCount int `json:"file_count"`
Size string `json:"size"`
SizeInBytes int64 `json:"size_in_bytes"`
} `json:"processed"`
Total struct {
FileCount int `json:"file_count"`
Size string `json:"size"`
SizeInBytes int64 `json:"size_in_bytes"`
} `json:"total"`
StartTime string `json:"start_time"`
StartTimeInMillis int64 `json:"start_time_in_millis"`
Time string `json:"time"`
TimeInMillis int64 `json:"time_in_millis"`
NumberOfFiles int `json:"number_of_files"`
ProcessedFiles int `json:"processed_files"`
TotalSize string `json:"total_size"`
TotalSizeInBytes int64 `json:"total_size_in_bytes"`
}
type SnapshotIndexStatus struct {
ShardsStats SnapshotShardsStats `json:"shards_stats"`
Stats SnapshotStats `json:"stats"`
Shards map[string]SnapshotIndexShardStatus `json:"shards"`
}
type SnapshotIndexShardStatus struct {
Stage string `json:"stage"` // initializing, started, finalize, done, or failed
Stats SnapshotStats `json:"stats"`
Node string `json:"node"`
Reason string `json:"reason"` // reason for failure
}
================================================
FILE: snapshot_status_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"net/url"
"reflect"
"testing"
)
func TestSnapshotStatusURL(t *testing.T) {
client := setupTestClient(t)
tests := []struct {
Repository string
Snapshot []string
MasterTimeout string
ExpectedPath string
ExpectedParams url.Values
}{
{
Repository: "repo",
Snapshot: []string{},
MasterTimeout: "60s",
ExpectedPath: "/_snapshot/repo/_status",
ExpectedParams: url.Values{
"master_timeout": []string{"60s"},
},
},
{
Repository: "repo",
Snapshot: []string{"snapA", "snapB"},
MasterTimeout: "30s",
ExpectedPath: "/_snapshot/repo/snapA%2CsnapB/_status",
ExpectedParams: url.Values{
"master_timeout": []string{"30s"},
},
},
}
for _, test := range tests {
path, params, err := client.SnapshotStatus().
MasterTimeout(test.MasterTimeout).
Repository(test.Repository).
Snapshot(test.Snapshot...).
buildURL()
if err != nil {
t.Fatal(err)
}
if path != test.ExpectedPath {
t.Errorf("expected %q; got: %q", test.ExpectedPath, path)
}
if !reflect.DeepEqual(params, test.ExpectedParams) {
t.Errorf("expected %q; got: %q", test.ExpectedParams, params)
}
}
}
================================================
FILE: snapshot_verify_repository.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// SnapshotVerifyRepositoryService verifies a snapshop repository.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/modules-snapshots.html
// for details.
type SnapshotVerifyRepositoryService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
repository string
masterTimeout string
timeout string
}
// NewSnapshotVerifyRepositoryService creates a new SnapshotVerifyRepositoryService.
func NewSnapshotVerifyRepositoryService(client *Client) *SnapshotVerifyRepositoryService {
return &SnapshotVerifyRepositoryService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *SnapshotVerifyRepositoryService) Pretty(pretty bool) *SnapshotVerifyRepositoryService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *SnapshotVerifyRepositoryService) Human(human bool) *SnapshotVerifyRepositoryService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *SnapshotVerifyRepositoryService) ErrorTrace(errorTrace bool) *SnapshotVerifyRepositoryService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *SnapshotVerifyRepositoryService) FilterPath(filterPath ...string) *SnapshotVerifyRepositoryService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *SnapshotVerifyRepositoryService) Header(name string, value string) *SnapshotVerifyRepositoryService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *SnapshotVerifyRepositoryService) Headers(headers http.Header) *SnapshotVerifyRepositoryService {
s.headers = headers
return s
}
// Repository specifies the repository name.
func (s *SnapshotVerifyRepositoryService) Repository(repository string) *SnapshotVerifyRepositoryService {
s.repository = repository
return s
}
// MasterTimeout is the explicit operation timeout for connection to master node.
func (s *SnapshotVerifyRepositoryService) MasterTimeout(masterTimeout string) *SnapshotVerifyRepositoryService {
s.masterTimeout = masterTimeout
return s
}
// Timeout is an explicit operation timeout.
func (s *SnapshotVerifyRepositoryService) Timeout(timeout string) *SnapshotVerifyRepositoryService {
s.timeout = timeout
return s
}
// buildURL builds the URL for the operation.
func (s *SnapshotVerifyRepositoryService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/_snapshot/{repository}/_verify", map[string]string{
"repository": s.repository,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
if s.timeout != "" {
params.Set("timeout", s.timeout)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *SnapshotVerifyRepositoryService) Validate() error {
var invalid []string
if s.repository == "" {
invalid = append(invalid, "Repository")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *SnapshotVerifyRepositoryService) Do(ctx context.Context) (*SnapshotVerifyRepositoryResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "POST",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(SnapshotVerifyRepositoryResponse)
if err := json.Unmarshal(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// SnapshotVerifyRepositoryResponse is the response of SnapshotVerifyRepositoryService.Do.
type SnapshotVerifyRepositoryResponse struct {
Nodes map[string]*SnapshotVerifyRepositoryNode `json:"nodes"`
}
type SnapshotVerifyRepositoryNode struct {
Name string `json:"name"`
}
================================================
FILE: snapshot_verify_repository_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import "testing"
func TestSnapshotVerifyRepositoryURL(t *testing.T) {
client := setupTestClient(t)
tests := []struct {
Repository string
Expected string
}{
{
"repo",
"/_snapshot/repo/_verify",
},
}
for _, test := range tests {
path, _, err := client.SnapshotVerifyRepository(test.Repository).buildURL()
if err != nil {
t.Fatal(err)
}
if path != test.Expected {
t.Errorf("expected %q; got: %q", test.Expected, path)
}
}
}
================================================
FILE: sort.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import "errors"
// -- Sorter --
// Sorter is an interface for sorting strategies, e.g. ScoreSort or FieldSort.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-request-sort.html.
type Sorter interface {
Source() (interface{}, error)
}
// -- SortInfo --
// SortInfo contains information about sorting a field.
type SortInfo struct {
Sorter
Field string
Ascending bool
Missing interface{}
IgnoreUnmapped *bool
UnmappedType string
SortMode string
NestedFilter Query // deprecated in 6.1 and replaced by Filter
Filter Query
NestedPath string // deprecated in 6.1 and replaced by Path
Path string
NestedSort *NestedSort // deprecated in 6.1 and replaced by Nested
Nested *NestedSort
}
func (info SortInfo) Source() (interface{}, error) {
prop := make(map[string]interface{})
if info.Ascending {
prop["order"] = "asc"
} else {
prop["order"] = "desc"
}
if info.Missing != nil {
prop["missing"] = info.Missing
}
if info.IgnoreUnmapped != nil {
prop["ignore_unmapped"] = *info.IgnoreUnmapped
}
if info.UnmappedType != "" {
prop["unmapped_type"] = info.UnmappedType
}
if info.SortMode != "" {
prop["mode"] = info.SortMode
}
if info.Filter != nil {
src, err := info.Filter.Source()
if err != nil {
return nil, err
}
prop["filter"] = src
} else if info.NestedFilter != nil {
src, err := info.NestedFilter.Source()
if err != nil {
return nil, err
}
prop["nested_filter"] = src // deprecated in 6.1
}
if info.Path != "" {
prop["path"] = info.Path
} else if info.NestedPath != "" {
prop["nested_path"] = info.NestedPath // deprecated in 6.1
}
if info.Nested != nil {
src, err := info.Nested.Source()
if err != nil {
return nil, err
}
prop["nested"] = src
} else if info.NestedSort != nil {
src, err := info.NestedSort.Source()
if err != nil {
return nil, err
}
prop["nested"] = src
}
source := make(map[string]interface{})
source[info.Field] = prop
return source, nil
}
// -- SortByDoc --
// SortByDoc sorts by the "_doc" field, as described in
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-request-scroll.html.
//
// Example:
// ss := elastic.NewSearchSource()
// ss = ss.SortBy(elastic.SortByDoc{})
type SortByDoc struct {
Sorter
}
// Source returns the JSON-serializable data.
func (s SortByDoc) Source() (interface{}, error) {
return "_doc", nil
}
// -- ScoreSort --
// ScoreSort sorts by relevancy score.
type ScoreSort struct {
Sorter
ascending bool
}
// NewScoreSort creates a new ScoreSort.
func NewScoreSort() *ScoreSort {
return &ScoreSort{ascending: false} // Descending by default!
}
// Order defines whether sorting ascending (default) or descending.
func (s *ScoreSort) Order(ascending bool) *ScoreSort {
s.ascending = ascending
return s
}
// Asc sets ascending sort order.
func (s *ScoreSort) Asc() *ScoreSort {
s.ascending = true
return s
}
// Desc sets descending sort order.
func (s *ScoreSort) Desc() *ScoreSort {
s.ascending = false
return s
}
// Source returns the JSON-serializable data.
func (s *ScoreSort) Source() (interface{}, error) {
source := make(map[string]interface{})
x := make(map[string]interface{})
source["_score"] = x
if s.ascending {
x["order"] = "asc"
} else {
x["order"] = "desc"
}
return source, nil
}
// -- FieldSort --
// FieldSort sorts by a given field.
type FieldSort struct {
Sorter
fieldName string
ascending bool
missing interface{}
unmappedType *string
sortMode *string
filter Query
path *string
nested *NestedSort
}
// NewFieldSort creates a new FieldSort.
func NewFieldSort(fieldName string) *FieldSort {
return &FieldSort{
fieldName: fieldName,
ascending: true,
}
}
// FieldName specifies the name of the field to be used for sorting.
func (s *FieldSort) FieldName(fieldName string) *FieldSort {
s.fieldName = fieldName
return s
}
// Order defines whether sorting ascending (default) or descending.
func (s *FieldSort) Order(ascending bool) *FieldSort {
s.ascending = ascending
return s
}
// Asc sets ascending sort order.
func (s *FieldSort) Asc() *FieldSort {
s.ascending = true
return s
}
// Desc sets descending sort order.
func (s *FieldSort) Desc() *FieldSort {
s.ascending = false
return s
}
// Missing sets the value to be used when a field is missing in a document.
// You can also use "_last" or "_first" to sort missing last or first
// respectively.
func (s *FieldSort) Missing(missing interface{}) *FieldSort {
s.missing = missing
return s
}
// UnmappedType sets the type to use when the current field is not mapped
// in an index.
func (s *FieldSort) UnmappedType(typ string) *FieldSort {
s.unmappedType = &typ
return s
}
// SortMode specifies what values to pick in case a document contains
// multiple values for the targeted sort field. Possible values are:
// min, max, sum, and avg.
func (s *FieldSort) SortMode(sortMode string) *FieldSort {
s.sortMode = &sortMode
return s
}
// NestedFilter sets a filter that nested objects should match with
// in order to be taken into account for sorting.
// Deprecated: Use Filter instead.
func (s *FieldSort) NestedFilter(nestedFilter Query) *FieldSort {
s.filter = nestedFilter
return s
}
// Filter sets a filter that nested objects should match with
// in order to be taken into account for sorting.
func (s *FieldSort) Filter(filter Query) *FieldSort {
s.filter = filter
return s
}
// NestedPath is used if sorting occurs on a field that is inside a
// nested object.
// Deprecated: Use Path instead.
func (s *FieldSort) NestedPath(nestedPath string) *FieldSort {
s.path = &nestedPath
return s
}
// Path is used if sorting occurs on a field that is inside a
// nested object.
func (s *FieldSort) Path(path string) *FieldSort {
s.path = &path
return s
}
// NestedSort is available starting with 6.1 and will replace NestedFilter
// and NestedPath.
// Deprecated: Use Nested instead.
func (s *FieldSort) NestedSort(nestedSort *NestedSort) *FieldSort {
s.nested = nestedSort
return s
}
// Nested is available starting with 6.1 and will replace Filter and Path.
func (s *FieldSort) Nested(nested *NestedSort) *FieldSort {
s.nested = nested
return s
}
// Source returns the JSON-serializable data.
func (s *FieldSort) Source() (interface{}, error) {
source := make(map[string]interface{})
x := make(map[string]interface{})
source[s.fieldName] = x
if s.ascending {
x["order"] = "asc"
} else {
x["order"] = "desc"
}
if s.missing != nil {
x["missing"] = s.missing
}
if s.unmappedType != nil {
x["unmapped_type"] = *s.unmappedType
}
if s.sortMode != nil {
x["mode"] = *s.sortMode
}
if s.filter != nil {
src, err := s.filter.Source()
if err != nil {
return nil, err
}
x["filter"] = src
}
if s.path != nil {
x["path"] = *s.path
}
if s.nested != nil {
src, err := s.nested.Source()
if err != nil {
return nil, err
}
x["nested"] = src
}
return source, nil
}
// -- GeoDistanceSort --
// GeoDistanceSort allows for sorting by geographic distance.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-request-sort.html#_geo_distance_sorting.
type GeoDistanceSort struct {
Sorter
fieldName string
points []*GeoPoint
geohashes []string
distanceType *string
unit string
ignoreUnmapped *bool
ascending bool
sortMode *string
nestedFilter Query
nestedPath *string
nestedSort *NestedSort
}
// NewGeoDistanceSort creates a new sorter for geo distances.
func NewGeoDistanceSort(fieldName string) *GeoDistanceSort {
return &GeoDistanceSort{
fieldName: fieldName,
ascending: true,
}
}
// FieldName specifies the name of the (geo) field to use for sorting.
func (s *GeoDistanceSort) FieldName(fieldName string) *GeoDistanceSort {
s.fieldName = fieldName
return s
}
// Order defines whether sorting ascending (default) or descending.
func (s *GeoDistanceSort) Order(ascending bool) *GeoDistanceSort {
s.ascending = ascending
return s
}
// Asc sets ascending sort order.
func (s *GeoDistanceSort) Asc() *GeoDistanceSort {
s.ascending = true
return s
}
// Desc sets descending sort order.
func (s *GeoDistanceSort) Desc() *GeoDistanceSort {
s.ascending = false
return s
}
// Point specifies a point to create the range distance aggregations from.
func (s *GeoDistanceSort) Point(lat, lon float64) *GeoDistanceSort {
s.points = append(s.points, GeoPointFromLatLon(lat, lon))
return s
}
// Points specifies the geo point(s) to create the range distance aggregations from.
func (s *GeoDistanceSort) Points(points ...*GeoPoint) *GeoDistanceSort {
s.points = append(s.points, points...)
return s
}
// GeoHashes specifies the geo point to create the range distance aggregations from.
func (s *GeoDistanceSort) GeoHashes(geohashes ...string) *GeoDistanceSort {
s.geohashes = append(s.geohashes, geohashes...)
return s
}
// Unit specifies the distance unit to use. It defaults to km.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/common-options.html#distance-units
// for details.
func (s *GeoDistanceSort) Unit(unit string) *GeoDistanceSort {
s.unit = unit
return s
}
// IgnoreUnmapped indicates whether the unmapped field should be treated as
// a missing value. Setting it to true is equivalent to specifying an
// unmapped_type in the field sort. The default is false (unmapped field
// causes the search to fail).
func (s *GeoDistanceSort) IgnoreUnmapped(ignoreUnmapped bool) *GeoDistanceSort {
s.ignoreUnmapped = &ignoreUnmapped
return s
}
// GeoDistance is an alias for DistanceType.
func (s *GeoDistanceSort) GeoDistance(geoDistance string) *GeoDistanceSort {
return s.DistanceType(geoDistance)
}
// DistanceType describes how to compute the distance, e.g. "arc" or "plane".
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-request-sort.html#geo-sorting
// for details.
func (s *GeoDistanceSort) DistanceType(distanceType string) *GeoDistanceSort {
s.distanceType = &distanceType
return s
}
// SortMode specifies what values to pick in case a document contains
// multiple values for the targeted sort field. Possible values are:
// min, max, sum, and avg.
func (s *GeoDistanceSort) SortMode(sortMode string) *GeoDistanceSort {
s.sortMode = &sortMode
return s
}
// NestedFilter sets a filter that nested objects should match with
// in order to be taken into account for sorting.
func (s *GeoDistanceSort) NestedFilter(nestedFilter Query) *GeoDistanceSort {
s.nestedFilter = nestedFilter
return s
}
// NestedPath is used if sorting occurs on a field that is inside a
// nested object.
func (s *GeoDistanceSort) NestedPath(nestedPath string) *GeoDistanceSort {
s.nestedPath = &nestedPath
return s
}
// NestedSort is available starting with 6.1 and will replace NestedFilter
// and NestedPath.
func (s *GeoDistanceSort) NestedSort(nestedSort *NestedSort) *GeoDistanceSort {
s.nestedSort = nestedSort
return s
}
// Source returns the JSON-serializable data.
func (s *GeoDistanceSort) Source() (interface{}, error) {
source := make(map[string]interface{})
x := make(map[string]interface{})
source["_geo_distance"] = x
// Points
var ptarr []interface{}
for _, pt := range s.points {
ptarr = append(ptarr, pt.Source())
}
for _, geohash := range s.geohashes {
ptarr = append(ptarr, geohash)
}
x[s.fieldName] = ptarr
if s.unit != "" {
x["unit"] = s.unit
}
if s.ignoreUnmapped != nil {
x["ignore_unmapped"] = *s.ignoreUnmapped
}
if s.distanceType != nil {
x["distance_type"] = *s.distanceType
}
if s.ascending {
x["order"] = "asc"
} else {
x["order"] = "desc"
}
if s.sortMode != nil {
x["mode"] = *s.sortMode
}
if s.nestedFilter != nil {
src, err := s.nestedFilter.Source()
if err != nil {
return nil, err
}
x["nested_filter"] = src
}
if s.nestedPath != nil {
x["nested_path"] = *s.nestedPath
}
if s.nestedSort != nil {
src, err := s.nestedSort.Source()
if err != nil {
return nil, err
}
x["nested"] = src
}
return source, nil
}
// -- ScriptSort --
// ScriptSort sorts by a custom script. See
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/modules-scripting.html#modules-scripting
// for details about scripting.
type ScriptSort struct {
Sorter
script *Script
typ string
ascending bool
sortMode *string
nestedFilter Query
nestedPath *string
nestedSort *NestedSort
}
// NewScriptSort creates and initializes a new ScriptSort.
// You must provide a script and a type, e.g. "string" or "number".
func NewScriptSort(script *Script, typ string) *ScriptSort {
return &ScriptSort{
script: script,
typ: typ,
ascending: true,
}
}
// Type sets the script type, which can be either "string" or "number".
func (s *ScriptSort) Type(typ string) *ScriptSort {
s.typ = typ
return s
}
// Order defines whether sorting ascending (default) or descending.
func (s *ScriptSort) Order(ascending bool) *ScriptSort {
s.ascending = ascending
return s
}
// Asc sets ascending sort order.
func (s *ScriptSort) Asc() *ScriptSort {
s.ascending = true
return s
}
// Desc sets descending sort order.
func (s *ScriptSort) Desc() *ScriptSort {
s.ascending = false
return s
}
// SortMode specifies what values to pick in case a document contains
// multiple values for the targeted sort field. Possible values are:
// min or max.
func (s *ScriptSort) SortMode(sortMode string) *ScriptSort {
s.sortMode = &sortMode
return s
}
// NestedFilter sets a filter that nested objects should match with
// in order to be taken into account for sorting.
func (s *ScriptSort) NestedFilter(nestedFilter Query) *ScriptSort {
s.nestedFilter = nestedFilter
return s
}
// NestedPath is used if sorting occurs on a field that is inside a
// nested object.
func (s *ScriptSort) NestedPath(nestedPath string) *ScriptSort {
s.nestedPath = &nestedPath
return s
}
// NestedSort is available starting with 6.1 and will replace NestedFilter
// and NestedPath.
func (s *ScriptSort) NestedSort(nestedSort *NestedSort) *ScriptSort {
s.nestedSort = nestedSort
return s
}
// Source returns the JSON-serializable data.
func (s *ScriptSort) Source() (interface{}, error) {
if s.script == nil {
return nil, errors.New("ScriptSort expected a script")
}
source := make(map[string]interface{})
x := make(map[string]interface{})
source["_script"] = x
src, err := s.script.Source()
if err != nil {
return nil, err
}
x["script"] = src
x["type"] = s.typ
if s.ascending {
x["order"] = "asc"
} else {
x["order"] = "desc"
}
if s.sortMode != nil {
x["mode"] = *s.sortMode
}
if s.nestedFilter != nil {
src, err := s.nestedFilter.Source()
if err != nil {
return nil, err
}
x["nested_filter"] = src
}
if s.nestedPath != nil {
x["nested_path"] = *s.nestedPath
}
if s.nestedSort != nil {
src, err := s.nestedSort.Source()
if err != nil {
return nil, err
}
x["nested"] = src
}
return source, nil
}
// -- NestedSort --
// NestedSort is used for fields that are inside a nested object.
// It takes a "path" argument and an optional nested filter that the
// nested objects should match with in order to be taken into account
// for sorting.
//
// NestedSort is available from 6.1 and replaces nestedFilter and nestedPath
// in the other sorters.
type NestedSort struct {
Sorter
path string
filter Query
nestedSort *NestedSort
}
// NewNestedSort creates a new NestedSort.
func NewNestedSort(path string) *NestedSort {
return &NestedSort{path: path}
}
// Filter sets the filter.
func (s *NestedSort) Filter(filter Query) *NestedSort {
s.filter = filter
return s
}
// NestedSort embeds another level of nested sorting.
func (s *NestedSort) NestedSort(nestedSort *NestedSort) *NestedSort {
s.nestedSort = nestedSort
return s
}
// Source returns the JSON-serializable data.
func (s *NestedSort) Source() (interface{}, error) {
source := make(map[string]interface{})
if s.path != "" {
source["path"] = s.path
}
if s.filter != nil {
src, err := s.filter.Source()
if err != nil {
return nil, err
}
source["filter"] = src
}
if s.nestedSort != nil {
src, err := s.nestedSort.Source()
if err != nil {
return nil, err
}
source["nested"] = src
}
return source, nil
}
================================================
FILE: sort_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestSortInfo(t *testing.T) {
builder := SortInfo{Field: "grade", Ascending: false}
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"grade":{"order":"desc"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSortInfoComplex(t *testing.T) {
builder := SortInfo{
Field: "price",
Ascending: false,
Missing: "_last",
SortMode: "avg",
Filter: NewTermQuery("product.color", "blue"),
Path: "variant",
}
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"price":{"filter":{"term":{"product.color":"blue"}},"missing":"_last","mode":"avg","order":"desc","path":"variant"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestScoreSort(t *testing.T) {
builder := NewScoreSort()
if builder.ascending != false {
t.Error("expected score sorter to be ascending by default")
}
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"_score":{"order":"desc"}}` // ScoreSort is "desc" by default
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestScoreSortOrderAscending(t *testing.T) {
builder := NewScoreSort().Asc()
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"_score":{"order":"asc"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestScoreSortOrderDescending(t *testing.T) {
builder := NewScoreSort().Desc()
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"_score":{"order":"desc"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestFieldSort(t *testing.T) {
builder := NewFieldSort("grade")
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"grade":{"order":"asc"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestFieldSortOrderDesc(t *testing.T) {
builder := NewFieldSort("grade").Desc()
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"grade":{"order":"desc"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestFieldSortComplex(t *testing.T) {
builder := NewFieldSort("price").Desc().
SortMode("avg").
Missing("_last").
UnmappedType("product").
Filter(NewTermQuery("product.color", "blue")).
Path("variant")
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"price":{"filter":{"term":{"product.color":"blue"}},"missing":"_last","mode":"avg","order":"desc","path":"variant","unmapped_type":"product"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestGeoDistanceSort(t *testing.T) {
builder := NewGeoDistanceSort("pin.location").
Point(-70, 40).
Order(true).
Unit("km").
IgnoreUnmapped(true).
SortMode("min").
GeoDistance("plane")
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"_geo_distance":{"distance_type":"plane","ignore_unmapped":true,"mode":"min","order":"asc","pin.location":[{"lat":-70,"lon":40}],"unit":"km"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestGeoDistanceSortOrderDesc(t *testing.T) {
builder := NewGeoDistanceSort("pin.location").
Point(-70, 40).
Unit("km").
SortMode("min").
GeoDistance("arc").
Desc()
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"_geo_distance":{"distance_type":"arc","mode":"min","order":"desc","pin.location":[{"lat":-70,"lon":40}],"unit":"km"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestScriptSort(t *testing.T) {
builder := NewScriptSort(NewScript("doc['field_name'].value * factor").Param("factor", 1.1), "number").Order(true)
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"_script":{"order":"asc","script":{"params":{"factor":1.1},"source":"doc['field_name'].value * factor"},"type":"number"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestScriptSortOrderDesc(t *testing.T) {
builder := NewScriptSort(NewScript("doc['field_name'].value * factor").Param("factor", 1.1), "number").Desc()
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"_script":{"order":"desc","script":{"params":{"factor":1.1},"source":"doc['field_name'].value * factor"},"type":"number"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestNestedSort(t *testing.T) {
builder := NewNestedSort("offer").
Filter(NewTermQuery("offer.color", "blue"))
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"filter":{"term":{"offer.color":"blue"}},"path":"offer"}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestFieldSortWithNestedSort(t *testing.T) {
builder := NewFieldSort("offer.price").
Asc().
SortMode("avg").
NestedSort(
NewNestedSort("offer").Filter(NewTermQuery("offer.color", "blue")),
)
src, err := builder.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"offer.price":{"mode":"avg","nested":{"filter":{"term":{"offer.color":"blue"}},"path":"offer"},"order":"asc"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: suggest_field.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"errors"
)
// SuggestField can be used by the caller to specify a suggest field
// at index time. For a detailed example, see e.g.
// https://www.elastic.co/blog/you-complete-me.
type SuggestField struct {
inputs []string
weight int
contextQueries []SuggesterContextQuery
}
func NewSuggestField(input ...string) *SuggestField {
return &SuggestField{
inputs: input,
weight: -1,
}
}
func (f *SuggestField) Input(input ...string) *SuggestField {
if f.inputs == nil {
f.inputs = make([]string, 0)
}
f.inputs = append(f.inputs, input...)
return f
}
func (f *SuggestField) Weight(weight int) *SuggestField {
f.weight = weight
return f
}
func (f *SuggestField) ContextQuery(queries ...SuggesterContextQuery) *SuggestField {
f.contextQueries = append(f.contextQueries, queries...)
return f
}
// MarshalJSON encodes SuggestField into JSON.
func (f *SuggestField) MarshalJSON() ([]byte, error) {
source := make(map[string]interface{})
if f.inputs != nil {
switch len(f.inputs) {
case 1:
source["input"] = f.inputs[0]
default:
source["input"] = f.inputs
}
}
if f.weight >= 0 {
source["weight"] = f.weight
}
switch len(f.contextQueries) {
case 0:
case 1:
src, err := f.contextQueries[0].Source()
if err != nil {
return nil, err
}
source["contexts"] = src
default:
ctxq := make(map[string]interface{})
for _, query := range f.contextQueries {
src, err := query.Source()
if err != nil {
return nil, err
}
m, ok := src.(map[string]interface{})
if !ok {
return nil, errors.New("SuggesterContextQuery must be of type map[string]interface{}")
}
for k, v := range m {
ctxq[k] = v
}
}
source["contexts"] = ctxq
}
return json.Marshal(source)
}
================================================
FILE: suggest_field_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestSuggestField(t *testing.T) {
field := NewSuggestField().
Input("Welcome to Golang and Elasticsearch.", "Golang and Elasticsearch").
Weight(1).
ContextQuery(
NewSuggesterCategoryMapping("color").FieldName("color_field").DefaultValues("red", "green", "blue"),
NewSuggesterGeoMapping("location").Precision("5m").Neighbors(true).DefaultLocations(GeoPointFromLatLon(52.516275, 13.377704)),
)
data, err := json.Marshal(field)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"contexts":{"color":{"default":["red","green","blue"],"path":"color_field","type":"category"},"location":{"default":{"lat":52.516275,"lon":13.377704},"neighbors":true,"precision":["5m"],"type":"geo"}},"input":["Welcome to Golang and Elasticsearch.","Golang and Elasticsearch"],"weight":1}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: suggester.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// Represents the generic suggester interface.
// A suggester's only purpose is to return the
// source of the query as a JSON-serializable
// object. Returning a map[string]interface{}
// will do.
type Suggester interface {
Name() string
Source(includeName bool) (interface{}, error)
}
================================================
FILE: suggester_completion.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import "errors"
// CompletionSuggester is a fast suggester for e.g. type-ahead completion.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-suggesters-completion.html
// for more details.
type CompletionSuggester struct {
Suggester
name string
text string
prefix string
regex string
field string
analyzer string
size *int
shardSize *int
contextQueries []SuggesterContextQuery
fuzzyOptions *FuzzyCompletionSuggesterOptions
regexOptions *RegexCompletionSuggesterOptions
skipDuplicates *bool
}
// Creates a new completion suggester.
func NewCompletionSuggester(name string) *CompletionSuggester {
return &CompletionSuggester{
name: name,
}
}
func (q *CompletionSuggester) Name() string {
return q.name
}
func (q *CompletionSuggester) Text(text string) *CompletionSuggester {
q.text = text
return q
}
func (q *CompletionSuggester) Prefix(prefix string) *CompletionSuggester {
q.prefix = prefix
return q
}
func (q *CompletionSuggester) PrefixWithEditDistance(prefix string, editDistance interface{}) *CompletionSuggester {
q.prefix = prefix
q.fuzzyOptions = NewFuzzyCompletionSuggesterOptions().EditDistance(editDistance)
return q
}
func (q *CompletionSuggester) PrefixWithOptions(prefix string, options *FuzzyCompletionSuggesterOptions) *CompletionSuggester {
q.prefix = prefix
q.fuzzyOptions = options
return q
}
func (q *CompletionSuggester) FuzzyOptions(options *FuzzyCompletionSuggesterOptions) *CompletionSuggester {
q.fuzzyOptions = options
return q
}
func (q *CompletionSuggester) Fuzziness(fuzziness interface{}) *CompletionSuggester {
if q.fuzzyOptions == nil {
q.fuzzyOptions = NewFuzzyCompletionSuggesterOptions()
}
q.fuzzyOptions = q.fuzzyOptions.EditDistance(fuzziness)
return q
}
func (q *CompletionSuggester) Regex(regex string) *CompletionSuggester {
q.regex = regex
return q
}
func (q *CompletionSuggester) RegexWithOptions(regex string, options *RegexCompletionSuggesterOptions) *CompletionSuggester {
q.regex = regex
q.regexOptions = options
return q
}
func (q *CompletionSuggester) RegexOptions(options *RegexCompletionSuggesterOptions) *CompletionSuggester {
q.regexOptions = options
return q
}
func (q *CompletionSuggester) SkipDuplicates(skipDuplicates bool) *CompletionSuggester {
q.skipDuplicates = &skipDuplicates
return q
}
func (q *CompletionSuggester) Field(field string) *CompletionSuggester {
q.field = field
return q
}
func (q *CompletionSuggester) Analyzer(analyzer string) *CompletionSuggester {
q.analyzer = analyzer
return q
}
func (q *CompletionSuggester) Size(size int) *CompletionSuggester {
q.size = &size
return q
}
func (q *CompletionSuggester) ShardSize(shardSize int) *CompletionSuggester {
q.shardSize = &shardSize
return q
}
func (q *CompletionSuggester) ContextQuery(query SuggesterContextQuery) *CompletionSuggester {
q.contextQueries = append(q.contextQueries, query)
return q
}
func (q *CompletionSuggester) ContextQueries(queries ...SuggesterContextQuery) *CompletionSuggester {
q.contextQueries = append(q.contextQueries, queries...)
return q
}
// completionSuggesterRequest is necessary because the order in which
// the JSON elements are routed to Elasticsearch is relevant.
// We got into trouble when using plain maps because the text element
// needs to go before the completion element.
type completionSuggesterRequest struct {
Text string `json:"text,omitempty"`
Prefix string `json:"prefix,omitempty"`
Regex string `json:"regex,omitempty"`
Completion interface{} `json:"completion,omitempty"`
}
// Source creates the JSON data for the completion suggester.
func (q *CompletionSuggester) Source(includeName bool) (interface{}, error) {
cs := &completionSuggesterRequest{}
if q.text != "" {
cs.Text = q.text
}
if q.prefix != "" {
cs.Prefix = q.prefix
}
if q.regex != "" {
cs.Regex = q.regex
}
suggester := make(map[string]interface{})
cs.Completion = suggester
if q.analyzer != "" {
suggester["analyzer"] = q.analyzer
}
if q.field != "" {
suggester["field"] = q.field
}
if q.size != nil {
suggester["size"] = *q.size
}
if q.shardSize != nil {
suggester["shard_size"] = *q.shardSize
}
switch len(q.contextQueries) {
case 0:
case 1:
src, err := q.contextQueries[0].Source()
if err != nil {
return nil, err
}
suggester["contexts"] = src
default:
ctxq := make(map[string]interface{})
for _, query := range q.contextQueries {
src, err := query.Source()
if err != nil {
return nil, err
}
// Merge the dictionary into ctxq
m, ok := src.(map[string]interface{})
if !ok {
return nil, errors.New("elastic: context query is not a map")
}
for k, v := range m {
ctxq[k] = v
}
}
suggester["contexts"] = ctxq
}
// Fuzzy options
if q.fuzzyOptions != nil {
src, err := q.fuzzyOptions.Source()
if err != nil {
return nil, err
}
suggester["fuzzy"] = src
}
// Regex options
if q.regexOptions != nil {
src, err := q.regexOptions.Source()
if err != nil {
return nil, err
}
suggester["regex"] = src
}
if q.skipDuplicates != nil {
suggester["skip_duplicates"] = *q.skipDuplicates
}
// TODO(oe) Add completion-suggester specific parameters here
if !includeName {
return cs, nil
}
source := make(map[string]interface{})
source[q.name] = cs
return source, nil
}
// -- Fuzzy options --
// FuzzyCompletionSuggesterOptions represents the options for fuzzy completion suggester.
type FuzzyCompletionSuggesterOptions struct {
editDistance interface{}
transpositions *bool
minLength *int
prefixLength *int
unicodeAware *bool
maxDeterminizedStates *int
}
// NewFuzzyCompletionSuggesterOptions initializes a new FuzzyCompletionSuggesterOptions instance.
func NewFuzzyCompletionSuggesterOptions() *FuzzyCompletionSuggesterOptions {
return &FuzzyCompletionSuggesterOptions{}
}
// EditDistance specifies the maximum number of edits, e.g. a number like "1" or "2"
// or a string like "0..2" or ">5".
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/common-options.html#fuzziness
// for details.
func (o *FuzzyCompletionSuggesterOptions) EditDistance(editDistance interface{}) *FuzzyCompletionSuggesterOptions {
o.editDistance = editDistance
return o
}
// Transpositions, if set to true, are counted as one change instead of two (defaults to true).
func (o *FuzzyCompletionSuggesterOptions) Transpositions(transpositions bool) *FuzzyCompletionSuggesterOptions {
o.transpositions = &transpositions
return o
}
// MinLength represents the minimum length of the input before fuzzy suggestions are returned (defaults to 3).
func (o *FuzzyCompletionSuggesterOptions) MinLength(minLength int) *FuzzyCompletionSuggesterOptions {
o.minLength = &minLength
return o
}
// PrefixLength represents the minimum length of the input, which is not checked for
// fuzzy alternatives (defaults to 1).
func (o *FuzzyCompletionSuggesterOptions) PrefixLength(prefixLength int) *FuzzyCompletionSuggesterOptions {
o.prefixLength = &prefixLength
return o
}
// UnicodeAware, if true, all measurements (like fuzzy edit distance, transpositions, and lengths)
// are measured in Unicode code points instead of in bytes. This is slightly slower than
// raw bytes, so it is set to false by default.
func (o *FuzzyCompletionSuggesterOptions) UnicodeAware(unicodeAware bool) *FuzzyCompletionSuggesterOptions {
o.unicodeAware = &unicodeAware
return o
}
// MaxDeterminizedStates is currently undocumented in Elasticsearch. It represents
// the maximum automaton states allowed for fuzzy expansion.
func (o *FuzzyCompletionSuggesterOptions) MaxDeterminizedStates(max int) *FuzzyCompletionSuggesterOptions {
o.maxDeterminizedStates = &max
return o
}
// Source creates the JSON data.
func (o *FuzzyCompletionSuggesterOptions) Source() (interface{}, error) {
out := make(map[string]interface{})
if o.editDistance != nil {
out["fuzziness"] = o.editDistance
}
if o.transpositions != nil {
out["transpositions"] = *o.transpositions
}
if o.minLength != nil {
out["min_length"] = *o.minLength
}
if o.prefixLength != nil {
out["prefix_length"] = *o.prefixLength
}
if o.unicodeAware != nil {
out["unicode_aware"] = *o.unicodeAware
}
if o.maxDeterminizedStates != nil {
out["max_determinized_states"] = *o.maxDeterminizedStates
}
return out, nil
}
// -- Regex options --
// RegexCompletionSuggesterOptions represents the options for regex completion suggester.
type RegexCompletionSuggesterOptions struct {
flags interface{} // string or int
maxDeterminizedStates *int
}
// NewRegexCompletionSuggesterOptions initializes a new RegexCompletionSuggesterOptions instance.
func NewRegexCompletionSuggesterOptions() *RegexCompletionSuggesterOptions {
return &RegexCompletionSuggesterOptions{}
}
// Flags represents internal regex flags.
// Possible flags are ALL (default), ANYSTRING, COMPLEMENT, EMPTY, INTERSECTION, INTERVAL, or NONE.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-suggesters-completion.html#regex
// for details.
func (o *RegexCompletionSuggesterOptions) Flags(flags interface{}) *RegexCompletionSuggesterOptions {
o.flags = flags
return o
}
// MaxDeterminizedStates represents the maximum automaton states allowed for regex expansion.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-suggesters-completion.html#regex
// for details.
func (o *RegexCompletionSuggesterOptions) MaxDeterminizedStates(max int) *RegexCompletionSuggesterOptions {
o.maxDeterminizedStates = &max
return o
}
// Source creates the JSON data.
func (o *RegexCompletionSuggesterOptions) Source() (interface{}, error) {
out := make(map[string]interface{})
if o.flags != nil {
out["flags"] = o.flags
}
if o.maxDeterminizedStates != nil {
out["max_determinized_states"] = *o.maxDeterminizedStates
}
return out, nil
}
================================================
FILE: suggester_completion_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestCompletionSuggesterSource(t *testing.T) {
s := NewCompletionSuggester("song-suggest").
Text("n").
Field("suggest")
src, err := s.Source(true)
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"song-suggest":{"text":"n","completion":{"field":"suggest"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestCompletionSuggesterPrefixSource(t *testing.T) {
s := NewCompletionSuggester("song-suggest").
Prefix("nir").
Field("suggest")
src, err := s.Source(true)
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"song-suggest":{"prefix":"nir","completion":{"field":"suggest"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestCompletionSuggesterPrefixWithFuzzySource(t *testing.T) {
s := NewCompletionSuggester("song-suggest").
Prefix("nor").
Field("suggest").
FuzzyOptions(NewFuzzyCompletionSuggesterOptions().EditDistance(2))
src, err := s.Source(true)
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"song-suggest":{"prefix":"nor","completion":{"field":"suggest","fuzzy":{"fuzziness":2}}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestCompletionSuggesterRegexSource(t *testing.T) {
s := NewCompletionSuggester("song-suggest").
Regex("n[ever|i]r").
Field("suggest")
src, err := s.Source(true)
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"song-suggest":{"regex":"n[ever|i]r","completion":{"field":"suggest"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestCompletionSuggesterSourceWithMultipleContexts(t *testing.T) {
s := NewCompletionSuggester("song-suggest").
Text("n").
Field("suggest").
ContextQueries(
NewSuggesterCategoryQuery("artist", "Sting"),
NewSuggesterCategoryQuery("label", "BMG"),
)
src, err := s.Source(true)
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"song-suggest":{"text":"n","completion":{"contexts":{"artist":[{"context":"Sting"}],"label":[{"context":"BMG"}]},"field":"suggest"}}}`
if got != expected {
t.Errorf("expected %s\n,got:\n%s", expected, got)
}
}
================================================
FILE: suggester_context.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import "errors"
// SuggesterContextQuery is used to define context information within
// a suggestion request.
type SuggesterContextQuery interface {
Source() (interface{}, error)
}
// ContextSuggester is a fast suggester for e.g. type-ahead completion that supports filtering and boosting based on contexts.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/suggester-context.html
// for more details.
type ContextSuggester struct {
Suggester
name string
prefix string
field string
size *int
contextQueries []SuggesterContextQuery
}
// Creates a new context suggester.
func NewContextSuggester(name string) *ContextSuggester {
return &ContextSuggester{
name: name,
contextQueries: make([]SuggesterContextQuery, 0),
}
}
func (q *ContextSuggester) Name() string {
return q.name
}
func (q *ContextSuggester) Prefix(prefix string) *ContextSuggester {
q.prefix = prefix
return q
}
func (q *ContextSuggester) Field(field string) *ContextSuggester {
q.field = field
return q
}
func (q *ContextSuggester) Size(size int) *ContextSuggester {
q.size = &size
return q
}
func (q *ContextSuggester) ContextQuery(query SuggesterContextQuery) *ContextSuggester {
q.contextQueries = append(q.contextQueries, query)
return q
}
func (q *ContextSuggester) ContextQueries(queries ...SuggesterContextQuery) *ContextSuggester {
q.contextQueries = append(q.contextQueries, queries...)
return q
}
// contextSuggesterRequest is necessary because the order in which
// the JSON elements are routed to Elasticsearch is relevant.
// We got into trouble when using plain maps because the text element
// needs to go before the completion element.
type contextSuggesterRequest struct {
Prefix string `json:"prefix"`
Completion interface{} `json:"completion"`
}
// Creates the source for the context suggester.
func (q *ContextSuggester) Source(includeName bool) (interface{}, error) {
cs := &contextSuggesterRequest{}
if q.prefix != "" {
cs.Prefix = q.prefix
}
suggester := make(map[string]interface{})
cs.Completion = suggester
if q.field != "" {
suggester["field"] = q.field
}
if q.size != nil {
suggester["size"] = *q.size
}
switch len(q.contextQueries) {
case 0:
case 1:
src, err := q.contextQueries[0].Source()
if err != nil {
return nil, err
}
suggester["contexts"] = src
default:
ctxq := make(map[string]interface{})
for _, query := range q.contextQueries {
src, err := query.Source()
if err != nil {
return nil, err
}
// Merge the dictionary into ctxq
m, ok := src.(map[string]interface{})
if !ok {
return nil, errors.New("elastic: context query is not a map")
}
for k, v := range m {
ctxq[k] = v
}
}
suggester["contexts"] = ctxq
}
if !includeName {
return cs, nil
}
source := make(map[string]interface{})
source[q.name] = cs
return source, nil
}
================================================
FILE: suggester_context_category.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// -- SuggesterCategoryMapping --
// SuggesterCategoryMapping provides a mapping for a category context in a suggester.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/suggester-context.html#_category_mapping.
type SuggesterCategoryMapping struct {
name string
fieldName string
defaultValues []string
}
// NewSuggesterCategoryMapping creates a new SuggesterCategoryMapping.
func NewSuggesterCategoryMapping(name string) *SuggesterCategoryMapping {
return &SuggesterCategoryMapping{
name: name,
defaultValues: make([]string, 0),
}
}
func (q *SuggesterCategoryMapping) DefaultValues(values ...string) *SuggesterCategoryMapping {
q.defaultValues = append(q.defaultValues, values...)
return q
}
func (q *SuggesterCategoryMapping) FieldName(fieldName string) *SuggesterCategoryMapping {
q.fieldName = fieldName
return q
}
// Source returns a map that will be used to serialize the context query as JSON.
func (q *SuggesterCategoryMapping) Source() (interface{}, error) {
source := make(map[string]interface{})
x := make(map[string]interface{})
source[q.name] = x
x["type"] = "category"
switch len(q.defaultValues) {
case 0:
x["default"] = q.defaultValues
case 1:
x["default"] = q.defaultValues[0]
default:
x["default"] = q.defaultValues
}
if q.fieldName != "" {
x["path"] = q.fieldName
}
return source, nil
}
// -- SuggesterCategoryQuery --
// SuggesterCategoryQuery provides querying a category context in a suggester.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/suggester-context.html#_category_query.
type SuggesterCategoryQuery struct {
name string
values map[string]*int
}
// NewSuggesterCategoryQuery creates a new SuggesterCategoryQuery.
func NewSuggesterCategoryQuery(name string, values ...string) *SuggesterCategoryQuery {
q := &SuggesterCategoryQuery{
name: name,
values: make(map[string]*int),
}
if len(values) > 0 {
q.Values(values...)
}
return q
}
func (q *SuggesterCategoryQuery) Value(val string) *SuggesterCategoryQuery {
q.values[val] = nil
return q
}
func (q *SuggesterCategoryQuery) ValueWithBoost(val string, boost int) *SuggesterCategoryQuery {
q.values[val] = &boost
return q
}
func (q *SuggesterCategoryQuery) Values(values ...string) *SuggesterCategoryQuery {
for _, val := range values {
q.values[val] = nil
}
return q
}
// Source returns a map that will be used to serialize the context query as JSON.
func (q *SuggesterCategoryQuery) Source() (interface{}, error) {
source := make(map[string]interface{})
switch len(q.values) {
case 0:
source[q.name] = make([]string, 0)
default:
contexts := make([]interface{}, 0)
for val, boost := range q.values {
context := make(map[string]interface{})
context["context"] = val
if boost != nil {
context["boost"] = *boost
}
contexts = append(contexts, context)
}
source[q.name] = contexts
}
return source, nil
}
type SuggesterCategoryIndex struct {
name string
values []string
}
// NewSuggesterCategoryIndex creates a new SuggesterCategoryIndex.
func NewSuggesterCategoryIndex(name string, values ...string) *SuggesterCategoryIndex {
q := &SuggesterCategoryIndex{
name: name,
values: values,
}
return q
}
func (q *SuggesterCategoryIndex) Values(values ...string) *SuggesterCategoryIndex {
q.values = append(q.values, values...)
return q
}
// Source returns a map that will be used to serialize the context query as JSON.
func (q *SuggesterCategoryIndex) Source() (interface{}, error) {
source := make(map[string]interface{})
switch len(q.values) {
case 0:
source[q.name] = make([]string, 0)
case 1:
source[q.name] = q.values[0]
default:
source[q.name] = q.values
}
return source, nil
}
================================================
FILE: suggester_context_category_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestSuggesterCategoryMapping(t *testing.T) {
q := NewSuggesterCategoryMapping("color").DefaultValues("red")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"color":{"default":"red","type":"category"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSuggesterCategoryMappingWithTwoDefaultValues(t *testing.T) {
q := NewSuggesterCategoryMapping("color").DefaultValues("red", "orange")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"color":{"default":["red","orange"],"type":"category"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSuggesterCategoryMappingWithFieldName(t *testing.T) {
q := NewSuggesterCategoryMapping("color").
DefaultValues("red", "orange").
FieldName("color_field")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"color":{"default":["red","orange"],"path":"color_field","type":"category"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSuggesterCategoryQuery(t *testing.T) {
q := NewSuggesterCategoryQuery("color", "red")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"color":[{"context":"red"}]}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSuggesterCategoryQueryWithTwoValues(t *testing.T) {
q := NewSuggesterCategoryQuery("color", "red", "yellow")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expectedOutcomes := []string{
`{"color":[{"context":"red"},{"context":"yellow"}]}`,
`{"color":[{"context":"yellow"},{"context":"red"}]}`,
}
var match bool
for _, expected := range expectedOutcomes {
if got == expected {
match = true
break
}
}
if !match {
t.Errorf("expected any of %v\n,got:\n%s", expectedOutcomes, got)
}
}
func TestSuggesterCategoryQueryWithBoost(t *testing.T) {
q := NewSuggesterCategoryQuery("color", "red")
q.ValueWithBoost("yellow", 4)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expectedOutcomes := []string{
`{"color":[{"context":"red"},{"boost":4,"context":"yellow"}]}`,
`{"color":[{"boost":4,"context":"yellow"},{"context":"red"}]}`,
}
var match bool
for _, expected := range expectedOutcomes {
if got == expected {
match = true
break
}
}
if !match {
t.Errorf("expected any of %v\n,got:\n%v", expectedOutcomes, got)
}
}
func TestSuggesterCategoryQueryWithoutBoost(t *testing.T) {
q := NewSuggesterCategoryQuery("color", "red")
q.Value("yellow")
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expectedOutcomes := []string{
`{"color":[{"context":"red"},{"context":"yellow"}]}`,
`{"color":[{"context":"yellow"},{"context":"red"}]}`,
}
var match bool
for _, expected := range expectedOutcomes {
if got == expected {
match = true
break
}
}
if !match {
t.Errorf("expected any of %v\n,got:\n%s", expectedOutcomes, got)
}
}
func TestSuggesterCategoryIndex(t *testing.T) {
in := NewSuggesterCategoryIndex("color", "red")
src, err := in.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"color":"red"}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSuggesterCategoryIndexWithTwoValues(t *testing.T) {
in := NewSuggesterCategoryIndex("color", "red", "yellow")
src, err := in.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"color":["red","yellow"]}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: suggester_context_geo.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// -- SuggesterGeoMapping --
// SuggesterGeoMapping provides a mapping for a geolocation context in a suggester.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/suggester-context.html#_geo_location_mapping.
type SuggesterGeoMapping struct {
name string
defaultLocations []*GeoPoint
precision []string
neighbors *bool
fieldName string
}
// NewSuggesterGeoMapping creates a new SuggesterGeoMapping.
func NewSuggesterGeoMapping(name string) *SuggesterGeoMapping {
return &SuggesterGeoMapping{
name: name,
}
}
func (q *SuggesterGeoMapping) DefaultLocations(locations ...*GeoPoint) *SuggesterGeoMapping {
q.defaultLocations = append(q.defaultLocations, locations...)
return q
}
func (q *SuggesterGeoMapping) Precision(precision ...string) *SuggesterGeoMapping {
q.precision = append(q.precision, precision...)
return q
}
func (q *SuggesterGeoMapping) Neighbors(neighbors bool) *SuggesterGeoMapping {
q.neighbors = &neighbors
return q
}
func (q *SuggesterGeoMapping) FieldName(fieldName string) *SuggesterGeoMapping {
q.fieldName = fieldName
return q
}
// Source returns a map that will be used to serialize the context query as JSON.
func (q *SuggesterGeoMapping) Source() (interface{}, error) {
source := make(map[string]interface{})
x := make(map[string]interface{})
source[q.name] = x
x["type"] = "geo"
if len(q.precision) > 0 {
x["precision"] = q.precision
}
if q.neighbors != nil {
x["neighbors"] = *q.neighbors
}
switch len(q.defaultLocations) {
case 0:
case 1:
x["default"] = q.defaultLocations[0].Source()
default:
var arr []interface{}
for _, p := range q.defaultLocations {
arr = append(arr, p.Source())
}
x["default"] = arr
}
if q.fieldName != "" {
x["path"] = q.fieldName
}
return source, nil
}
// -- SuggesterGeoQuery --
// SuggesterGeoQuery provides querying a geolocation context in a suggester.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/suggester-context.html#_geo_location_query
type SuggesterGeoQuery struct {
name string
location *GeoPoint
precision string
neighbours []string
boost *int
}
// NewSuggesterGeoQuery creates a new SuggesterGeoQuery.
func NewSuggesterGeoQuery(name string, location *GeoPoint) *SuggesterGeoQuery {
return &SuggesterGeoQuery{
name: name,
location: location,
neighbours: make([]string, 0),
}
}
func (q *SuggesterGeoQuery) Precision(precision string) *SuggesterGeoQuery {
q.precision = precision
return q
}
func (q *SuggesterGeoQuery) Neighbours(neighbours ...string) *SuggesterGeoQuery {
q.neighbours = append(q.neighbours, neighbours...)
return q
}
func (q *SuggesterGeoQuery) Boost(boost int) *SuggesterGeoQuery {
q.boost = &boost
return q
}
// Source returns a map that will be used to serialize the context query as JSON.
func (q *SuggesterGeoQuery) Source() (interface{}, error) {
source := make(map[string]interface{})
x := make(map[string]interface{})
source[q.name] = x
if q.location != nil {
x["context"] = q.location.Source()
}
if q.precision != "" {
x["precision"] = q.precision
}
if q.boost != nil {
x["boost"] = q.boost
}
switch len(q.neighbours) {
case 0:
case 1:
x["neighbours"] = q.neighbours[0]
default:
x["neighbours"] = q.neighbours
}
return source, nil
}
type SuggesterGeoIndex struct {
name string
locations []*GeoPoint
}
// NewSuggesterGeoQuery creates a new SuggesterGeoQuery.
func NewSuggesterGeoIndex(name string) *SuggesterGeoIndex {
return &SuggesterGeoIndex{
name: name,
}
}
func (q *SuggesterGeoIndex) Locations(locations ...*GeoPoint) *SuggesterGeoIndex {
q.locations = append(q.locations, locations...)
return q
}
// Source returns a map that will be used to serialize the context query as JSON.
func (q *SuggesterGeoIndex) Source() (interface{}, error) {
source := make(map[string]interface{})
switch len(q.locations) {
case 0:
source[q.name] = make([]string, 0)
case 1:
source[q.name] = q.locations[0].Source()
default:
var arr []interface{}
for _, p := range q.locations {
arr = append(arr, p.Source())
}
source[q.name] = arr
}
return source, nil
}
================================================
FILE: suggester_context_geo_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestSuggesterGeoMapping(t *testing.T) {
q := NewSuggesterGeoMapping("location").
Precision("1km", "5m").
Neighbors(true).
FieldName("pin").
DefaultLocations(GeoPointFromLatLon(0.0, 0.0))
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"location":{"default":{"lat":0,"lon":0},"neighbors":true,"path":"pin","precision":["1km","5m"],"type":"geo"}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSuggesterGeoQuery(t *testing.T) {
q := NewSuggesterGeoQuery("location", GeoPointFromLatLon(11.5, 62.71)).Precision("1km").
Neighbours("2km", "3km").Boost(2)
src, err := q.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expectedOutcomes := []string{
`{"location":{"context":{"lat":11.5,"lon":62.71},"precision":"1km","boost":2,"neighbours":["2km","3km"]}}`,
`{"location":{"boost":2,"context":{"lat":11.5,"lon":62.71},"neighbours":["2km","3km"],"precision":"1km"}}`,
}
var match bool
for _, expected := range expectedOutcomes {
if got == expected {
match = true
break
}
}
if !match {
t.Errorf("expected any of %v\n,got:\n%s", expectedOutcomes, got)
}
}
func TestSuggesterGeoIndex(t *testing.T) {
in := NewSuggesterGeoIndex("location").Locations(GeoPointFromLatLon(11.5, 62.71))
src, err := in.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"location":{"lat":11.5,"lon":62.71}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestSuggesterGeoIndexWithTwoValues(t *testing.T) {
in := NewSuggesterGeoIndex("location").Locations(GeoPointFromLatLon(11.5, 62.71), GeoPointFromLatLon(31.5, 22.71))
src, err := in.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"location":[{"lat":11.5,"lon":62.71},{"lat":31.5,"lon":22.71}]}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: suggester_context_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestContextSuggesterSource(t *testing.T) {
s := NewContextSuggester("place_suggestion").
Prefix("tim").
Field("suggest")
src, err := s.Source(true)
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"place_suggestion":{"prefix":"tim","completion":{"field":"suggest"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestContextSuggesterSourceWithMultipleContexts(t *testing.T) {
s := NewContextSuggester("place_suggestion").
Prefix("tim").
Field("suggest").
ContextQueries(
NewSuggesterCategoryQuery("place_type", "cafe", "restaurants"),
)
src, err := s.Source(true)
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
// Due to the randomization of dictionary key, we could actually have two different valid expected outcomes
expected := `{"place_suggestion":{"prefix":"tim","completion":{"contexts":{"place_type":[{"context":"cafe"},{"context":"restaurants"}]},"field":"suggest"}}}`
if got != expected {
expected := `{"place_suggestion":{"prefix":"tim","completion":{"contexts":{"place_type":[{"context":"restaurants"},{"context":"cafe"}]},"field":"suggest"}}}`
if got != expected {
t.Errorf("expected %s\n,got:\n%s", expected, got)
}
}
}
================================================
FILE: suggester_phrase.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// PhraseSuggester provides an API to access word alternatives
// on a per token basis within a certain string distance.
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-suggesters-phrase.html.
type PhraseSuggester struct {
Suggester
name string
text string
field string
analyzer string
size *int
shardSize *int
contextQueries []SuggesterContextQuery
// fields specific to a phrase suggester
maxErrors *float64
separator *string
realWordErrorLikelihood *float64
confidence *float64
generators map[string][]CandidateGenerator
gramSize *int
smoothingModel SmoothingModel
forceUnigrams *bool
tokenLimit *int
preTag, postTag *string
collateQuery *Script
collatePreference *string
collateParams map[string]interface{}
collatePrune *bool
}
// NewPhraseSuggester creates a new PhraseSuggester.
func NewPhraseSuggester(name string) *PhraseSuggester {
return &PhraseSuggester{
name: name,
collateParams: make(map[string]interface{}),
}
}
func (q *PhraseSuggester) Name() string {
return q.name
}
func (q *PhraseSuggester) Text(text string) *PhraseSuggester {
q.text = text
return q
}
func (q *PhraseSuggester) Field(field string) *PhraseSuggester {
q.field = field
return q
}
func (q *PhraseSuggester) Analyzer(analyzer string) *PhraseSuggester {
q.analyzer = analyzer
return q
}
func (q *PhraseSuggester) Size(size int) *PhraseSuggester {
q.size = &size
return q
}
func (q *PhraseSuggester) ShardSize(shardSize int) *PhraseSuggester {
q.shardSize = &shardSize
return q
}
func (q *PhraseSuggester) ContextQuery(query SuggesterContextQuery) *PhraseSuggester {
q.contextQueries = append(q.contextQueries, query)
return q
}
func (q *PhraseSuggester) ContextQueries(queries ...SuggesterContextQuery) *PhraseSuggester {
q.contextQueries = append(q.contextQueries, queries...)
return q
}
func (q *PhraseSuggester) GramSize(gramSize int) *PhraseSuggester {
if gramSize >= 1 {
q.gramSize = &gramSize
}
return q
}
func (q *PhraseSuggester) MaxErrors(maxErrors float64) *PhraseSuggester {
q.maxErrors = &maxErrors
return q
}
func (q *PhraseSuggester) Separator(separator string) *PhraseSuggester {
q.separator = &separator
return q
}
func (q *PhraseSuggester) RealWordErrorLikelihood(realWordErrorLikelihood float64) *PhraseSuggester {
q.realWordErrorLikelihood = &realWordErrorLikelihood
return q
}
func (q *PhraseSuggester) Confidence(confidence float64) *PhraseSuggester {
q.confidence = &confidence
return q
}
func (q *PhraseSuggester) CandidateGenerator(generator CandidateGenerator) *PhraseSuggester {
if q.generators == nil {
q.generators = make(map[string][]CandidateGenerator)
}
typ := generator.Type()
if _, found := q.generators[typ]; !found {
q.generators[typ] = make([]CandidateGenerator, 0)
}
q.generators[typ] = append(q.generators[typ], generator)
return q
}
func (q *PhraseSuggester) CandidateGenerators(generators ...CandidateGenerator) *PhraseSuggester {
for _, g := range generators {
q = q.CandidateGenerator(g)
}
return q
}
func (q *PhraseSuggester) ClearCandidateGenerator() *PhraseSuggester {
q.generators = nil
return q
}
func (q *PhraseSuggester) ForceUnigrams(forceUnigrams bool) *PhraseSuggester {
q.forceUnigrams = &forceUnigrams
return q
}
func (q *PhraseSuggester) SmoothingModel(smoothingModel SmoothingModel) *PhraseSuggester {
q.smoothingModel = smoothingModel
return q
}
func (q *PhraseSuggester) TokenLimit(tokenLimit int) *PhraseSuggester {
q.tokenLimit = &tokenLimit
return q
}
func (q *PhraseSuggester) Highlight(preTag, postTag string) *PhraseSuggester {
q.preTag = &preTag
q.postTag = &postTag
return q
}
func (q *PhraseSuggester) CollateQuery(collateQuery *Script) *PhraseSuggester {
q.collateQuery = collateQuery
return q
}
func (q *PhraseSuggester) CollatePreference(collatePreference string) *PhraseSuggester {
q.collatePreference = &collatePreference
return q
}
func (q *PhraseSuggester) CollateParams(collateParams map[string]interface{}) *PhraseSuggester {
q.collateParams = collateParams
return q
}
func (q *PhraseSuggester) CollatePrune(collatePrune bool) *PhraseSuggester {
q.collatePrune = &collatePrune
return q
}
// phraseSuggesterRequest is necessary because the order in which
// the JSON elements are routed to Elasticsearch is relevant.
// We got into trouble when using plain maps because the text element
// needs to go before the simple_phrase element.
type phraseSuggesterRequest struct {
Text string `json:"text"`
Phrase interface{} `json:"phrase"`
}
// Source generates the source for the phrase suggester.
func (q *PhraseSuggester) Source(includeName bool) (interface{}, error) {
ps := &phraseSuggesterRequest{}
if q.text != "" {
ps.Text = q.text
}
suggester := make(map[string]interface{})
ps.Phrase = suggester
if q.analyzer != "" {
suggester["analyzer"] = q.analyzer
}
if q.field != "" {
suggester["field"] = q.field
}
if q.size != nil {
suggester["size"] = *q.size
}
if q.shardSize != nil {
suggester["shard_size"] = *q.shardSize
}
switch len(q.contextQueries) {
case 0:
case 1:
src, err := q.contextQueries[0].Source()
if err != nil {
return nil, err
}
suggester["contexts"] = src
default:
var ctxq []interface{}
for _, query := range q.contextQueries {
src, err := query.Source()
if err != nil {
return nil, err
}
ctxq = append(ctxq, src)
}
suggester["contexts"] = ctxq
}
// Phase-specified parameters
if q.realWordErrorLikelihood != nil {
suggester["real_word_error_likelihood"] = *q.realWordErrorLikelihood
}
if q.confidence != nil {
suggester["confidence"] = *q.confidence
}
if q.separator != nil {
suggester["separator"] = *q.separator
}
if q.maxErrors != nil {
suggester["max_errors"] = *q.maxErrors
}
if q.gramSize != nil {
suggester["gram_size"] = *q.gramSize
}
if q.forceUnigrams != nil {
suggester["force_unigrams"] = *q.forceUnigrams
}
if q.tokenLimit != nil {
suggester["token_limit"] = *q.tokenLimit
}
if q.generators != nil && len(q.generators) > 0 {
for typ, generators := range q.generators {
var arr []interface{}
for _, g := range generators {
src, err := g.Source()
if err != nil {
return nil, err
}
arr = append(arr, src)
}
suggester[typ] = arr
}
}
if q.smoothingModel != nil {
src, err := q.smoothingModel.Source()
if err != nil {
return nil, err
}
x := make(map[string]interface{})
x[q.smoothingModel.Type()] = src
suggester["smoothing"] = x
}
if q.preTag != nil {
hl := make(map[string]string)
hl["pre_tag"] = *q.preTag
if q.postTag != nil {
hl["post_tag"] = *q.postTag
}
suggester["highlight"] = hl
}
if q.collateQuery != nil {
collate := make(map[string]interface{})
suggester["collate"] = collate
if q.collateQuery != nil {
src, err := q.collateQuery.Source()
if err != nil {
return nil, err
}
collate["query"] = src
}
if q.collatePreference != nil {
collate["preference"] = *q.collatePreference
}
if len(q.collateParams) > 0 {
collate["params"] = q.collateParams
}
if q.collatePrune != nil {
collate["prune"] = *q.collatePrune
}
}
if !includeName {
return ps, nil
}
source := make(map[string]interface{})
source[q.name] = ps
return source, nil
}
// -- Smoothing models --
type SmoothingModel interface {
Type() string
Source() (interface{}, error)
}
// StupidBackoffSmoothingModel implements a stupid backoff smoothing model.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-suggesters-phrase.html#_smoothing_models
// for details about smoothing models.
type StupidBackoffSmoothingModel struct {
discount float64
}
func NewStupidBackoffSmoothingModel(discount float64) *StupidBackoffSmoothingModel {
return &StupidBackoffSmoothingModel{
discount: discount,
}
}
func (sm *StupidBackoffSmoothingModel) Type() string {
return "stupid_backoff"
}
func (sm *StupidBackoffSmoothingModel) Source() (interface{}, error) {
source := make(map[string]interface{})
source["discount"] = sm.discount
return source, nil
}
// --
// LaplaceSmoothingModel implements a laplace smoothing model.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-suggesters-phrase.html#_smoothing_models
// for details about smoothing models.
type LaplaceSmoothingModel struct {
alpha float64
}
func NewLaplaceSmoothingModel(alpha float64) *LaplaceSmoothingModel {
return &LaplaceSmoothingModel{
alpha: alpha,
}
}
func (sm *LaplaceSmoothingModel) Type() string {
return "laplace"
}
func (sm *LaplaceSmoothingModel) Source() (interface{}, error) {
source := make(map[string]interface{})
source["alpha"] = sm.alpha
return source, nil
}
// --
// LinearInterpolationSmoothingModel implements a linear interpolation
// smoothing model.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-suggesters-phrase.html#_smoothing_models
// for details about smoothing models.
type LinearInterpolationSmoothingModel struct {
trigramLamda float64
bigramLambda float64
unigramLambda float64
}
func NewLinearInterpolationSmoothingModel(trigramLamda, bigramLambda, unigramLambda float64) *LinearInterpolationSmoothingModel {
return &LinearInterpolationSmoothingModel{
trigramLamda: trigramLamda,
bigramLambda: bigramLambda,
unigramLambda: unigramLambda,
}
}
func (sm *LinearInterpolationSmoothingModel) Type() string {
return "linear_interpolation"
}
func (sm *LinearInterpolationSmoothingModel) Source() (interface{}, error) {
source := make(map[string]interface{})
source["trigram_lambda"] = sm.trigramLamda
source["bigram_lambda"] = sm.bigramLambda
source["unigram_lambda"] = sm.unigramLambda
return source, nil
}
// -- CandidateGenerator --
type CandidateGenerator interface {
Type() string
Source() (interface{}, error)
}
// DirectCandidateGenerator implements a direct candidate generator.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-suggesters-phrase.html#_smoothing_models
// for details about smoothing models.
type DirectCandidateGenerator struct {
field string
preFilter *string
postFilter *string
suggestMode *string
accuracy *float64
size *int
sort *string
stringDistance *string
maxEdits *int
maxInspections *int
maxTermFreq *float64
prefixLength *int
minWordLength *int
minDocFreq *float64
}
func NewDirectCandidateGenerator(field string) *DirectCandidateGenerator {
return &DirectCandidateGenerator{
field: field,
}
}
func (g *DirectCandidateGenerator) Type() string {
return "direct_generator"
}
func (g *DirectCandidateGenerator) Field(field string) *DirectCandidateGenerator {
g.field = field
return g
}
func (g *DirectCandidateGenerator) PreFilter(preFilter string) *DirectCandidateGenerator {
g.preFilter = &preFilter
return g
}
func (g *DirectCandidateGenerator) PostFilter(postFilter string) *DirectCandidateGenerator {
g.postFilter = &postFilter
return g
}
func (g *DirectCandidateGenerator) SuggestMode(suggestMode string) *DirectCandidateGenerator {
g.suggestMode = &suggestMode
return g
}
func (g *DirectCandidateGenerator) Accuracy(accuracy float64) *DirectCandidateGenerator {
g.accuracy = &accuracy
return g
}
func (g *DirectCandidateGenerator) Size(size int) *DirectCandidateGenerator {
g.size = &size
return g
}
func (g *DirectCandidateGenerator) Sort(sort string) *DirectCandidateGenerator {
g.sort = &sort
return g
}
func (g *DirectCandidateGenerator) StringDistance(stringDistance string) *DirectCandidateGenerator {
g.stringDistance = &stringDistance
return g
}
func (g *DirectCandidateGenerator) MaxEdits(maxEdits int) *DirectCandidateGenerator {
g.maxEdits = &maxEdits
return g
}
func (g *DirectCandidateGenerator) MaxInspections(maxInspections int) *DirectCandidateGenerator {
g.maxInspections = &maxInspections
return g
}
func (g *DirectCandidateGenerator) MaxTermFreq(maxTermFreq float64) *DirectCandidateGenerator {
g.maxTermFreq = &maxTermFreq
return g
}
func (g *DirectCandidateGenerator) PrefixLength(prefixLength int) *DirectCandidateGenerator {
g.prefixLength = &prefixLength
return g
}
func (g *DirectCandidateGenerator) MinWordLength(minWordLength int) *DirectCandidateGenerator {
g.minWordLength = &minWordLength
return g
}
func (g *DirectCandidateGenerator) MinDocFreq(minDocFreq float64) *DirectCandidateGenerator {
g.minDocFreq = &minDocFreq
return g
}
func (g *DirectCandidateGenerator) Source() (interface{}, error) {
source := make(map[string]interface{})
if g.field != "" {
source["field"] = g.field
}
if g.suggestMode != nil {
source["suggest_mode"] = *g.suggestMode
}
if g.accuracy != nil {
source["accuracy"] = *g.accuracy
}
if g.size != nil {
source["size"] = *g.size
}
if g.sort != nil {
source["sort"] = *g.sort
}
if g.stringDistance != nil {
source["string_distance"] = *g.stringDistance
}
if g.maxEdits != nil {
source["max_edits"] = *g.maxEdits
}
if g.maxInspections != nil {
source["max_inspections"] = *g.maxInspections
}
if g.maxTermFreq != nil {
source["max_term_freq"] = *g.maxTermFreq
}
if g.prefixLength != nil {
source["prefix_length"] = *g.prefixLength
}
if g.minWordLength != nil {
source["min_word_length"] = *g.minWordLength
}
if g.minDocFreq != nil {
source["min_doc_freq"] = *g.minDocFreq
}
if g.preFilter != nil {
source["pre_filter"] = *g.preFilter
}
if g.postFilter != nil {
source["post_filter"] = *g.postFilter
}
return source, nil
}
================================================
FILE: suggester_phrase_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestPhraseSuggesterSource(t *testing.T) {
s := NewPhraseSuggester("name").
Text("Xor the Got-Jewel").
Analyzer("body").
Field("bigram").
Size(1).
RealWordErrorLikelihood(0.95).
MaxErrors(0.5).
GramSize(2).
Highlight("", "")
src, err := s.Source(true)
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"name":{"text":"Xor the Got-Jewel","phrase":{"analyzer":"body","field":"bigram","gram_size":2,"highlight":{"post_tag":"\u003c/em\u003e","pre_tag":"\u003cem\u003e"},"max_errors":0.5,"real_word_error_likelihood":0.95,"size":1}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestPhraseSuggesterSourceWithContextQuery(t *testing.T) {
geomapQ := NewSuggesterGeoMapping("location").
Precision("1km", "5m").
Neighbors(true).
FieldName("pin").
DefaultLocations(GeoPointFromLatLon(0.0, 0.0))
s := NewPhraseSuggester("name").
Text("Xor the Got-Jewel").
Analyzer("body").
Field("bigram").
Size(1).
RealWordErrorLikelihood(0.95).
MaxErrors(0.5).
GramSize(2).
Highlight("", "").
ContextQuery(geomapQ)
src, err := s.Source(true)
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"name":{"text":"Xor the Got-Jewel","phrase":{"analyzer":"body","contexts":{"location":{"default":{"lat":0,"lon":0},"neighbors":true,"path":"pin","precision":["1km","5m"],"type":"geo"}},"field":"bigram","gram_size":2,"highlight":{"post_tag":"\u003c/em\u003e","pre_tag":"\u003cem\u003e"},"max_errors":0.5,"real_word_error_likelihood":0.95,"size":1}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestPhraseSuggesterComplexSource(t *testing.T) {
g1 := NewDirectCandidateGenerator("body").
SuggestMode("always").
MinWordLength(1)
g2 := NewDirectCandidateGenerator("reverse").
SuggestMode("always").
MinWordLength(1).
PreFilter("reverse").
PostFilter("reverse")
s := NewPhraseSuggester("simple_phrase").
Text("Xor the Got-Jewel").
Analyzer("body").
Field("bigram").
Size(4).
RealWordErrorLikelihood(0.95).
Confidence(2.0).
GramSize(2).
CandidateGenerators(g1, g2).
CollateQuery(
NewScriptInline(`{"match":{"{{field_name}}" : "{{suggestion}}"}}`),
).
CollateParams(map[string]interface{}{"field_name": "title"}).
CollatePreference("_primary").
CollatePrune(true)
src, err := s.Source(true)
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"simple_phrase":{"text":"Xor the Got-Jewel","phrase":{"analyzer":"body","collate":{"params":{"field_name":"title"},"preference":"_primary","prune":true,"query":{"source":{"match":{"{{field_name}}":"{{suggestion}}"}}}},"confidence":2,"direct_generator":[{"field":"body","min_word_length":1,"suggest_mode":"always"},{"field":"reverse","min_word_length":1,"post_filter":"reverse","pre_filter":"reverse","suggest_mode":"always"}],"field":"bigram","gram_size":2,"real_word_error_likelihood":0.95,"size":4}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestPhraseStupidBackoffSmoothingModel(t *testing.T) {
s := NewStupidBackoffSmoothingModel(0.42)
src, err := s.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
// The source does NOT include the smoothing model type!
expected := `{"discount":0.42}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
if s.Type() != "stupid_backoff" {
t.Errorf("expected %q, got: %q", "stupid_backoff", s.Type())
}
}
func TestPhraseLaplaceSmoothingModel(t *testing.T) {
s := NewLaplaceSmoothingModel(0.63)
src, err := s.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
// The source does NOT include the smoothing model type!
expected := `{"alpha":0.63}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
if s.Type() != "laplace" {
t.Errorf("expected %q, got: %q", "laplace", s.Type())
}
}
func TestLinearInterpolationSmoothingModel(t *testing.T) {
s := NewLinearInterpolationSmoothingModel(0.3, 0.2, 0.05)
src, err := s.Source()
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
// The source does NOT include the smoothing model type!
expected := `{"bigram_lambda":0.2,"trigram_lambda":0.3,"unigram_lambda":0.05}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
if s.Type() != "linear_interpolation" {
t.Errorf("expected %q, got: %q", "linear_interpolation", s.Type())
}
}
func TestPhraseSuggesterSourceWithCollateQueryString(t *testing.T) {
s := NewPhraseSuggester("simple_phrase").
Text("noble price").
Field("title.trigram").
Size(1).
CandidateGenerator(
NewDirectCandidateGenerator("title.trigram").
SuggestMode("always").
MinWordLength(1),
).
CollateQuery(
NewScriptInline(`{"match":{"{{field_name}}":"{{suggestion}}"}}`),
).
CollateParams(map[string]interface{}{"field_name": "title"}).
CollatePrune(true)
src, err := s.Source(true)
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"simple_phrase":{"text":"noble price","phrase":{"collate":{"params":{"field_name":"title"},"prune":true,"query":{"source":{"match":{"{{field_name}}":"{{suggestion}}"}}}},"direct_generator":[{"field":"title.trigram","min_word_length":1,"suggest_mode":"always"}],"field":"title.trigram","size":1}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: suggester_term.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
// TermSuggester suggests terms based on edit distance.
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-suggesters-term.html.
type TermSuggester struct {
Suggester
name string
text string
field string
analyzer string
size *int
shardSize *int
contextQueries []SuggesterContextQuery
// fields specific to term suggester
suggestMode string
accuracy *float64
sort string
stringDistance string
maxEdits *int
maxInspections *int
maxTermFreq *float64
prefixLength *int
minWordLength *int
minDocFreq *float64
}
// NewTermSuggester creates a new TermSuggester.
func NewTermSuggester(name string) *TermSuggester {
return &TermSuggester{
name: name,
}
}
func (q *TermSuggester) Name() string {
return q.name
}
func (q *TermSuggester) Text(text string) *TermSuggester {
q.text = text
return q
}
func (q *TermSuggester) Field(field string) *TermSuggester {
q.field = field
return q
}
func (q *TermSuggester) Analyzer(analyzer string) *TermSuggester {
q.analyzer = analyzer
return q
}
func (q *TermSuggester) Size(size int) *TermSuggester {
q.size = &size
return q
}
func (q *TermSuggester) ShardSize(shardSize int) *TermSuggester {
q.shardSize = &shardSize
return q
}
func (q *TermSuggester) ContextQuery(query SuggesterContextQuery) *TermSuggester {
q.contextQueries = append(q.contextQueries, query)
return q
}
func (q *TermSuggester) ContextQueries(queries ...SuggesterContextQuery) *TermSuggester {
q.contextQueries = append(q.contextQueries, queries...)
return q
}
func (q *TermSuggester) SuggestMode(suggestMode string) *TermSuggester {
q.suggestMode = suggestMode
return q
}
func (q *TermSuggester) Accuracy(accuracy float64) *TermSuggester {
q.accuracy = &accuracy
return q
}
func (q *TermSuggester) Sort(sort string) *TermSuggester {
q.sort = sort
return q
}
func (q *TermSuggester) StringDistance(stringDistance string) *TermSuggester {
q.stringDistance = stringDistance
return q
}
func (q *TermSuggester) MaxEdits(maxEdits int) *TermSuggester {
q.maxEdits = &maxEdits
return q
}
func (q *TermSuggester) MaxInspections(maxInspections int) *TermSuggester {
q.maxInspections = &maxInspections
return q
}
func (q *TermSuggester) MaxTermFreq(maxTermFreq float64) *TermSuggester {
q.maxTermFreq = &maxTermFreq
return q
}
func (q *TermSuggester) PrefixLength(prefixLength int) *TermSuggester {
q.prefixLength = &prefixLength
return q
}
func (q *TermSuggester) MinWordLength(minWordLength int) *TermSuggester {
q.minWordLength = &minWordLength
return q
}
func (q *TermSuggester) MinDocFreq(minDocFreq float64) *TermSuggester {
q.minDocFreq = &minDocFreq
return q
}
// termSuggesterRequest is necessary because the order in which
// the JSON elements are routed to Elasticsearch is relevant.
// We got into trouble when using plain maps because the text element
// needs to go before the term element.
type termSuggesterRequest struct {
Text string `json:"text"`
Term interface{} `json:"term"`
}
// Source generates the source for the term suggester.
func (q *TermSuggester) Source(includeName bool) (interface{}, error) {
// "suggest" : {
// "my-suggest-1" : {
// "text" : "the amsterdma meetpu",
// "term" : {
// "field" : "body"
// }
// },
// "my-suggest-2" : {
// "text" : "the rottredam meetpu",
// "term" : {
// "field" : "title",
// }
// }
// }
ts := &termSuggesterRequest{}
if q.text != "" {
ts.Text = q.text
}
suggester := make(map[string]interface{})
ts.Term = suggester
if q.analyzer != "" {
suggester["analyzer"] = q.analyzer
}
if q.field != "" {
suggester["field"] = q.field
}
if q.size != nil {
suggester["size"] = *q.size
}
if q.shardSize != nil {
suggester["shard_size"] = *q.shardSize
}
switch len(q.contextQueries) {
case 0:
case 1:
src, err := q.contextQueries[0].Source()
if err != nil {
return nil, err
}
suggester["contexts"] = src
default:
ctxq := make([]interface{}, len(q.contextQueries))
for i, query := range q.contextQueries {
src, err := query.Source()
if err != nil {
return nil, err
}
ctxq[i] = src
}
suggester["contexts"] = ctxq
}
// Specific to term suggester
if q.suggestMode != "" {
suggester["suggest_mode"] = q.suggestMode
}
if q.accuracy != nil {
suggester["accuracy"] = *q.accuracy
}
if q.sort != "" {
suggester["sort"] = q.sort
}
if q.stringDistance != "" {
suggester["string_distance"] = q.stringDistance
}
if q.maxEdits != nil {
suggester["max_edits"] = *q.maxEdits
}
if q.maxInspections != nil {
suggester["max_inspections"] = *q.maxInspections
}
if q.maxTermFreq != nil {
suggester["max_term_freq"] = *q.maxTermFreq
}
if q.prefixLength != nil {
suggester["prefix_length"] = *q.prefixLength
}
if q.minWordLength != nil {
suggester["min_word_length"] = *q.minWordLength
}
if q.minDocFreq != nil {
suggester["min_doc_freq"] = *q.minDocFreq
}
if !includeName {
return ts, nil
}
source := make(map[string]interface{})
source[q.name] = ts
return source, nil
}
================================================
FILE: suggester_term_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
)
func TestTermSuggesterSource(t *testing.T) {
s := NewTermSuggester("name").
Text("n").
Field("suggest")
src, err := s.Source(true)
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"name":{"text":"n","term":{"field":"suggest"}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
func TestTermSuggesterWithPrefixLengthSource(t *testing.T) {
s := NewTermSuggester("name").
Text("n").
Field("suggest").
PrefixLength(0)
src, err := s.Source(true)
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(src)
if err != nil {
t.Fatalf("marshaling to JSON failed: %v", err)
}
got := string(data)
expected := `{"name":{"text":"n","term":{"field":"suggest","prefix_length":0}}}`
if got != expected {
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
}
}
================================================
FILE: tasks_cancel.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// TasksCancelService can cancel long-running tasks.
// It is supported as of Elasticsearch 2.3.0.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/tasks.html#task-cancellation
// for details.
type TasksCancelService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
taskId string
actions []string
nodeId []string
parentTaskId string
}
// NewTasksCancelService creates a new TasksCancelService.
func NewTasksCancelService(client *Client) *TasksCancelService {
return &TasksCancelService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *TasksCancelService) Pretty(pretty bool) *TasksCancelService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *TasksCancelService) Human(human bool) *TasksCancelService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *TasksCancelService) ErrorTrace(errorTrace bool) *TasksCancelService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *TasksCancelService) FilterPath(filterPath ...string) *TasksCancelService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *TasksCancelService) Header(name string, value string) *TasksCancelService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *TasksCancelService) Headers(headers http.Header) *TasksCancelService {
s.headers = headers
return s
}
// TaskId specifies the task to cancel. Notice that the caller is responsible
// for using the correct format, i.e. node_id:task_number, as specified in
// the REST API.
func (s *TasksCancelService) TaskId(taskId string) *TasksCancelService {
s.taskId = taskId
return s
}
// TaskIdFromNodeAndId specifies the task to cancel. Set id to -1 for all tasks.
func (s *TasksCancelService) TaskIdFromNodeAndId(nodeId string, id int64) *TasksCancelService {
if id != -1 {
s.taskId = fmt.Sprintf("%s:%d", nodeId, id)
}
return s
}
// Actions is a list of actions that should be cancelled. Leave empty to cancel all.
func (s *TasksCancelService) Actions(actions ...string) *TasksCancelService {
s.actions = append(s.actions, actions...)
return s
}
// NodeId is a list of node IDs or names to limit the returned information;
// use `_local` to return information from the node you're connecting to,
// leave empty to get information from all nodes.
func (s *TasksCancelService) NodeId(nodeId ...string) *TasksCancelService {
s.nodeId = append(s.nodeId, nodeId...)
return s
}
// ParentTaskId specifies to cancel tasks with specified parent task id.
// Notice that the caller is responsible for using the correct format,
// i.e. node_id:task_number, as specified in the REST API.
func (s *TasksCancelService) ParentTaskId(parentTaskId string) *TasksCancelService {
s.parentTaskId = parentTaskId
return s
}
// ParentTaskIdFromNodeAndId specifies to cancel tasks with specified parent task id.
func (s *TasksCancelService) ParentTaskIdFromNodeAndId(nodeId string, id int64) *TasksCancelService {
if id != -1 {
s.parentTaskId = fmt.Sprintf("%s:%d", nodeId, id)
}
return s
}
// buildURL builds the URL for the operation.
func (s *TasksCancelService) buildURL() (string, url.Values, error) {
// Build URL
var err error
var path string
if s.taskId != "" {
path, err = uritemplates.Expand("/_tasks/{task_id}/_cancel", map[string]string{
"task_id": s.taskId,
})
} else {
path = "/_tasks/_cancel"
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if len(s.actions) > 0 {
params.Set("actions", strings.Join(s.actions, ","))
}
if len(s.nodeId) > 0 {
params.Set("nodes", strings.Join(s.nodeId, ","))
}
if s.parentTaskId != "" {
params.Set("parent_task_id", s.parentTaskId)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *TasksCancelService) Validate() error {
return nil
}
// Do executes the operation.
func (s *TasksCancelService) Do(ctx context.Context) (*TasksListResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "POST",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(TasksListResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
================================================
FILE: tasks_cancel_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import "testing"
func TestTasksCancelBuildURL(t *testing.T) {
client := setupTestClient(t)
// Cancel all
got, _, err := client.TasksCancel().buildURL()
if err != nil {
t.Fatal(err)
}
want := "/_tasks/_cancel"
if got != want {
t.Errorf("want %q; got %q", want, got)
}
// Cancel all with empty TaskId
got, _, err = client.TasksCancel().TaskId("").buildURL()
if err != nil {
t.Fatal(err)
}
want = "/_tasks/_cancel"
if got != want {
t.Errorf("want %q; got %q", want, got)
}
// Cancel all with invalid TaskId
got, _, err = client.TasksCancel().TaskId("invalid").buildURL()
if err != nil {
t.Fatal(err)
}
want = "/_tasks/invalid/_cancel"
if got != want {
t.Errorf("want %q; got %q", want, got)
}
// Cancel all with id set to -1
got, _, err = client.TasksCancel().TaskIdFromNodeAndId("", -1).buildURL()
if err != nil {
t.Fatal(err)
}
want = "/_tasks/_cancel"
if got != want {
t.Errorf("want %q; got %q", want, got)
}
// Cancel specific task
got, _, err = client.TasksCancel().TaskIdFromNodeAndId("abc", 42).buildURL()
if err != nil {
t.Fatal(err)
}
want = "/_tasks/abc%3A42/_cancel"
if got != want {
t.Errorf("want %q; got %q", want, got)
}
}
/*
func TestTasksCancel(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t)
esversion, err := client.ElasticsearchVersion(DefaultURL)
if err != nil {
t.Fatal(err)
}
if esversion < "2.3.0" {
t.Skipf("Elasticsearch %v does not support Tasks Management API yet", esversion)
}
res, err := client.TasksCancel("1").Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("response is nil")
}
}
*/
================================================
FILE: tasks_get_task.go
================================================
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// TasksGetTaskService retrieves the state of a task in the cluster. It is part of the Task Management API
// documented at https://www.elastic.co/guide/en/elasticsearch/reference/7.0/tasks.html#_current_tasks_information.
type TasksGetTaskService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
taskId string
waitForCompletion *bool
}
// NewTasksGetTaskService creates a new TasksGetTaskService.
func NewTasksGetTaskService(client *Client) *TasksGetTaskService {
return &TasksGetTaskService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *TasksGetTaskService) Pretty(pretty bool) *TasksGetTaskService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *TasksGetTaskService) Human(human bool) *TasksGetTaskService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *TasksGetTaskService) ErrorTrace(errorTrace bool) *TasksGetTaskService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *TasksGetTaskService) FilterPath(filterPath ...string) *TasksGetTaskService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *TasksGetTaskService) Header(name string, value string) *TasksGetTaskService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *TasksGetTaskService) Headers(headers http.Header) *TasksGetTaskService {
s.headers = headers
return s
}
// TaskId specifies the task to return. Notice that the caller is responsible
// for using the correct format, i.e. node_id:task_number, as specified in
// the REST API.
func (s *TasksGetTaskService) TaskId(taskId string) *TasksGetTaskService {
s.taskId = taskId
return s
}
// TaskIdFromNodeAndId indicates to return the task on the given node with specified id.
func (s *TasksGetTaskService) TaskIdFromNodeAndId(nodeId string, id int64) *TasksGetTaskService {
s.taskId = fmt.Sprintf("%s:%d", nodeId, id)
return s
}
// WaitForCompletion indicates whether to wait for the matching tasks
// to complete (default: false).
func (s *TasksGetTaskService) WaitForCompletion(waitForCompletion bool) *TasksGetTaskService {
s.waitForCompletion = &waitForCompletion
return s
}
// buildURL builds the URL for the operation.
func (s *TasksGetTaskService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/_tasks/{task_id}", map[string]string{
"task_id": s.taskId,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if v := s.waitForCompletion; v != nil {
params.Set("wait_for_completion", fmt.Sprint(*v))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *TasksGetTaskService) Validate() error {
return nil
}
// Do executes the operation.
func (s *TasksGetTaskService) Do(ctx context.Context) (*TasksGetTaskResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(TasksGetTaskResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
ret.Header = res.Header
return ret, nil
}
type TasksGetTaskResponse struct {
Header http.Header `json:"-"`
Completed bool `json:"completed"`
Task *TaskInfo `json:"task,omitempty"`
Error *ErrorDetails `json:"error,omitempty"`
}
================================================
FILE: tasks_get_task_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"errors"
"testing"
"time"
)
func TestTasksGetTaskBuildURL(t *testing.T) {
client := setupTestClient(t)
// Get specific task
got, _, err := client.TasksGetTask().TaskId("node1:123").buildURL()
if err != nil {
t.Fatal(err)
}
want := "/_tasks/node1%3A123"
if got != want {
t.Errorf("want %q; got %q", want, got)
}
// Get specific task
got, _, err = client.TasksGetTask().TaskIdFromNodeAndId("node2", 678).buildURL()
if err != nil {
t.Fatal(err)
}
want = "/_tasks/node2%3A678"
if got != want {
t.Errorf("want %q; got %q", want, got)
}
}
func TestTasksGetTask(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t) //, SetTraceLog(log.New(os.Stdout, "", 0)))
// Create a reindexing task
var taskID string
{
res, err := client.Reindex().
SourceIndex(testIndexName).
DestinationIndex(testIndexName4).
DoAsync(context.Background())
if err != nil {
t.Fatalf("unable to start reindexing task: %v", err)
}
taskID = res.TaskId
}
// Get the task by ID
res, err := client.TasksGetTask().
TaskId(taskID).
Header("X-Opaque-Id", "987654").
Do(context.Background())
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("response is nil")
}
if want, have := "987654", res.Header.Get("X-Opaque-Id"); want != have {
t.Fatalf("expected HTTP header %#v; got: %#v", want, have)
}
if res.Task == nil {
t.Fatal("task is nil")
}
// Elasticsearch <= 6.4.1 doesn't return the X-Opaque-Id in the body,
// only in response header.
/*
have, found := res.Task.Headers["X-Opaque-Id"]
if !found {
t.Fatalf("expected to find headers[%q]", "X-Opaque-Id")
}
if want := "987654"; want != have {
t.Fatalf("expected headers[%q]=%q; got: %q", "X-Opaque-Id", want, have)
}
*/
}
func TestTasksGetTaskWithError(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t) //, SetTraceLog(log.New(os.Stdout, "", 0)))
// Create a reindexing task
var taskID string
{
res, err := client.UpdateByQuery(testIndexName).
WaitForCompletion(false).
Conflicts("proceed").
Script(NewScript("kaboom")).
DoAsync(context.Background())
if err != nil {
t.Fatalf("unable to start update_by_query task: %v", err)
}
taskID = res.TaskId
}
var (
response *TasksGetTaskResponse
lastErr error
)
done := make(chan struct{}, 1)
go func() {
defer close(done)
for {
// Get the task by ID
res, err := client.TasksGetTask().
TaskId(taskID).
Do(context.Background())
if err != nil {
lastErr = err
return
}
if res == nil {
lastErr = errors.New("response is nil")
return
}
if res.Completed {
lastErr = nil
response = res
return
}
time.Sleep(1 * time.Second) // retry
}
}()
select {
case <-done:
case <-time.After(5 * time.Second):
t.Fatal("expected to finish the task after 5 seconds")
}
if lastErr != nil {
t.Fatalf("expected no error, got %v", lastErr)
}
if response == nil {
t.Fatal("expected a response, got nil")
}
if response.Error == nil {
t.Fatal("expected a response with an error, got nil")
}
if want, have := "script_exception", response.Error.Type; want != have {
t.Fatalf("expected an error type of %q, got %q", want, have)
}
}
================================================
FILE: tasks_list.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// TasksListService retrieves the list of currently executing tasks
// on one ore more nodes in the cluster. It is part of the Task Management API
// documented at https://www.elastic.co/guide/en/elasticsearch/reference/7.0/tasks.html.
//
// It is supported as of Elasticsearch 2.3.0.
type TasksListService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
taskId []string
actions []string
detailed *bool
nodeId []string
parentTaskId string
waitForCompletion *bool
groupBy string
}
// NewTasksListService creates a new TasksListService.
func NewTasksListService(client *Client) *TasksListService {
return &TasksListService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *TasksListService) Pretty(pretty bool) *TasksListService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *TasksListService) Human(human bool) *TasksListService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *TasksListService) ErrorTrace(errorTrace bool) *TasksListService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *TasksListService) FilterPath(filterPath ...string) *TasksListService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *TasksListService) Header(name string, value string) *TasksListService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *TasksListService) Headers(headers http.Header) *TasksListService {
s.headers = headers
return s
}
// TaskId indicates to returns the task(s) with specified id(s).
// Notice that the caller is responsible for using the correct format,
// i.e. node_id:task_number, as specified in the REST API.
func (s *TasksListService) TaskId(taskId ...string) *TasksListService {
s.taskId = append(s.taskId, taskId...)
return s
}
// Actions is a list of actions that should be returned. Leave empty to return all.
func (s *TasksListService) Actions(actions ...string) *TasksListService {
s.actions = append(s.actions, actions...)
return s
}
// Detailed indicates whether to return detailed task information (default: false).
func (s *TasksListService) Detailed(detailed bool) *TasksListService {
s.detailed = &detailed
return s
}
// NodeId is a list of node IDs or names to limit the returned information;
// use `_local` to return information from the node you're connecting to,
// leave empty to get information from all nodes.
func (s *TasksListService) NodeId(nodeId ...string) *TasksListService {
s.nodeId = append(s.nodeId, nodeId...)
return s
}
// ParentTaskId returns tasks with specified parent task id.
// Notice that the caller is responsible for using the correct format,
// i.e. node_id:task_number, as specified in the REST API.
func (s *TasksListService) ParentTaskId(parentTaskId string) *TasksListService {
s.parentTaskId = parentTaskId
return s
}
// WaitForCompletion indicates whether to wait for the matching tasks
// to complete (default: false).
func (s *TasksListService) WaitForCompletion(waitForCompletion bool) *TasksListService {
s.waitForCompletion = &waitForCompletion
return s
}
// GroupBy groups tasks by nodes or parent/child relationships.
// As of now, it can either be "nodes" (default) or "parents" or "none".
func (s *TasksListService) GroupBy(groupBy string) *TasksListService {
s.groupBy = groupBy
return s
}
// buildURL builds the URL for the operation.
func (s *TasksListService) buildURL() (string, url.Values, error) {
// Build URL
var err error
var path string
if len(s.taskId) > 0 {
path, err = uritemplates.Expand("/_tasks/{task_id}", map[string]string{
"task_id": strings.Join(s.taskId, ","),
})
} else {
path = "/_tasks"
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if len(s.actions) > 0 {
params.Set("actions", strings.Join(s.actions, ","))
}
if v := s.detailed; v != nil {
params.Set("detailed", fmt.Sprint(*v))
}
if len(s.nodeId) > 0 {
params.Set("nodes", strings.Join(s.nodeId, ","))
}
if s.parentTaskId != "" {
params.Set("parent_task_id", s.parentTaskId)
}
if v := s.waitForCompletion; v != nil {
params.Set("wait_for_completion", fmt.Sprint(*v))
}
if s.groupBy != "" {
params.Set("group_by", s.groupBy)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *TasksListService) Validate() error {
return nil
}
// Do executes the operation.
func (s *TasksListService) Do(ctx context.Context) (*TasksListResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(TasksListResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
ret.Header = res.Header
return ret, nil
}
// TasksListResponse is the response of TasksListService.Do.
type TasksListResponse struct {
Header http.Header `json:"-"`
TaskFailures []*TaskOperationFailure `json:"task_failures"`
NodeFailures []*FailedNodeException `json:"node_failures"`
// Nodes returns the tasks per node. The key is the node id.
Nodes map[string]*DiscoveryNode `json:"nodes"`
}
type TaskOperationFailure struct {
TaskId int64 `json:"task_id"` // this is a long in the Java source
NodeId string `json:"node_id"`
Status string `json:"status"`
Reason *ErrorDetails `json:"reason"`
}
type DiscoveryNode struct {
Name string `json:"name"`
TransportAddress string `json:"transport_address"`
Host string `json:"host"`
IP string `json:"ip"`
Roles []string `json:"roles"` // "master", "data", or "ingest"
Attributes map[string]interface{} `json:"attributes"`
// Tasks returns the tasks by its id (as a string).
Tasks map[string]*TaskInfo `json:"tasks"`
}
// TaskInfo represents information about a currently running task.
type TaskInfo struct {
Node string `json:"node"`
Id int64 `json:"id"` // the task id (yes, this is a long in the Java source)
Type string `json:"type"`
Action string `json:"action"`
Status interface{} `json:"status"` // has separate implementations of Task.Status in Java for reindexing, replication, and "RawTaskStatus"
Description interface{} `json:"description"` // same as Status
StartTime string `json:"start_time"`
StartTimeInMillis int64 `json:"start_time_in_millis"`
RunningTime string `json:"running_time"`
RunningTimeInNanos int64 `json:"running_time_in_nanos"`
Cancellable bool `json:"cancellable"`
Cancelled bool `json:"cancelled"`
ParentTaskId string `json:"parent_task_id"` // like "YxJnVYjwSBm_AUbzddTajQ:12356"
Headers map[string]string `json:"headers"`
}
// StartTaskResult is used in cases where a task gets started asynchronously and
// the operation simply returnes a TaskID to watch for via the Task Management API.
type StartTaskResult struct {
Header http.Header `json:"-"`
TaskId string `json:"task"`
}
================================================
FILE: tasks_list_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestTasksListBuildURL(t *testing.T) {
client := setupTestClient(t)
tests := []struct {
TaskId []string
Expected string
}{
{
TaskId: []string{},
Expected: "/_tasks",
},
{
TaskId: []string{"node1:42"},
Expected: "/_tasks/node1%3A42",
},
{
TaskId: []string{"node1:42", "node2:37"},
Expected: "/_tasks/node1%3A42%2Cnode2%3A37",
},
}
for i, tt := range tests {
path, _, err := client.TasksList().
TaskId(tt.TaskId...).
buildURL()
if err != nil {
t.Errorf("case #%d: %v", i+1, err)
continue
}
if path != tt.Expected {
t.Errorf("case #%d: expected %q; got: %q", i+1, tt.Expected, path)
}
}
}
func TestTasksList(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t) //, SetTraceLog(log.New(os.Stdout, "", 0)))
res, err := client.TasksList().
Pretty(true).
Human(true).
Header("X-Opaque-Id", "123456").
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("response is nil")
}
if len(res.Nodes) == 0 {
t.Fatalf("expected at least 1 node; got: %d", len(res.Nodes))
}
if want, have := "123456", res.Header.Get("X-Opaque-Id"); want != have {
t.Fatalf("expected HTTP header %#v; got: %#v", want, have)
}
for _, node := range res.Nodes {
if len(node.Tasks) == 0 {
t.Fatalf("expected at least 1 task; got: %d", len(node.Tasks))
}
for _, task := range node.Tasks {
have, found := task.Headers["X-Opaque-Id"]
if !found {
t.Logf("flaky test: expected to find headers[%q]", "X-Opaque-Id")
} else if want := "123456"; want != have {
t.Fatalf("expected headers[%q]=%q; got: %q", "X-Opaque-Id", want, have)
}
}
}
}
================================================
FILE: termvectors.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// TermvectorsService returns information and statistics on terms in the
// fields of a particular document. The document could be stored in the
// index or artificially provided by the user.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-termvectors.html
// for documentation.
type TermvectorsService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
id string
index string
typ string
dfs *bool
doc interface{}
fieldStatistics *bool
fields []string
filter *TermvectorsFilterSettings
perFieldAnalyzer map[string]string
offsets *bool
parent string
payloads *bool
positions *bool
preference string
realtime *bool
routing string
termStatistics *bool
version interface{}
versionType string
bodyJson interface{}
bodyString string
}
// NewTermvectorsService creates a new TermvectorsService.
func NewTermvectorsService(client *Client) *TermvectorsService {
return &TermvectorsService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *TermvectorsService) Pretty(pretty bool) *TermvectorsService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *TermvectorsService) Human(human bool) *TermvectorsService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *TermvectorsService) ErrorTrace(errorTrace bool) *TermvectorsService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *TermvectorsService) FilterPath(filterPath ...string) *TermvectorsService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *TermvectorsService) Header(name string, value string) *TermvectorsService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *TermvectorsService) Headers(headers http.Header) *TermvectorsService {
s.headers = headers
return s
}
// Index in which the document resides.
func (s *TermvectorsService) Index(index string) *TermvectorsService {
s.index = index
return s
}
// Type of the document.
//
// Deprecated: Types are in the process of being removed.
func (s *TermvectorsService) Type(typ string) *TermvectorsService {
s.typ = typ
return s
}
// Id of the document.
func (s *TermvectorsService) Id(id string) *TermvectorsService {
s.id = id
return s
}
// Dfs specifies if distributed frequencies should be returned instead
// shard frequencies.
func (s *TermvectorsService) Dfs(dfs bool) *TermvectorsService {
s.dfs = &dfs
return s
}
// Doc is the document to analyze.
func (s *TermvectorsService) Doc(doc interface{}) *TermvectorsService {
s.doc = doc
return s
}
// FieldStatistics specifies if document count, sum of document frequencies
// and sum of total term frequencies should be returned.
func (s *TermvectorsService) FieldStatistics(fieldStatistics bool) *TermvectorsService {
s.fieldStatistics = &fieldStatistics
return s
}
// Fields a list of fields to return.
func (s *TermvectorsService) Fields(fields ...string) *TermvectorsService {
if s.fields == nil {
s.fields = make([]string, 0)
}
s.fields = append(s.fields, fields...)
return s
}
// Filter adds terms filter settings.
func (s *TermvectorsService) Filter(filter *TermvectorsFilterSettings) *TermvectorsService {
s.filter = filter
return s
}
// PerFieldAnalyzer allows to specify a different analyzer than the one
// at the field.
func (s *TermvectorsService) PerFieldAnalyzer(perFieldAnalyzer map[string]string) *TermvectorsService {
s.perFieldAnalyzer = perFieldAnalyzer
return s
}
// Offsets specifies if term offsets should be returned.
func (s *TermvectorsService) Offsets(offsets bool) *TermvectorsService {
s.offsets = &offsets
return s
}
// Parent id of documents.
func (s *TermvectorsService) Parent(parent string) *TermvectorsService {
s.parent = parent
return s
}
// Payloads specifies if term payloads should be returned.
func (s *TermvectorsService) Payloads(payloads bool) *TermvectorsService {
s.payloads = &payloads
return s
}
// Positions specifies if term positions should be returned.
func (s *TermvectorsService) Positions(positions bool) *TermvectorsService {
s.positions = &positions
return s
}
// Preference specify the node or shard the operation
// should be performed on (default: random).
func (s *TermvectorsService) Preference(preference string) *TermvectorsService {
s.preference = preference
return s
}
// Realtime specifies if request is real-time as opposed to
// near-real-time (default: true).
func (s *TermvectorsService) Realtime(realtime bool) *TermvectorsService {
s.realtime = &realtime
return s
}
// Routing is a specific routing value.
func (s *TermvectorsService) Routing(routing string) *TermvectorsService {
s.routing = routing
return s
}
// TermStatistics specifies if total term frequency and document frequency
// should be returned.
func (s *TermvectorsService) TermStatistics(termStatistics bool) *TermvectorsService {
s.termStatistics = &termStatistics
return s
}
// Version an explicit version number for concurrency control.
func (s *TermvectorsService) Version(version interface{}) *TermvectorsService {
s.version = version
return s
}
// VersionType specifies a version type ("internal", "external", or "external_gte").
func (s *TermvectorsService) VersionType(versionType string) *TermvectorsService {
s.versionType = versionType
return s
}
// BodyJson defines the body parameters. See documentation.
func (s *TermvectorsService) BodyJson(body interface{}) *TermvectorsService {
s.bodyJson = body
return s
}
// BodyString defines the body parameters as a string. See documentation.
func (s *TermvectorsService) BodyString(body string) *TermvectorsService {
s.bodyString = body
return s
}
// buildURL builds the URL for the operation.
func (s *TermvectorsService) buildURL() (string, url.Values, error) {
var pathParam = map[string]string{
"index": s.index,
}
path := "/{index}"
var err error
if s.typ != "" {
pathParam["type"] = s.typ
path += "/{type}"
} else {
path += "/_termvectors"
}
if s.id != "" {
pathParam["id"] = s.id
path += "/{id}"
}
if s.typ != "" {
path += "/_termvectors"
}
path, err = uritemplates.Expand(path, pathParam)
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if v := s.dfs; v != nil {
params.Set("dfs", fmt.Sprint(*v))
}
if v := s.fieldStatistics; v != nil {
params.Set("field_statistics", fmt.Sprint(*v))
}
if len(s.fields) > 0 {
params.Set("fields", strings.Join(s.fields, ","))
}
if v := s.offsets; v != nil {
params.Set("offsets", fmt.Sprint(*v))
}
if s.parent != "" {
params.Set("parent", s.parent)
}
if v := s.payloads; v != nil {
params.Set("payloads", fmt.Sprint(*v))
}
if v := s.positions; v != nil {
params.Set("positions", fmt.Sprint(*v))
}
if s.preference != "" {
params.Set("preference", s.preference)
}
if v := s.realtime; v != nil {
params.Set("realtime", fmt.Sprint(*v))
}
if s.routing != "" {
params.Set("routing", s.routing)
}
if v := s.termStatistics; v != nil {
params.Set("term_statistics", fmt.Sprint(*v))
}
if s.version != nil {
params.Set("version", fmt.Sprintf("%v", s.version))
}
if s.versionType != "" {
params.Set("version_type", s.versionType)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *TermvectorsService) Validate() error {
var invalid []string
if s.index == "" {
invalid = append(invalid, "Index")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *TermvectorsService) Do(ctx context.Context) (*TermvectorsResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Setup HTTP request body
var body interface{}
if s.bodyJson != nil {
body = s.bodyJson
} else if s.bodyString != "" {
body = s.bodyString
} else {
data := make(map[string]interface{})
if s.doc != nil {
data["doc"] = s.doc
}
if len(s.perFieldAnalyzer) > 0 {
data["per_field_analyzer"] = s.perFieldAnalyzer
}
if s.filter != nil {
src, err := s.filter.Source()
if err != nil {
return nil, err
}
data["filter"] = src
}
if len(data) > 0 {
body = data
}
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Body: body,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(TermvectorsResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// -- Filter settings --
// TermvectorsFilterSettings adds additional filters to a Termsvector request.
// It allows to filter terms based on their tf-idf scores.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-termvectors.html#_terms_filtering
// for more information.
type TermvectorsFilterSettings struct {
maxNumTerms *int64
minTermFreq *int64
maxTermFreq *int64
minDocFreq *int64
maxDocFreq *int64
minWordLength *int64
maxWordLength *int64
}
// NewTermvectorsFilterSettings creates and initializes a new TermvectorsFilterSettings struct.
func NewTermvectorsFilterSettings() *TermvectorsFilterSettings {
return &TermvectorsFilterSettings{}
}
// MaxNumTerms specifies the maximum number of terms the must be returned per field.
func (fs *TermvectorsFilterSettings) MaxNumTerms(value int64) *TermvectorsFilterSettings {
fs.maxNumTerms = &value
return fs
}
// MinTermFreq ignores words with less than this frequency in the source doc.
func (fs *TermvectorsFilterSettings) MinTermFreq(value int64) *TermvectorsFilterSettings {
fs.minTermFreq = &value
return fs
}
// MaxTermFreq ignores words with more than this frequency in the source doc.
func (fs *TermvectorsFilterSettings) MaxTermFreq(value int64) *TermvectorsFilterSettings {
fs.maxTermFreq = &value
return fs
}
// MinDocFreq ignores terms which do not occur in at least this many docs.
func (fs *TermvectorsFilterSettings) MinDocFreq(value int64) *TermvectorsFilterSettings {
fs.minDocFreq = &value
return fs
}
// MaxDocFreq ignores terms which occur in more than this many docs.
func (fs *TermvectorsFilterSettings) MaxDocFreq(value int64) *TermvectorsFilterSettings {
fs.maxDocFreq = &value
return fs
}
// MinWordLength specifies the minimum word length below which words will be ignored.
func (fs *TermvectorsFilterSettings) MinWordLength(value int64) *TermvectorsFilterSettings {
fs.minWordLength = &value
return fs
}
// MaxWordLength specifies the maximum word length above which words will be ignored.
func (fs *TermvectorsFilterSettings) MaxWordLength(value int64) *TermvectorsFilterSettings {
fs.maxWordLength = &value
return fs
}
// Source returns JSON for the query.
func (fs *TermvectorsFilterSettings) Source() (interface{}, error) {
source := make(map[string]interface{})
if fs.maxNumTerms != nil {
source["max_num_terms"] = *fs.maxNumTerms
}
if fs.minTermFreq != nil {
source["min_term_freq"] = *fs.minTermFreq
}
if fs.maxTermFreq != nil {
source["max_term_freq"] = *fs.maxTermFreq
}
if fs.minDocFreq != nil {
source["min_doc_freq"] = *fs.minDocFreq
}
if fs.maxDocFreq != nil {
source["max_doc_freq"] = *fs.maxDocFreq
}
if fs.minWordLength != nil {
source["min_word_length"] = *fs.minWordLength
}
if fs.maxWordLength != nil {
source["max_word_length"] = *fs.maxWordLength
}
return source, nil
}
// -- Response types --
type TokenInfo struct {
StartOffset int64 `json:"start_offset"`
EndOffset int64 `json:"end_offset"`
Position int64 `json:"position"`
Payload string `json:"payload"`
}
type TermsInfo struct {
DocFreq int64 `json:"doc_freq"`
Score float64 `json:"score"`
TermFreq int64 `json:"term_freq"`
Ttf int64 `json:"ttf"`
Tokens []TokenInfo `json:"tokens"`
}
type FieldStatistics struct {
DocCount int64 `json:"doc_count"`
SumDocFreq int64 `json:"sum_doc_freq"`
SumTtf int64 `json:"sum_ttf"`
}
type TermVectorsFieldInfo struct {
FieldStatistics FieldStatistics `json:"field_statistics"`
Terms map[string]TermsInfo `json:"terms"`
}
// TermvectorsResponse is the response of TermvectorsService.Do.
type TermvectorsResponse struct {
Index string `json:"_index"`
Type string `json:"_type"`
Id string `json:"_id,omitempty"`
Version int `json:"_version"`
Found bool `json:"found"`
Took int64 `json:"took"`
TermVectors map[string]TermVectorsFieldInfo `json:"term_vectors"`
}
================================================
FILE: termvectors_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
"time"
)
func TestTermVectorsBuildURL(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tests := []struct {
Index string
Type string
Id string
Expected string
}{
{
"twitter",
"_doc",
"",
"/twitter/_doc/_termvectors",
},
{
"twitter",
"",
"",
"/twitter/_termvectors",
},
{
"twitter",
"_doc",
"1",
"/twitter/_doc/1/_termvectors",
},
{
"twitter",
"",
"1",
"/twitter/_termvectors/1",
},
}
for _, test := range tests {
builder := client.TermVectors(test.Index).Type(test.Type)
if test.Id != "" {
builder = builder.Id(test.Id)
}
path, _, err := builder.buildURL()
if err != nil {
t.Fatal(err)
}
if path != test.Expected {
t.Errorf("expected %q; got: %q", test.Expected, path)
}
}
}
func TestTermVectorsWithId(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
// Add a document
indexResult, err := client.Index().
Index(testIndexName).
Id("1").
BodyJson(&tweet1).
Refresh("true").
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if indexResult == nil {
t.Errorf("expected result to be != nil; got: %v", indexResult)
}
// TermVectors by specifying ID
field := "Message"
result, err := client.TermVectors(testIndexName).
Id("1").
Fields(field).
FieldStatistics(true).
TermStatistics(true).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if result == nil {
t.Fatal("expected to return information and statistics")
}
if !result.Found {
t.Errorf("expected found to be %v; got: %v", true, result.Found)
}
}
func TestTermVectorsWithDoc(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
// Travis lags sometimes
if isCI() {
time.Sleep(2 * time.Second)
}
// TermVectors by specifying Doc
var doc = map[string]interface{}{
"fullname": "John Doe",
"text": "twitter test test test",
}
var perFieldAnalyzer = map[string]string{
"fullname": "keyword",
}
result, err := client.TermVectors(testIndexName).
Doc(doc).
PerFieldAnalyzer(perFieldAnalyzer).
FieldStatistics(true).
TermStatistics(true).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if result == nil {
t.Fatal("expected to return information and statistics")
}
if !result.Found {
t.Errorf("expected found to be %v; got: %v", true, result.Found)
}
}
func TestTermVectorsWithFilter(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
// Travis lags sometimes
if isCI() {
time.Sleep(2 * time.Second)
}
// TermVectors by specifying Doc
var doc = map[string]interface{}{
"fullname": "John Doe",
"text": "twitter test test test",
}
var perFieldAnalyzer = map[string]string{
"fullname": "keyword",
}
result, err := client.TermVectors(testIndexName).
Doc(doc).
PerFieldAnalyzer(perFieldAnalyzer).
FieldStatistics(true).
TermStatistics(true).
Filter(NewTermvectorsFilterSettings().MinTermFreq(1)).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if result == nil {
t.Fatal("expected to return information and statistics")
}
if !result.Found {
t.Errorf("expected found to be %v; got: %v", true, result.Found)
}
}
================================================
FILE: trace/opencensus/transport.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package opencensus
import (
"context"
"fmt"
"net/http"
"net/url"
"github.com/pkg/errors"
"go.opencensus.io/trace"
)
// Transport for tracing Elastic operations.
type Transport struct {
rt http.RoundTripper
defaultAttributes []trace.Attribute
}
// Option signature for specifying options, e.g. WithRoundTripper.
type Option func(t *Transport)
// WithRoundTripper specifies the http.RoundTripper to call
// next after this transport. If it is nil (default), the
// transport will use http.DefaultTransport.
func WithRoundTripper(rt http.RoundTripper) Option {
return func(t *Transport) {
t.rt = rt
}
}
// WithDefaultAttributes specifies default attributes to add
// to each span.
func WithDefaultAttributes(attrs ...trace.Attribute) Option {
return func(t *Transport) {
t.defaultAttributes = attrs
}
}
// NewTransport specifies a transport that will trace Elastic
// and report back via OpenTracing.
func NewTransport(opts ...Option) *Transport {
t := &Transport{}
for _, o := range opts {
o(t)
}
return t
}
// RoundTrip captures the request and starts an OpenTracing span
// for Elastic PerformRequest operation.
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
_, span := trace.StartSpan(req.Context(), "elastic:PerformRequest")
attrs := append([]trace.Attribute(nil), t.defaultAttributes...)
attrs = append(attrs,
trace.StringAttribute("Component", "github.com/olivere/elastic/v7"),
trace.StringAttribute("Method", req.Method),
trace.StringAttribute("URL", req.URL.Redacted()),
trace.StringAttribute("Hostname", req.URL.Hostname()),
trace.Int64Attribute("Port", atoi64(req.URL.Port())),
)
span.AddAttributes(attrs...)
var (
resp *http.Response
err error
)
defer func() {
setSpanStatus(span, err)
span.End()
}()
if t.rt != nil {
resp, err = t.rt.RoundTrip(req)
} else {
resp, err = http.DefaultTransport.RoundTrip(req)
}
return resp, err
}
// See https://github.com/opencensus-integrations/ocsql/blob/master/driver.go#L749
func setSpanStatus(span *trace.Span, err error) {
var status trace.Status
switch {
case err == nil:
status.Code = trace.StatusCodeOK
span.SetStatus(status)
return
case err == context.Canceled:
status.Code = trace.StatusCodeCancelled
case err == context.DeadlineExceeded:
status.Code = trace.StatusCodeDeadlineExceeded
case isConnErr(err):
status.Code = trace.StatusCodeUnavailable
case isNotFound(err):
status.Code = trace.StatusCodeNotFound
case isConflict(err):
status.Code = trace.StatusCodeFailedPrecondition
case isForbidden(err):
status.Code = trace.StatusCodePermissionDenied
case isTimeout(err):
status.Code = trace.StatusCodeResourceExhausted
default:
status.Code = trace.StatusCodeUnknown
}
status.Message = err.Error()
span.SetStatus(status)
}
// Copied from elastic to prevent cyclic dependencies.
type elasticError struct {
Status int `json:"status"`
Details *errorDetails `json:"error,omitempty"`
}
// errorDetails encapsulate error details from Elasticsearch.
// It is used in e.g. elastic.Error and elastic.BulkResponseItem.
type errorDetails struct {
Type string `json:"type"`
Reason string `json:"reason"`
ResourceType string `json:"resource.type,omitempty"`
ResourceId string `json:"resource.id,omitempty"`
Index string `json:"index,omitempty"`
Phase string `json:"phase,omitempty"`
Grouped bool `json:"grouped,omitempty"`
CausedBy map[string]interface{} `json:"caused_by,omitempty"`
RootCause []*errorDetails `json:"root_cause,omitempty"`
FailedShards []map[string]interface{} `json:"failed_shards,omitempty"`
}
// Error returns a string representation of the error.
func (e *elasticError) Error() string {
if e.Details != nil && e.Details.Reason != "" {
return fmt.Sprintf("elastic: Error %d (%s): %s [type=%s]", e.Status, http.StatusText(e.Status), e.Details.Reason, e.Details.Type)
}
return fmt.Sprintf("elastic: Error %d (%s)", e.Status, http.StatusText(e.Status))
}
// isContextErr returns true if the error is from a context that was canceled or deadline exceeded
func isContextErr(err error) bool {
if err == context.Canceled || err == context.DeadlineExceeded {
return true
}
// This happens e.g. on redirect errors, see https://golang.org/src/net/http/client_test.go#L329
if ue, ok := err.(*url.Error); ok {
if ue.Temporary() {
return true
}
// Use of an AWS Signing Transport can result in a wrapped url.Error
return isContextErr(ue.Err)
}
return false
}
// isConnErr returns true if the error indicates that Elastic could not
// find an Elasticsearch host to connect to.
func isConnErr(err error) bool {
if err == nil {
return false
}
if err.Error() == "no Elasticsearch node available" {
return true
}
innerErr := errors.Cause(err)
if innerErr == nil {
return false
}
if innerErr.Error() == "no Elasticsearch node available" {
return true
}
return false
}
// isNotFound returns true if the given error indicates that Elasticsearch
// returned HTTP status 404. The err parameter can be of type *elastic.Error,
// elastic.Error, *http.Response or int (indicating the HTTP status code).
func isNotFound(err interface{}) bool {
return isStatusCode(err, http.StatusNotFound)
}
// isTimeout returns true if the given error indicates that Elasticsearch
// returned HTTP status 408. The err parameter can be of type *elastic.Error,
// elastic.Error, *http.Response or int (indicating the HTTP status code).
func isTimeout(err interface{}) bool {
return isStatusCode(err, http.StatusRequestTimeout)
}
// isConflict returns true if the given error indicates that the Elasticsearch
// operation resulted in a version conflict. This can occur in operations like
// `update` or `index` with `op_type=create`. The err parameter can be of
// type *elastic.Error, elastic.Error, *http.Response or int (indicating the
// HTTP status code).
func isConflict(err interface{}) bool {
return isStatusCode(err, http.StatusConflict)
}
// isForbidden returns true if the given error indicates that Elasticsearch
// returned HTTP status 403. This happens e.g. due to a missing license.
// The err parameter can be of type *elastic.Error, elastic.Error,
// *http.Response or int (indicating the HTTP status code).
func isForbidden(err interface{}) bool {
return isStatusCode(err, http.StatusForbidden)
}
// isStatusCode returns true if the given error indicates that the Elasticsearch
// operation returned the specified HTTP status code. The err parameter can be of
// type *http.Response, *Error, Error, or int (indicating the HTTP status code).
func isStatusCode(err interface{}, code int) bool {
switch e := err.(type) {
case *http.Response:
return e.StatusCode == code
case *elasticError:
return e.Status == code
case elasticError:
return e.Status == code
case int:
return e == code
}
return false
}
================================================
FILE: trace/opencensus/transport_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package opencensus
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"
"go.opencensus.io/trace"
"github.com/olivere/elastic/v7"
)
func init() {
// Always sample
trace.ApplyConfig(trace.Config{DefaultSampler: trace.AlwaysSample()})
}
type testExporter struct {
spans []*trace.SpanData
}
func (t *testExporter) ExportSpan(s *trace.SpanData) {
t.spans = append(t.spans, s)
}
func TestTransport(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
username, password, ok := r.BasicAuth()
if !ok || username != "alice" || password != "secret" {
w.WriteHeader(http.StatusForbidden)
return
}
switch r.URL.Path {
case "/":
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, `{
"name" : "Qg28M36",
"cluster_name" : "docker-cluster",
"cluster_uuid" : "rwHa7BBnRC2h8KoDfCbmuQ",
"version" : {
"number" : "6.3.2",
"build_flavor" : "oss",
"build_type" : "tar",
"build_hash" : "053779d",
"build_date" : "2018-07-20T05:20:23.451332Z",
"build_snapshot" : false,
"lucene_version" : "7.3.1",
"minimum_wire_compatibility_version" : "5.6.0",
"minimum_index_compatibility_version" : "5.0.0"
},
"tagline" : "You Know, for Search"
}`)
return
default:
w.WriteHeader(http.StatusInternalServerError)
return
}
}))
defer ts.Close()
// Register test exporter
var te testExporter
trace.RegisterExporter(&te)
// Setup a simple transport
tr := NewTransport(
WithDefaultAttributes(
trace.StringAttribute("Opaque-Id", "12345"),
),
)
httpClient := &http.Client{
Transport: tr,
}
// Create a simple Ping request via Elastic
client, err := elastic.NewClient(
elastic.SetURL(ts.URL),
elastic.SetHttpClient(httpClient),
elastic.SetHealthcheck(false),
elastic.SetSniff(false),
elastic.SetBasicAuth("alice", "secret"),
)
if err != nil {
t.Fatal(err)
}
res, code, err := client.Ping(ts.URL).Do(context.Background())
if err != nil {
t.Fatal(err)
}
if want, have := http.StatusOK, code; want != have {
t.Fatalf("want Status=%d, have %d", want, have)
}
if want, have := "You Know, for Search", res.TagLine; want != have {
t.Fatalf("want TagLine=%q, have %q", want, have)
}
trace.UnregisterExporter(&te)
// Check the data written into tracer
spans := te.spans
if want, have := 1, len(spans); want != have {
t.Fatalf("want %d finished spans, have %d", want, have)
}
span := spans[0]
if want, have := "elastic:PerformRequest", span.Name; want != have {
t.Fatalf("want Span.Name=%q, have %q", want, have)
}
if attr, ok := span.Attributes["Component"].(string); !ok {
t.Fatalf("attribute %q not found", "Component")
} else if want, have := "github.com/olivere/elastic/v7", attr; want != have {
t.Fatalf("want attribute=%q, have %q", want, have)
}
if attr, ok := span.Attributes["Method"].(string); !ok {
t.Fatalf("attribute %q not found", "Method")
} else if want, have := "GET", attr; want != have {
t.Fatalf("want attribute=%q, have %q", want, have)
}
if attr, ok := span.Attributes["URL"].(string); !ok || attr == "" {
t.Fatalf("attribute %q not found", "URL")
} else if strings.Contains(attr, "alice") || strings.Contains(attr, "password") {
t.Fatalf("attribute %q contains username and/or password: %s", "URL", attr)
}
if attr, ok := span.Attributes["Hostname"].(string); !ok || attr == "" {
t.Fatalf("attribute %q not found", "Hostname")
}
if port, ok := span.Attributes["Port"].(int64); !ok || port <= 0 {
t.Fatalf("attribute %q not found", "Port")
}
if attr, ok := span.Attributes["Opaque-Id"].(string); !ok {
t.Fatalf("attribute %q not found", "Opaque-Id")
} else if want, have := "12345", attr; want != have {
t.Fatalf("want attribute=%q, have %q", want, have)
}
}
================================================
FILE: trace/opencensus/util.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package opencensus
import (
"strconv"
)
func atoi64(s string) int64 {
i, _ := strconv.ParseInt(s, 10, 64)
return i
}
================================================
FILE: trace/opentelemetry/transport.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package opentelemetry
import (
"net/http"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
)
// Transport for tracing Elastic operations.
type Transport struct {
rt http.RoundTripper
}
// Option signature for specifying options, e.g. WithRoundTripper.
type Option func(t *Transport)
// WithRoundTripper specifies the http.RoundTripper to call
// next after this transport. If it is nil (default), the
// transport will use http.DefaultTransport.
func WithRoundTripper(rt http.RoundTripper) Option {
return func(t *Transport) {
t.rt = rt
}
}
// NewTransport specifies a transport that will trace Elastic
// and report back via OpenTracing.
func NewTransport(opts ...Option) *Transport {
t := &Transport{}
for _, o := range opts {
o(t)
}
return t
}
// RoundTrip captures the request and starts an OpenTracing span
// for Elastic PerformRequest operation.
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
ctx, span := otel.Tracer("Elastic").Start(req.Context(), "PerformRequest")
defer span.End()
req = req.WithContext(ctx)
// See General (https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/span-general.md)
// and HTTP (https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md)
span.SetAttributes(
attribute.String("code.namespace", "github.com/olivere/elastic/v7"),
attribute.String("code.function", "PerformRequest"),
attribute.String("http.url", req.URL.Redacted()),
attribute.String("http.method", req.Method),
attribute.String("http.scheme", req.URL.Scheme),
attribute.String("http.host", req.URL.Hostname()),
attribute.String("http.path", req.URL.Path),
attribute.String("http.user_agent", req.UserAgent()),
)
var (
resp *http.Response
err error
)
if t.rt != nil {
resp, err = t.rt.RoundTrip(req)
} else {
resp, err = http.DefaultTransport.RoundTrip(req)
}
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
}
if resp != nil {
span.SetAttributes(attribute.Int64("http.status_code", int64(resp.StatusCode)))
}
return resp, err
}
================================================
FILE: trace/opentelemetry/util.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package opentelemetry
import (
"strconv"
)
func atouint16(s string) uint16 {
v, _ := strconv.ParseUint(s, 10, 16)
return uint16(v)
}
================================================
FILE: trace/opentracing/transport.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package opentracing
import (
"net/http"
"github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
)
// Transport for tracing Elastic operations.
type Transport struct {
rt http.RoundTripper
}
// Option signature for specifying options, e.g. WithRoundTripper.
type Option func(t *Transport)
// WithRoundTripper specifies the http.RoundTripper to call
// next after this transport. If it is nil (default), the
// transport will use http.DefaultTransport.
func WithRoundTripper(rt http.RoundTripper) Option {
return func(t *Transport) {
t.rt = rt
}
}
// NewTransport specifies a transport that will trace Elastic
// and report back via OpenTracing.
func NewTransport(opts ...Option) *Transport {
t := &Transport{}
for _, o := range opts {
o(t)
}
return t
}
// RoundTrip captures the request and starts an OpenTracing span
// for Elastic PerformRequest operation.
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
span, ctx := opentracing.StartSpanFromContext(req.Context(), "PerformRequest")
req = req.WithContext(ctx)
defer span.Finish()
ext.Component.Set(span, "github.com/olivere/elastic/v7")
ext.HTTPUrl.Set(span, req.URL.Redacted())
ext.HTTPMethod.Set(span, req.Method)
ext.PeerHostname.Set(span, req.URL.Hostname())
ext.PeerPort.Set(span, atouint16(req.URL.Port()))
var (
resp *http.Response
err error
)
if t.rt != nil {
resp, err = t.rt.RoundTrip(req)
} else {
resp, err = http.DefaultTransport.RoundTrip(req)
}
if err != nil {
ext.Error.Set(span, true)
}
if resp != nil {
ext.HTTPStatusCode.Set(span, uint16(resp.StatusCode))
}
return resp, err
}
================================================
FILE: trace/opentracing/transport_integration_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package opentracing
import (
"context"
"net/http"
"strings"
"testing"
"github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/mocktracer"
"github.com/olivere/elastic/v7"
)
func TestTransportIntegration(t *testing.T) {
// Mock tracer
tracer := mocktracer.New()
opentracing.InitGlobalTracer(tracer)
// Setup a simple transport
tr := NewTransport()
httpClient := &http.Client{
Transport: tr,
}
// Create a simple Ping request via Elastic
client, err := elastic.NewClient(
elastic.SetURL("http://127.0.0.1:9210"),
elastic.SetHealthcheck(false),
elastic.SetSniff(false),
elastic.SetBasicAuth("elastic", "elastic"),
elastic.SetHttpClient(httpClient),
)
if err != nil {
t.Fatal(err)
}
_, err = client.Search("_all").Query(elastic.NewMatchAllQuery()).Do(context.Background())
if err != nil {
t.Fatal(err)
}
// Check the data written into tracer
spans := tracer.FinishedSpans()
if want, have := 1, len(spans); want != have {
t.Fatalf("want %d finished spans, have %d", want, have)
}
span := spans[0]
if want, have := "PerformRequest", span.OperationName; want != have {
t.Fatalf("want Span.OperationName=%q, have %q", want, have)
}
if want, have := "github.com/olivere/elastic/v7", span.Tag("component"); want != have {
t.Fatalf("want component tag=%q, have %q", want, have)
}
httpURL, ok := span.Tag("http.url").(string)
if !ok || httpURL == "" {
t.Fatalf("want http.url tag=%q to be a non-empty string (found type %T)", "http.url", span.Tag("http.url"))
}
if want, have := "http://127.0.0.1:9210/_all/_search", httpURL; want != have {
t.Fatalf("want http.url tag=%q, have %q", want, have)
}
if strings.Contains(httpURL, "elastic") {
t.Fatalf("want http.url tag %q to not contain username and/or password: %s", "URL", span.Tag("http.url"))
}
if want, have := "POST", span.Tag("http.method"); want != have {
t.Fatalf("want http.method tag=%q, have %q", want, have)
}
if want, have := uint16(http.StatusOK), span.Tag("http.status_code"); want != have {
t.Fatalf("want http.status_code tag=%v (%T), have %v (%T)", want, want, have, have)
}
}
================================================
FILE: trace/opentracing/transport_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package opentracing
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/mocktracer"
"github.com/olivere/elastic/v7"
)
func TestTransport(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
username, password, ok := r.BasicAuth()
if !ok || username != "alice" || password != "secret" {
w.WriteHeader(http.StatusForbidden)
return
}
switch r.URL.Path {
case "/":
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, `{
"name" : "Qg28M36",
"cluster_name" : "docker-cluster",
"cluster_uuid" : "rwHa7BBnRC2h8KoDfCbmuQ",
"version" : {
"number" : "6.3.2",
"build_flavor" : "oss",
"build_type" : "tar",
"build_hash" : "053779d",
"build_date" : "2018-07-20T05:20:23.451332Z",
"build_snapshot" : false,
"lucene_version" : "7.3.1",
"minimum_wire_compatibility_version" : "5.6.0",
"minimum_index_compatibility_version" : "5.0.0"
},
"tagline" : "You Know, for Search"
}`)
return
default:
w.WriteHeader(http.StatusInternalServerError)
return
}
}))
defer ts.Close()
// Mock tracer
tracer := mocktracer.New()
opentracing.InitGlobalTracer(tracer)
// Setup a simple transport
tr := NewTransport()
httpClient := &http.Client{
Transport: tr,
}
// Create a simple Ping request via Elastic
client, err := elastic.NewClient(
elastic.SetURL(ts.URL),
elastic.SetHttpClient(httpClient),
elastic.SetHealthcheck(false),
elastic.SetSniff(false),
elastic.SetBasicAuth("alice", "secret"),
)
if err != nil {
t.Fatal(err)
}
res, code, err := client.Ping(ts.URL).Do(context.Background())
if err != nil {
t.Fatal(err)
}
if want, have := http.StatusOK, code; want != have {
t.Fatalf("want Status=%d, have %d", want, have)
}
if want, have := "You Know, for Search", res.TagLine; want != have {
t.Fatalf("want TagLine=%q, have %q", want, have)
}
// Check the data written into tracer
spans := tracer.FinishedSpans()
if want, have := 1, len(spans); want != have {
t.Fatalf("want %d finished spans, have %d", want, have)
}
span := spans[0]
if want, have := "PerformRequest", span.OperationName; want != have {
t.Fatalf("want Span.OperationName=%q, have %q", want, have)
}
if want, have := "github.com/olivere/elastic/v7", span.Tag("component"); want != have {
t.Fatalf("want component tag=%q, have %q", want, have)
}
httpURL, ok := span.Tag("http.url").(string)
if !ok || httpURL == "" {
t.Fatalf("want http.url tag=%q to be a non-empty string (found type %T)", "http.url", span.Tag("http.url"))
}
if want, have := ts.URL+"/", httpURL; want != have {
t.Fatalf("want http.url tag=%q, have %q", want, have)
}
t.Logf("http.url = %q", httpURL)
if strings.Contains(httpURL, "alice") || strings.Contains(httpURL, "password") {
t.Fatalf("want http.url tag %q to not contain username and/or password: %s", "URL", span.Tag("http.url"))
}
if want, have := "GET", span.Tag("http.method"); want != have {
t.Fatalf("want http.method tag=%q, have %q", want, have)
}
if want, have := uint16(http.StatusOK), span.Tag("http.status_code"); want != have {
t.Fatalf("want http.status_code tag=%v (%T), have %v (%T)", want, want, have, have)
}
}
================================================
FILE: trace/opentracing/util.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package opentracing
import (
"strconv"
)
func atouint16(s string) uint16 {
v, _ := strconv.ParseUint(s, 10, 16)
return uint16(v)
}
================================================
FILE: update.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// UpdateService updates a document in Elasticsearch.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-update.html
// for details.
type UpdateService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
index string
typ string
id string
routing string
parent string
script *Script
fields []string
fsc *FetchSourceContext
version *int64
versionType string
retryOnConflict *int
refresh string
waitForActiveShards string
upsert interface{}
scriptedUpsert *bool
docAsUpsert *bool
detectNoop *bool
doc interface{}
timeout string
ifSeqNo *int64
ifPrimaryTerm *int64
}
// NewUpdateService creates the service to update documents in Elasticsearch.
func NewUpdateService(client *Client) *UpdateService {
return &UpdateService{
client: client,
typ: "_doc",
fields: make([]string, 0),
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *UpdateService) Pretty(pretty bool) *UpdateService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *UpdateService) Human(human bool) *UpdateService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *UpdateService) ErrorTrace(errorTrace bool) *UpdateService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *UpdateService) FilterPath(filterPath ...string) *UpdateService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *UpdateService) Header(name string, value string) *UpdateService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *UpdateService) Headers(headers http.Header) *UpdateService {
s.headers = headers
return s
}
// Index is the name of the Elasticsearch index (required).
func (s *UpdateService) Index(name string) *UpdateService {
s.index = name
return s
}
// Type is the type of the document.
//
// Deprecated: Types are in the process of being removed.
func (s *UpdateService) Type(typ string) *UpdateService {
s.typ = typ
return s
}
// Id is the identifier of the document to update (required).
func (s *UpdateService) Id(id string) *UpdateService {
s.id = id
return s
}
// Routing specifies a specific routing value.
func (s *UpdateService) Routing(routing string) *UpdateService {
s.routing = routing
return s
}
// Parent sets the id of the parent document.
func (s *UpdateService) Parent(parent string) *UpdateService {
s.parent = parent
return s
}
// Script is the script definition.
func (s *UpdateService) Script(script *Script) *UpdateService {
s.script = script
return s
}
// RetryOnConflict specifies how many times the operation should be retried
// when a conflict occurs (default: 0).
func (s *UpdateService) RetryOnConflict(retryOnConflict int) *UpdateService {
s.retryOnConflict = &retryOnConflict
return s
}
// Fields is a list of fields to return in the response.
func (s *UpdateService) Fields(fields ...string) *UpdateService {
s.fields = make([]string, 0, len(fields))
s.fields = append(s.fields, fields...)
return s
}
// Version defines the explicit version number for concurrency control.
func (s *UpdateService) Version(version int64) *UpdateService {
s.version = &version
return s
}
// VersionType is e.g. "internal".
func (s *UpdateService) VersionType(versionType string) *UpdateService {
s.versionType = versionType
return s
}
// Refresh the index after performing the update.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-refresh.html
// for details.
func (s *UpdateService) Refresh(refresh string) *UpdateService {
s.refresh = refresh
return s
}
// WaitForActiveShards sets the number of shard copies that must be active before
// proceeding with the update operation. Defaults to 1, meaning the primary shard only.
// Set to `all` for all shard copies, otherwise set to any non-negative value less than
// or equal to the total number of copies for the shard (number of replicas + 1).
func (s *UpdateService) WaitForActiveShards(waitForActiveShards string) *UpdateService {
s.waitForActiveShards = waitForActiveShards
return s
}
// Doc allows for updating a partial document.
func (s *UpdateService) Doc(doc interface{}) *UpdateService {
s.doc = doc
return s
}
// Upsert can be used to index the document when it doesn't exist yet.
// Use this e.g. to initialize a document with a default value.
func (s *UpdateService) Upsert(doc interface{}) *UpdateService {
s.upsert = doc
return s
}
// DocAsUpsert can be used to insert the document if it doesn't already exist.
func (s *UpdateService) DocAsUpsert(docAsUpsert bool) *UpdateService {
s.docAsUpsert = &docAsUpsert
return s
}
// DetectNoop will instruct Elasticsearch to check if changes will occur
// when updating via Doc. It there aren't any changes, the request will
// turn into a no-op.
func (s *UpdateService) DetectNoop(detectNoop bool) *UpdateService {
s.detectNoop = &detectNoop
return s
}
// ScriptedUpsert should be set to true if the referenced script
// (defined in Script or ScriptId) should be called to perform an insert.
// The default is false.
func (s *UpdateService) ScriptedUpsert(scriptedUpsert bool) *UpdateService {
s.scriptedUpsert = &scriptedUpsert
return s
}
// Timeout is an explicit timeout for the operation, e.g. "1000", "1s" or "500ms".
func (s *UpdateService) Timeout(timeout string) *UpdateService {
s.timeout = timeout
return s
}
// IfSeqNo indicates to only perform the update operation if the last
// operation that has changed the document has the specified sequence number.
func (s *UpdateService) IfSeqNo(seqNo int64) *UpdateService {
s.ifSeqNo = &seqNo
return s
}
// IfPrimaryTerm indicates to only perform the update operation if the
// last operation that has changed the document has the specified primary term.
func (s *UpdateService) IfPrimaryTerm(primaryTerm int64) *UpdateService {
s.ifPrimaryTerm = &primaryTerm
return s
}
// FetchSource asks Elasticsearch to return the updated _source in the response.
func (s *UpdateService) FetchSource(fetchSource bool) *UpdateService {
if s.fsc == nil {
s.fsc = NewFetchSourceContext(fetchSource)
} else {
s.fsc.SetFetchSource(fetchSource)
}
return s
}
// FetchSourceContext indicates that _source should be returned in the response,
// allowing wildcard patterns to be defined via FetchSourceContext.
func (s *UpdateService) FetchSourceContext(fetchSourceContext *FetchSourceContext) *UpdateService {
s.fsc = fetchSourceContext
return s
}
// url returns the URL part of the document request.
func (s *UpdateService) url() (string, url.Values, error) {
// Build url
var path string
var err error
if s.typ == "" || s.typ == "_doc" {
path, err = uritemplates.Expand("/{index}/_update/{id}", map[string]string{
"index": s.index,
"id": s.id,
})
} else {
path, err = uritemplates.Expand("/{index}/{type}/{id}/_update", map[string]string{
"index": s.index,
"type": s.typ,
"id": s.id,
})
}
if err != nil {
return "", url.Values{}, err
}
// Parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.routing != "" {
params.Set("routing", s.routing)
}
if s.parent != "" {
params.Set("parent", s.parent)
}
if s.timeout != "" {
params.Set("timeout", s.timeout)
}
if s.refresh != "" {
params.Set("refresh", s.refresh)
}
if s.waitForActiveShards != "" {
params.Set("wait_for_active_shards", s.waitForActiveShards)
}
if len(s.fields) > 0 {
params.Set("fields", strings.Join(s.fields, ","))
}
if s.version != nil {
params.Set("version", fmt.Sprintf("%d", *s.version))
}
if s.versionType != "" {
params.Set("version_type", s.versionType)
}
if s.retryOnConflict != nil {
params.Set("retry_on_conflict", fmt.Sprintf("%v", *s.retryOnConflict))
}
if v := s.ifSeqNo; v != nil {
params.Set("if_seq_no", fmt.Sprintf("%d", *v))
}
if v := s.ifPrimaryTerm; v != nil {
params.Set("if_primary_term", fmt.Sprintf("%d", *v))
}
return path, params, nil
}
// body returns the body part of the document request.
func (s *UpdateService) body() (interface{}, error) {
source := make(map[string]interface{})
if s.script != nil {
src, err := s.script.Source()
if err != nil {
return nil, err
}
source["script"] = src
}
if v := s.scriptedUpsert; v != nil {
source["scripted_upsert"] = *v
}
if s.upsert != nil {
source["upsert"] = s.upsert
}
if s.doc != nil {
source["doc"] = s.doc
}
if v := s.docAsUpsert; v != nil {
source["doc_as_upsert"] = *v
}
if v := s.detectNoop; v != nil {
source["detect_noop"] = *v
}
if s.fsc != nil {
src, err := s.fsc.Source()
if err != nil {
return nil, err
}
source["_source"] = src
}
return source, nil
}
// Do executes the update operation.
func (s *UpdateService) Do(ctx context.Context) (*UpdateResponse, error) {
path, params, err := s.url()
if err != nil {
return nil, err
}
// Get body of the request
body, err := s.body()
if err != nil {
return nil, err
}
// Get response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "POST",
Path: path,
Params: params,
Body: body,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return result
ret := new(UpdateResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// UpdateResponse is the result of updating a document in Elasticsearch.
type UpdateResponse struct {
Index string `json:"_index,omitempty"`
Type string `json:"_type,omitempty"`
Id string `json:"_id,omitempty"`
Version int64 `json:"_version,omitempty"`
Result string `json:"result,omitempty"`
Shards *ShardsInfo `json:"_shards,omitempty"`
SeqNo int64 `json:"_seq_no,omitempty"`
PrimaryTerm int64 `json:"_primary_term,omitempty"`
Status int `json:"status,omitempty"`
ForcedRefresh bool `json:"forced_refresh,omitempty"`
GetResult *GetResult `json:"get,omitempty"`
}
================================================
FILE: update_by_query.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// UpdateByQueryService is documented at https://www.elastic.co/guide/en/elasticsearch/plugins/master/plugins-reindex.html.
type UpdateByQueryService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
index []string
typ []string
script *Script
query Query
body interface{}
xSource []string
xSourceExclude []string
xSourceInclude []string
allowNoIndices *bool
analyzeWildcard *bool
analyzer string
conflicts string
defaultOperator string
docvalueFields []string
df string
expandWildcards string
explain *bool
fielddataFields []string
from *int
ignoreUnavailable *bool
lenient *bool
lowercaseExpandedTerms *bool
maxDocs *int
pipeline string
preference string
q string
refresh string
requestCache *bool
requestsPerSecond *int
routing []string
scroll string
scrollSize *int
searchTimeout string
searchType string
size *int
slices interface{}
sort []string
stats []string
storedFields []string
suggestField string
suggestMode string
suggestSize *int
suggestText string
terminateAfter *int
timeout string
trackScores *bool
version *bool
versionType *bool
waitForActiveShards string
waitForCompletion *bool
}
// NewUpdateByQueryService creates a new UpdateByQueryService.
func NewUpdateByQueryService(client *Client) *UpdateByQueryService {
return &UpdateByQueryService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *UpdateByQueryService) Pretty(pretty bool) *UpdateByQueryService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *UpdateByQueryService) Human(human bool) *UpdateByQueryService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *UpdateByQueryService) ErrorTrace(errorTrace bool) *UpdateByQueryService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *UpdateByQueryService) FilterPath(filterPath ...string) *UpdateByQueryService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *UpdateByQueryService) Header(name string, value string) *UpdateByQueryService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *UpdateByQueryService) Headers(headers http.Header) *UpdateByQueryService {
s.headers = headers
return s
}
// Index is a list of index names to search; use `_all` or empty string to
// perform the operation on all indices.
func (s *UpdateByQueryService) Index(index ...string) *UpdateByQueryService {
s.index = append(s.index, index...)
return s
}
// Type is a list of document types to search; leave empty to perform
// the operation on all types.
func (s *UpdateByQueryService) Type(typ ...string) *UpdateByQueryService {
s.typ = append(s.typ, typ...)
return s
}
// Script sets an update script.
func (s *UpdateByQueryService) Script(script *Script) *UpdateByQueryService {
s.script = script
return s
}
// Body specifies the body of the request. It overrides data being specified via
// SearchService or Script.
func (s *UpdateByQueryService) Body(body string) *UpdateByQueryService {
s.body = body
return s
}
// XSource is true or false to return the _source field or not,
// or a list of fields to return.
func (s *UpdateByQueryService) XSource(xSource ...string) *UpdateByQueryService {
s.xSource = append(s.xSource, xSource...)
return s
}
// XSourceExclude represents a list of fields to exclude from the returned _source field.
func (s *UpdateByQueryService) XSourceExclude(xSourceExclude ...string) *UpdateByQueryService {
s.xSourceExclude = append(s.xSourceExclude, xSourceExclude...)
return s
}
// XSourceInclude represents a list of fields to extract and return from the _source field.
func (s *UpdateByQueryService) XSourceInclude(xSourceInclude ...string) *UpdateByQueryService {
s.xSourceInclude = append(s.xSourceInclude, xSourceInclude...)
return s
}
// AllowNoIndices indicates whether to ignore if a wildcard indices expression
// resolves into no concrete indices. (This includes `_all` string or when
// no indices have been specified).
func (s *UpdateByQueryService) AllowNoIndices(allowNoIndices bool) *UpdateByQueryService {
s.allowNoIndices = &allowNoIndices
return s
}
// AnalyzeWildcard specifies whether wildcard and prefix queries should be
// analyzed (default: false).
func (s *UpdateByQueryService) AnalyzeWildcard(analyzeWildcard bool) *UpdateByQueryService {
s.analyzeWildcard = &analyzeWildcard
return s
}
// Analyzer specifies the analyzer to use for the query string.
func (s *UpdateByQueryService) Analyzer(analyzer string) *UpdateByQueryService {
s.analyzer = analyzer
return s
}
// Conflicts indicates what to do when the process detects version conflicts.
// Possible values are "proceed" and "abort".
func (s *UpdateByQueryService) Conflicts(conflicts string) *UpdateByQueryService {
s.conflicts = conflicts
return s
}
// AbortOnVersionConflict aborts the request on version conflicts.
// It is an alias to setting Conflicts("abort").
func (s *UpdateByQueryService) AbortOnVersionConflict() *UpdateByQueryService {
s.conflicts = "abort"
return s
}
// ProceedOnVersionConflict won't abort the request on version conflicts.
// It is an alias to setting Conflicts("proceed").
func (s *UpdateByQueryService) ProceedOnVersionConflict() *UpdateByQueryService {
s.conflicts = "proceed"
return s
}
// DefaultOperator is the default operator for query string query (AND or OR).
func (s *UpdateByQueryService) DefaultOperator(defaultOperator string) *UpdateByQueryService {
s.defaultOperator = defaultOperator
return s
}
// DF specifies the field to use as default where no field prefix is given in the query string.
func (s *UpdateByQueryService) DF(df string) *UpdateByQueryService {
s.df = df
return s
}
// DocvalueFields specifies the list of fields to return as the docvalue representation of a field for each hit.
func (s *UpdateByQueryService) DocvalueFields(docvalueFields ...string) *UpdateByQueryService {
s.docvalueFields = docvalueFields
return s
}
// ExpandWildcards indicates whether to expand wildcard expression to
// concrete indices that are open, closed or both.
func (s *UpdateByQueryService) ExpandWildcards(expandWildcards string) *UpdateByQueryService {
s.expandWildcards = expandWildcards
return s
}
// Explain specifies whether to return detailed information about score
// computation as part of a hit.
func (s *UpdateByQueryService) Explain(explain bool) *UpdateByQueryService {
s.explain = &explain
return s
}
// FielddataFields is a list of fields to return as the field data
// representation of a field for each hit.
func (s *UpdateByQueryService) FielddataFields(fielddataFields ...string) *UpdateByQueryService {
s.fielddataFields = append(s.fielddataFields, fielddataFields...)
return s
}
// From is the starting offset (default: 0).
func (s *UpdateByQueryService) From(from int) *UpdateByQueryService {
s.from = &from
return s
}
// IgnoreUnavailable indicates whether specified concrete indices should be
// ignored when unavailable (missing or closed).
func (s *UpdateByQueryService) IgnoreUnavailable(ignoreUnavailable bool) *UpdateByQueryService {
s.ignoreUnavailable = &ignoreUnavailable
return s
}
// Lenient specifies whether format-based query failures
// (such as providing text to a numeric field) should be ignored.
func (s *UpdateByQueryService) Lenient(lenient bool) *UpdateByQueryService {
s.lenient = &lenient
return s
}
// LowercaseExpandedTerms specifies whether query terms should be lowercased.
func (s *UpdateByQueryService) LowercaseExpandedTerms(lowercaseExpandedTerms bool) *UpdateByQueryService {
s.lowercaseExpandedTerms = &lowercaseExpandedTerms
return s
}
// MaxDocs specifies maximum number of documents to process
func (s *UpdateByQueryService) MaxDocs(maxDocs int) *UpdateByQueryService {
s.maxDocs = &maxDocs
return s
}
// Pipeline specifies the ingest pipeline to set on index requests made by this action (default: none).
func (s *UpdateByQueryService) Pipeline(pipeline string) *UpdateByQueryService {
s.pipeline = pipeline
return s
}
// Preference specifies the node or shard the operation should be performed on
// (default: random).
func (s *UpdateByQueryService) Preference(preference string) *UpdateByQueryService {
s.preference = preference
return s
}
// Q specifies the query in the Lucene query string syntax.
func (s *UpdateByQueryService) Q(q string) *UpdateByQueryService {
s.q = q
return s
}
// Query sets a query definition using the Query DSL.
func (s *UpdateByQueryService) Query(query Query) *UpdateByQueryService {
s.query = query
return s
}
// Refresh indicates whether the effected indexes should be refreshed.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-refresh.html
// for details.
func (s *UpdateByQueryService) Refresh(refresh string) *UpdateByQueryService {
s.refresh = refresh
return s
}
// RequestCache specifies if request cache should be used for this request
// or not, defaults to index level setting.
func (s *UpdateByQueryService) RequestCache(requestCache bool) *UpdateByQueryService {
s.requestCache = &requestCache
return s
}
// RequestsPerSecond sets the throttle on this request in sub-requests per second.
// -1 means set no throttle as does "unlimited" which is the only non-float this accepts.
func (s *UpdateByQueryService) RequestsPerSecond(requestsPerSecond int) *UpdateByQueryService {
s.requestsPerSecond = &requestsPerSecond
return s
}
// Routing is a list of specific routing values.
func (s *UpdateByQueryService) Routing(routing ...string) *UpdateByQueryService {
s.routing = append(s.routing, routing...)
return s
}
// Scroll specifies how long a consistent view of the index should be maintained
// for scrolled search.
func (s *UpdateByQueryService) Scroll(scroll string) *UpdateByQueryService {
s.scroll = scroll
return s
}
// ScrollSize is the size on the scroll request powering the update_by_query.
func (s *UpdateByQueryService) ScrollSize(scrollSize int) *UpdateByQueryService {
s.scrollSize = &scrollSize
return s
}
// SearchTimeout defines an explicit timeout for each search request.
// Defaults to no timeout.
func (s *UpdateByQueryService) SearchTimeout(searchTimeout string) *UpdateByQueryService {
s.searchTimeout = searchTimeout
return s
}
// SearchType is the search operation type. Possible values are
// "query_then_fetch" and "dfs_query_then_fetch".
func (s *UpdateByQueryService) SearchType(searchType string) *UpdateByQueryService {
s.searchType = searchType
return s
}
// Size represents the number of hits to return (default: 10).
func (s *UpdateByQueryService) Size(size int) *UpdateByQueryService {
s.size = &size
return s
}
// Slices represents the number of slices (default: 1).
// It used to be a number, but can be set to "auto" as of 6.7.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/docs-update-by-query.html#docs-update-by-query-slice
// for details.
func (s *UpdateByQueryService) Slices(slices interface{}) *UpdateByQueryService {
s.slices = slices
return s
}
// Sort is a list of : pairs.
func (s *UpdateByQueryService) Sort(sort ...string) *UpdateByQueryService {
s.sort = append(s.sort, sort...)
return s
}
// SortByField adds a sort order.
func (s *UpdateByQueryService) SortByField(field string, ascending bool) *UpdateByQueryService {
if ascending {
s.sort = append(s.sort, fmt.Sprintf("%s:asc", field))
} else {
s.sort = append(s.sort, fmt.Sprintf("%s:desc", field))
}
return s
}
// Stats specifies specific tag(s) of the request for logging and statistical purposes.
func (s *UpdateByQueryService) Stats(stats ...string) *UpdateByQueryService {
s.stats = append(s.stats, stats...)
return s
}
// StoredFields specifies the list of stored fields to return as part of a hit.
func (s *UpdateByQueryService) StoredFields(storedFields ...string) *UpdateByQueryService {
s.storedFields = storedFields
return s
}
// SuggestField specifies which field to use for suggestions.
func (s *UpdateByQueryService) SuggestField(suggestField string) *UpdateByQueryService {
s.suggestField = suggestField
return s
}
// SuggestMode specifies the suggest mode. Possible values are
// "missing", "popular", and "always".
func (s *UpdateByQueryService) SuggestMode(suggestMode string) *UpdateByQueryService {
s.suggestMode = suggestMode
return s
}
// SuggestSize specifies how many suggestions to return in response.
func (s *UpdateByQueryService) SuggestSize(suggestSize int) *UpdateByQueryService {
s.suggestSize = &suggestSize
return s
}
// SuggestText specifies the source text for which the suggestions should be returned.
func (s *UpdateByQueryService) SuggestText(suggestText string) *UpdateByQueryService {
s.suggestText = suggestText
return s
}
// TerminateAfter indicates the maximum number of documents to collect
// for each shard, upon reaching which the query execution will terminate early.
func (s *UpdateByQueryService) TerminateAfter(terminateAfter int) *UpdateByQueryService {
s.terminateAfter = &terminateAfter
return s
}
// Timeout is the time each individual bulk request should wait for shards
// that are unavailable.
func (s *UpdateByQueryService) Timeout(timeout string) *UpdateByQueryService {
s.timeout = timeout
return s
}
// TimeoutInMillis sets the timeout in milliseconds.
func (s *UpdateByQueryService) TimeoutInMillis(timeoutInMillis int) *UpdateByQueryService {
s.timeout = fmt.Sprintf("%dms", timeoutInMillis)
return s
}
// TrackScores indicates whether to calculate and return scores even if
// they are not used for sorting.
func (s *UpdateByQueryService) TrackScores(trackScores bool) *UpdateByQueryService {
s.trackScores = &trackScores
return s
}
// Version specifies whether to return document version as part of a hit.
func (s *UpdateByQueryService) Version(version bool) *UpdateByQueryService {
s.version = &version
return s
}
// VersionType indicates if the document increment the version number (internal)
// on hit or not (reindex).
func (s *UpdateByQueryService) VersionType(versionType bool) *UpdateByQueryService {
s.versionType = &versionType
return s
}
// WaitForActiveShards sets the number of shard copies that must be active before proceeding
// with the update by query operation. Defaults to 1, meaning the primary shard only.
// Set to `all` for all shard copies, otherwise set to any non-negative value less than or equal
// to the total number of copies for the shard (number of replicas + 1).
func (s *UpdateByQueryService) WaitForActiveShards(waitForActiveShards string) *UpdateByQueryService {
s.waitForActiveShards = waitForActiveShards
return s
}
// WaitForCompletion indicates if the request should block until the reindex is complete.
func (s *UpdateByQueryService) WaitForCompletion(waitForCompletion bool) *UpdateByQueryService {
s.waitForCompletion = &waitForCompletion
return s
}
// buildURL builds the URL for the operation.
func (s *UpdateByQueryService) buildURL() (string, url.Values, error) {
// Build URL
var err error
var path string
if len(s.typ) > 0 {
path, err = uritemplates.Expand("/{index}/{type}/_update_by_query", map[string]string{
"index": strings.Join(s.index, ","),
"type": strings.Join(s.typ, ","),
})
} else {
path, err = uritemplates.Expand("/{index}/_update_by_query", map[string]string{
"index": strings.Join(s.index, ","),
})
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if len(s.xSource) > 0 {
params.Set("_source", strings.Join(s.xSource, ","))
}
if len(s.xSourceExclude) > 0 {
params.Set("_source_excludes", strings.Join(s.xSourceExclude, ","))
}
if len(s.xSourceInclude) > 0 {
params.Set("_source_includes", strings.Join(s.xSourceInclude, ","))
}
if s.allowNoIndices != nil {
params.Set("allow_no_indices", fmt.Sprintf("%v", *s.allowNoIndices))
}
if s.analyzer != "" {
params.Set("analyzer", s.analyzer)
}
if v := s.analyzeWildcard; v != nil {
params.Set("analyze_wildcard", fmt.Sprint(*v))
}
if s.conflicts != "" {
params.Set("conflicts", s.conflicts)
}
if s.defaultOperator != "" {
params.Set("default_operator", s.defaultOperator)
}
if s.df != "" {
params.Set("df", s.df)
}
if s.expandWildcards != "" {
params.Set("expand_wildcards", s.expandWildcards)
}
if v := s.explain; v != nil {
params.Set("explain", fmt.Sprint(*v))
}
if len(s.storedFields) > 0 {
params.Set("stored_fields", strings.Join(s.storedFields, ","))
}
if len(s.docvalueFields) > 0 {
params.Set("docvalue_fields", strings.Join(s.docvalueFields, ","))
}
if len(s.fielddataFields) > 0 {
params.Set("fielddata_fields", strings.Join(s.fielddataFields, ","))
}
if s.from != nil {
params.Set("from", fmt.Sprintf("%d", *s.from))
}
if v := s.ignoreUnavailable; v != nil {
params.Set("ignore_unavailable", fmt.Sprint(*v))
}
if v := s.lenient; v != nil {
params.Set("lenient", fmt.Sprint(*v))
}
if v := s.lowercaseExpandedTerms; v != nil {
params.Set("lowercase_expanded_terms", fmt.Sprint(*v))
}
if s.maxDocs != nil {
params.Set("max_docs", fmt.Sprintf("%d", *s.maxDocs))
}
if s.pipeline != "" {
params.Set("pipeline", s.pipeline)
}
if s.preference != "" {
params.Set("preference", s.preference)
}
if s.q != "" {
params.Set("q", s.q)
}
if s.refresh != "" {
params.Set("refresh", s.refresh)
}
if v := s.requestCache; v != nil {
params.Set("request_cache", fmt.Sprint(*v))
}
if len(s.routing) > 0 {
params.Set("routing", strings.Join(s.routing, ","))
}
if s.scroll != "" {
params.Set("scroll", s.scroll)
}
if s.scrollSize != nil {
params.Set("scroll_size", fmt.Sprintf("%d", *s.scrollSize))
}
if s.searchTimeout != "" {
params.Set("search_timeout", s.searchTimeout)
}
if s.searchType != "" {
params.Set("search_type", s.searchType)
}
if s.size != nil {
params.Set("size", fmt.Sprintf("%d", *s.size))
}
if s.slices != nil {
params.Set("slices", fmt.Sprintf("%v", s.slices))
}
if len(s.sort) > 0 {
params.Set("sort", strings.Join(s.sort, ","))
}
if len(s.stats) > 0 {
params.Set("stats", strings.Join(s.stats, ","))
}
if s.suggestField != "" {
params.Set("suggest_field", s.suggestField)
}
if s.suggestMode != "" {
params.Set("suggest_mode", s.suggestMode)
}
if s.suggestSize != nil {
params.Set("suggest_size", fmt.Sprintf("%v", *s.suggestSize))
}
if s.suggestText != "" {
params.Set("suggest_text", s.suggestText)
}
if s.terminateAfter != nil {
params.Set("terminate_after", fmt.Sprintf("%v", *s.terminateAfter))
}
if s.timeout != "" {
params.Set("timeout", s.timeout)
}
if v := s.trackScores; v != nil {
params.Set("track_scores", fmt.Sprint(*v))
}
if v := s.version; v != nil {
params.Set("version", fmt.Sprint(*v))
}
if v := s.versionType; v != nil {
params.Set("version_type", fmt.Sprint(*v))
}
if s.waitForActiveShards != "" {
params.Set("wait_for_active_shards", s.waitForActiveShards)
}
if v := s.waitForCompletion; v != nil {
params.Set("wait_for_completion", fmt.Sprint(*v))
}
if s.requestsPerSecond != nil {
params.Set("requests_per_second", fmt.Sprintf("%v", *s.requestsPerSecond))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *UpdateByQueryService) Validate() error {
var invalid []string
if len(s.index) == 0 {
invalid = append(invalid, "Index")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// getBody returns the body part of the document request.
func (s *UpdateByQueryService) getBody() (interface{}, error) {
if s.body != nil {
return s.body, nil
}
source := make(map[string]interface{})
if s.script != nil {
src, err := s.script.Source()
if err != nil {
return nil, err
}
source["script"] = src
}
if s.query != nil {
src, err := s.query.Source()
if err != nil {
return nil, err
}
source["query"] = src
}
return source, nil
}
// Do executes the operation.
func (s *UpdateByQueryService) Do(ctx context.Context) (*BulkIndexByScrollResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Setup HTTP request body
body, err := s.getBody()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "POST",
Path: path,
Params: params,
Body: body,
Headers: s.headers,
IgnoreErrors: []int{http.StatusConflict},
})
if err != nil {
return nil, err
}
// Return operation response (BulkIndexByScrollResponse is defined in DeleteByQuery)
ret := new(BulkIndexByScrollResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// DoAsync executes the update-by-query operation asynchronously by starting a new task.
// Callers need to use the Task Management API to watch the outcome of the reindexing
// operation.
func (s *UpdateByQueryService) DoAsync(ctx context.Context) (*StartTaskResult, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// DoAsync only makes sense with WaitForCompletion set to true
if s.waitForCompletion != nil && *s.waitForCompletion {
return nil, fmt.Errorf("cannot start a task with WaitForCompletion set to true")
}
f := false
s.waitForCompletion = &f
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Setup HTTP request body
body, err := s.getBody()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "POST",
Path: path,
Params: params,
Body: body,
Headers: s.headers,
IgnoreErrors: []int{http.StatusConflict},
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(StartTaskResult)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
================================================
FILE: update_by_query_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"io/ioutil"
"net/http"
"strings"
"testing"
)
func TestUpdateByQueryBuildURL(t *testing.T) {
client := setupTestClient(t)
tests := []struct {
Indices []string
Types []string
Expected string
ExpectErr bool
}{
{
[]string{},
[]string{},
"",
true,
},
{
[]string{"index1"},
[]string{},
"/index1/_update_by_query",
false,
},
{
[]string{"index1", "index2"},
[]string{},
"/index1%2Cindex2/_update_by_query",
false,
},
{
[]string{},
[]string{"type1"},
"",
true,
},
{
[]string{"index1"},
[]string{"type1"},
"/index1/type1/_update_by_query",
false,
},
{
[]string{"index1", "index2"},
[]string{"type1", "type2"},
"/index1%2Cindex2/type1%2Ctype2/_update_by_query",
false,
},
}
for i, test := range tests {
builder := client.UpdateByQuery().Index(test.Indices...).Type(test.Types...)
err := builder.Validate()
if err != nil {
if !test.ExpectErr {
t.Errorf("case #%d: %v", i+1, err)
continue
}
} else {
// err == nil
if test.ExpectErr {
t.Errorf("case #%d: expected error", i+1)
continue
}
path, _, _ := builder.buildURL()
if path != test.Expected {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.Expected, path)
}
}
}
}
func TestUpdateByQueryBodyWithQuery(t *testing.T) {
client := setupTestClient(t)
out, err := client.UpdateByQuery().Query(NewTermQuery("user", "olivere")).getBody()
if err != nil {
t.Fatal(err)
}
b, err := json.Marshal(out)
if err != nil {
t.Fatal(err)
}
got := string(b)
want := `{"query":{"term":{"user":"olivere"}}}`
if got != want {
t.Fatalf("\ngot %s\nwant %s", got, want)
}
}
func TestUpdateByQueryBodyWithQueryAndScript(t *testing.T) {
client := setupTestClient(t)
out, err := client.UpdateByQuery().
Query(NewTermQuery("user", "olivere")).
Script(NewScriptInline("ctx._source.likes++")).
getBody()
if err != nil {
t.Fatal(err)
}
b, err := json.Marshal(out)
if err != nil {
t.Fatal(err)
}
got := string(b)
want := `{"query":{"term":{"user":"olivere"}},"script":{"source":"ctx._source.likes++"}}`
if got != want {
t.Fatalf("\ngot %s\nwant %s", got, want)
}
}
func TestUpdateByQuery(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t) //, SetTraceLog(log.New(os.Stdout, "", 0)))
esversion, err := client.ElasticsearchVersion(DefaultURL)
if err != nil {
t.Fatal(err)
}
if esversion < "2.3.0" {
t.Skipf("Elasticsearch %v does not support update-by-query yet", esversion)
}
sourceCount, err := client.Count(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if sourceCount <= 0 {
t.Fatalf("expected more than %d documents; got: %d", 0, sourceCount)
}
res, err := client.UpdateByQuery(testIndexName).ProceedOnVersionConflict().Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("response is nil")
}
if res.Updated != sourceCount {
t.Fatalf("expected %d; got: %d", sourceCount, res.Updated)
}
}
func TestUpdateByQueryAsync(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t) //, SetTraceLog(log.New(os.Stdout, "", 0)))
esversion, err := client.ElasticsearchVersion(DefaultURL)
if err != nil {
t.Fatal(err)
}
if esversion < "2.3.0" {
t.Skipf("Elasticsearch %v does not support update-by-query yet", esversion)
}
sourceCount, err := client.Count(testIndexName).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if sourceCount <= 0 {
t.Fatalf("expected more than %d documents; got: %d", 0, sourceCount)
}
res, err := client.UpdateByQuery(testIndexName).
ProceedOnVersionConflict().
Slices("auto").
DoAsync(context.TODO())
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected result != nil")
}
if res.TaskId == "" {
t.Errorf("expected a task id, got %+v", res)
}
tasksGetTask := client.TasksGetTask()
taskStatus, err := tasksGetTask.TaskId(res.TaskId).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if taskStatus == nil {
t.Fatal("expected task status result != nil")
}
}
func TestUpdateByQueryConflict(t *testing.T) {
fail := func(r *http.Request) (*http.Response, error) {
body := `{
"took": 3,
"timed_out": false,
"total": 1,
"updated": 0,
"deleted": 0,
"batches": 1,
"version_conflicts": 1,
"noops": 0,
"retries": {
"bulk": 0,
"search": 0
},
"throttled_millis": 0,
"requests_per_second": -1,
"throttled_until_millis": 0,
"failures": [
{
"index": "a",
"type": "_doc",
"id": "yjsmdGsBm363wfQmSbhj",
"cause": {
"type": "version_conflict_engine_exception",
"reason": "[_doc][yjsmdGsBm363wfQmSbhj]: version conflict, current version [4] is different than the one provided [3]",
"index_uuid": "1rmL3mt8TimwshF-M1DxdQ",
"shard": "0",
"index": "a"
},
"status": 409
}
]
}`
return &http.Response{
StatusCode: http.StatusConflict,
Body: ioutil.NopCloser(strings.NewReader(body)),
ContentLength: int64(len(body)),
}, nil
}
// Run against a failing endpoint and see if PerformRequest
// retries correctly.
tr := &failingTransport{path: "/example/_update_by_query", fail: fail}
httpClient := &http.Client{Transport: tr}
client, err := NewClient(SetHttpClient(httpClient), SetHealthcheck(false))
if err != nil {
t.Fatal(err)
}
res, err := client.UpdateByQuery("example").ProceedOnVersionConflict().Do(context.TODO())
if err != nil {
t.Fatalf("mock should not be failed %+v", err)
}
if res.Took != 3 {
t.Errorf("took should be 3, got %d", res.Took)
}
if res.Total != 1 {
t.Errorf("total should be 1, got %d", res.Total)
}
if res.VersionConflicts != 1 {
t.Errorf("total should be 1, got %d", res.VersionConflicts)
}
if len(res.Failures) != 1 {
t.Errorf("failures length should be 1, got %d", len(res.Failures))
}
expected := bulkIndexByScrollResponseFailure{Index: "a", Type: "_doc", Id: "yjsmdGsBm363wfQmSbhj", Status: 409}
if res.Failures[0] != expected {
t.Errorf("failures should be %+v, got %+v", expected, res.Failures[0])
}
}
================================================
FILE: update_integration_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"testing"
"time"
)
func TestUpdateWithDoc(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t) // , SetTraceLog(log.New(os.Stdout, "", 0)))
// Get original
getRes, err := client.Get().Index(testIndexName).Id("1").Do(context.TODO())
if err != nil {
t.Fatal(err)
}
var original tweet
if err := json.Unmarshal(getRes.Source, &original); err != nil {
t.Fatal(err)
}
// Partial update
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
updRes, err := client.Update().
Index(testIndexName).
Id("1").
Doc(map[string]interface{}{
"message": "Updated message text.",
}).
Do(ctx)
if err != nil {
t.Fatal(err)
}
if updRes == nil {
t.Fatal("response is nil")
}
if want, have := "updated", updRes.Result; want != have {
t.Fatalf("want Result = %q, have %v", want, have)
}
// Get new version
getRes, err = client.Get().Index(testIndexName).Id("1").Do(context.TODO())
if err != nil {
t.Fatal(err)
}
var updated tweet
if err := json.Unmarshal(getRes.Source, &updated); err != nil {
t.Fatal(err)
}
if want, have := original.User, updated.User; want != have {
t.Fatalf("want User = %q, have %v", want, have)
}
if want, have := "Updated message text.", updated.Message; want != have {
t.Fatalf("want Message = %q, have %v", want, have)
}
}
func TestUpdateWithScript(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t) // , SetTraceLog(log.New(os.Stdout, "", 0)))
// Get original
getRes, err := client.Get().Index(testIndexName).Id("1").Do(context.TODO())
if err != nil {
t.Fatal(err)
}
var original tweet
if err := json.Unmarshal(getRes.Source, &original); err != nil {
t.Fatal(err)
}
// Update with script
updRes, err := client.Update().Index(testIndexName).Id("1").
Script(
NewScript(`ctx._source.message = "Updated message text."`).Lang("painless"),
).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if updRes == nil {
t.Fatal("response is nil")
}
if want, have := "updated", updRes.Result; want != have {
t.Fatalf("want Result = %q, have %v", want, have)
}
// Get new version
getRes, err = client.Get().Index(testIndexName).Id("1").Do(context.TODO())
if err != nil {
t.Fatal(err)
}
var updated tweet
if err := json.Unmarshal(getRes.Source, &updated); err != nil {
t.Fatal(err)
}
if want, have := original.User, updated.User; want != have {
t.Fatalf("want User = %q, have %v", want, have)
}
if want, have := "Updated message text.", updated.Message; want != have {
t.Fatalf("want Message = %q, have %v", want, have)
}
}
func TestUpdateWithScriptID(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t) //, SetTraceLog(log.New(os.Stdout, "", 0)))
// Get original
getRes, err := client.Get().Index(testIndexName).Id("1").Do(context.TODO())
if err != nil {
t.Fatal(err)
}
var original tweet
if err := json.Unmarshal(getRes.Source, &original); err != nil {
t.Fatal(err)
}
// Set script with ID
scriptID := "example-script-id"
_, err = client.DeleteScript().Id(scriptID).Do(context.Background())
if err != nil && !IsNotFound(err) {
t.Fatal(err)
}
_, err = client.PutScript().
Id(scriptID).
BodyString(`{
"script": {
"lang": "painless",
"source": "ctx._source.message = params.new_message"
}
}`).
Do(context.Background())
if err != nil {
t.Fatal(err)
}
// Update with script
updRes, err := client.Update().Index(testIndexName).Id("1").
Script(
NewScriptStored(scriptID).Param("new_message", "Updated message text."),
).
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if updRes == nil {
t.Fatal("response is nil")
}
if want, have := "updated", updRes.Result; want != have {
t.Fatalf("want Result = %q, have %v", want, have)
}
// Get new version
getRes, err = client.Get().Index(testIndexName).Id("1").Do(context.TODO())
if err != nil {
t.Fatal(err)
}
var updated tweet
if err := json.Unmarshal(getRes.Source, &updated); err != nil {
t.Fatal(err)
}
if want, have := original.User, updated.User; want != have {
t.Fatalf("want User = %q, have %v", want, have)
}
if want, have := "Updated message text.", updated.Message; want != have {
t.Fatalf("want Message = %q, have %v", want, have)
}
}
================================================
FILE: update_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"net/url"
"testing"
)
func TestUpdateViaScript(t *testing.T) {
client := setupTestClient(t) // , SetTraceLog(log.New(os.Stdout, "", 0)))
update := client.Update().
Index("test").Type("type1").Id("1").
Script(NewScript("ctx._source.tags += params.tag").Params(map[string]interface{}{"tag": "blue"}).Lang("groovy"))
path, params, err := update.url()
if err != nil {
t.Fatalf("expected to return URL, got: %v", err)
}
expectedPath := `/test/type1/1/_update`
if expectedPath != path {
t.Errorf("expected URL path\n%s\ngot:\n%s", expectedPath, path)
}
expectedParams := url.Values{}
if expectedParams.Encode() != params.Encode() {
t.Errorf("expected URL parameters\n%s\ngot:\n%s", expectedParams.Encode(), params.Encode())
}
body, err := update.body()
if err != nil {
t.Fatalf("expected to return body, got: %v", err)
}
data, err := json.Marshal(body)
if err != nil {
t.Fatalf("expected to marshal body as JSON, got: %v", err)
}
got := string(data)
expected := `{"script":{"lang":"groovy","params":{"tag":"blue"},"source":"ctx._source.tags += params.tag"}}`
if got != expected {
t.Errorf("expected\n%s\ngot:\n%s", expected, got)
}
}
func TestUpdateViaScriptId(t *testing.T) {
client := setupTestClient(t) // , SetTraceLog(log.New(os.Stdout, "", 0)))
scriptParams := map[string]interface{}{
"pageViewEvent": map[string]interface{}{
"url": "foo.com/bar",
"response": 404,
"time": "2014-01-01 12:32",
},
}
script := NewScriptStored("my_web_session_summariser").Params(scriptParams)
update := client.Update().
Index("sessions").Type("session").Id("dh3sgudg8gsrgl").
Script(script).
ScriptedUpsert(true).
Upsert(map[string]interface{}{})
path, params, err := update.url()
if err != nil {
t.Fatalf("expected to return URL, got: %v", err)
}
expectedPath := `/sessions/session/dh3sgudg8gsrgl/_update`
if expectedPath != path {
t.Errorf("expected URL path\n%s\ngot:\n%s", expectedPath, path)
}
expectedParams := url.Values{}
if expectedParams.Encode() != params.Encode() {
t.Errorf("expected URL parameters\n%s\ngot:\n%s", expectedParams.Encode(), params.Encode())
}
body, err := update.body()
if err != nil {
t.Fatalf("expected to return body, got: %v", err)
}
data, err := json.Marshal(body)
if err != nil {
t.Fatalf("expected to marshal body as JSON, got: %v", err)
}
got := string(data)
expected := `{"script":{"id":"my_web_session_summariser","params":{"pageViewEvent":{"response":404,"time":"2014-01-01 12:32","url":"foo.com/bar"}}},"scripted_upsert":true,"upsert":{}}`
if got != expected {
t.Errorf("expected\n%s\ngot:\n%s", expected, got)
}
}
func TestUpdateViaScriptAndUpsert(t *testing.T) {
client := setupTestClient(t) // , SetTraceLog(log.New(os.Stdout, "", 0)))
update := client.Update().
Index("test").Type("type1").Id("1").
Script(NewScript("ctx._source.counter += params.count").Params(map[string]interface{}{"count": 4})).
Upsert(map[string]interface{}{"counter": 1})
path, params, err := update.url()
if err != nil {
t.Fatalf("expected to return URL, got: %v", err)
}
expectedPath := `/test/type1/1/_update`
if expectedPath != path {
t.Errorf("expected URL path\n%s\ngot:\n%s", expectedPath, path)
}
expectedParams := url.Values{}
if expectedParams.Encode() != params.Encode() {
t.Errorf("expected URL parameters\n%s\ngot:\n%s", expectedParams.Encode(), params.Encode())
}
body, err := update.body()
if err != nil {
t.Fatalf("expected to return body, got: %v", err)
}
data, err := json.Marshal(body)
if err != nil {
t.Fatalf("expected to marshal body as JSON, got: %v", err)
}
got := string(data)
expected := `{"script":{"params":{"count":4},"source":"ctx._source.counter += params.count"},"upsert":{"counter":1}}`
if got != expected {
t.Errorf("expected\n%s\ngot:\n%s", expected, got)
}
}
func TestUpdateViaDoc(t *testing.T) {
client := setupTestClient(t) // , SetTraceLog(log.New(os.Stdout, "", 0)))
update := client.Update().
Index("test").Type("type1").Id("1").
Doc(map[string]interface{}{"name": "new_name"}).
DetectNoop(true)
path, params, err := update.url()
if err != nil {
t.Fatalf("expected to return URL, got: %v", err)
}
expectedPath := `/test/type1/1/_update`
if expectedPath != path {
t.Errorf("expected URL path\n%s\ngot:\n%s", expectedPath, path)
}
expectedParams := url.Values{}
if expectedParams.Encode() != params.Encode() {
t.Errorf("expected URL parameters\n%s\ngot:\n%s", expectedParams.Encode(), params.Encode())
}
body, err := update.body()
if err != nil {
t.Fatalf("expected to return body, got: %v", err)
}
data, err := json.Marshal(body)
if err != nil {
t.Fatalf("expected to marshal body as JSON, got: %v", err)
}
got := string(data)
expected := `{"detect_noop":true,"doc":{"name":"new_name"}}`
if got != expected {
t.Errorf("expected\n%s\ngot:\n%s", expected, got)
}
}
func TestUpdateViaDocAndUpsert(t *testing.T) {
client := setupTestClient(t) // , SetTraceLog(log.New(os.Stdout, "", 0)))
update := client.Update().
Index("test").Type("type1").Id("1").
Doc(map[string]interface{}{"name": "new_name"}).
DocAsUpsert(true).
Timeout("1s").
Refresh("true")
path, params, err := update.url()
if err != nil {
t.Fatalf("expected to return URL, got: %v", err)
}
expectedPath := `/test/type1/1/_update`
if expectedPath != path {
t.Errorf("expected URL path\n%s\ngot:\n%s", expectedPath, path)
}
expectedParams := url.Values{"refresh": []string{"true"}, "timeout": []string{"1s"}}
if expectedParams.Encode() != params.Encode() {
t.Errorf("expected URL parameters\n%s\ngot:\n%s", expectedParams.Encode(), params.Encode())
}
body, err := update.body()
if err != nil {
t.Fatalf("expected to return body, got: %v", err)
}
data, err := json.Marshal(body)
if err != nil {
t.Fatalf("expected to marshal body as JSON, got: %v", err)
}
got := string(data)
expected := `{"doc":{"name":"new_name"},"doc_as_upsert":true}`
if got != expected {
t.Errorf("expected\n%s\ngot:\n%s", expected, got)
}
}
func TestUpdateViaDocAndUpsertAndFetchSource(t *testing.T) {
client := setupTestClient(t) // , SetTraceLog(log.New(os.Stdout, "", 0)))
update := client.Update().
Index("test").Type("type1").Id("1").
Doc(map[string]interface{}{"name": "new_name"}).
DocAsUpsert(true).
Timeout("1s").
Refresh("true").
FetchSource(true)
path, params, err := update.url()
if err != nil {
t.Fatalf("expected to return URL, got: %v", err)
}
expectedPath := `/test/type1/1/_update`
if expectedPath != path {
t.Errorf("expected URL path\n%s\ngot:\n%s", expectedPath, path)
}
expectedParams := url.Values{
"refresh": []string{"true"},
"timeout": []string{"1s"},
}
if expectedParams.Encode() != params.Encode() {
t.Errorf("expected URL parameters\n%s\ngot:\n%s", expectedParams.Encode(), params.Encode())
}
body, err := update.body()
if err != nil {
t.Fatalf("expected to return body, got: %v", err)
}
data, err := json.Marshal(body)
if err != nil {
t.Fatalf("expected to marshal body as JSON, got: %v", err)
}
got := string(data)
expected := `{"_source":true,"doc":{"name":"new_name"},"doc_as_upsert":true}`
if got != expected {
t.Errorf("expected\n%s\ngot:\n%s", expected, got)
}
}
func TestUpdateAndFetchSource(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t) // , SetTraceLog(log.New(os.Stdout, "", 0)))
res, err := client.Update().
Index(testIndexName).Id("1").
Doc(map[string]interface{}{"user": "sandrae"}).
DetectNoop(true).
FetchSource(true).
Do(context.Background())
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected response != nil")
}
if res.GetResult == nil {
t.Fatal("expected GetResult != nil")
}
data, err := json.Marshal(res.GetResult.Source)
if err != nil {
t.Fatalf("expected to marshal body as JSON, got: %v", err)
}
got := string(data)
expected := `{"user":"sandrae","message":"Welcome to Golang and Elasticsearch.","retweets":108,"created":"0001-01-01T00:00:00Z","tags":["golang","elasticsearch"]}`
if got != expected {
t.Errorf("expected\n%s\ngot:\n%s", expected, got)
}
}
func TestUpdateOptimistic(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t) //, SetTraceLog(log.New(os.Stdout, "", 0)))
doc, err := client.Get().
Index(testIndexName).Id("1").
Do(context.Background())
if err != nil {
t.Fatal(err)
}
if doc.SeqNo == nil {
t.Fatal("expected seq_no != nil")
}
if doc.PrimaryTerm == nil {
t.Fatal("expected primary_term != nil")
}
// Update with seqNo != doc.SeqNo and primaryTerm != doc.PrimaryTerm
_, err = client.Update().
Index(testIndexName).Id(doc.Id).
Doc(map[string]interface{}{"user": "sandrae"}).
IfSeqNo(*doc.SeqNo + 1000).
IfPrimaryTerm(*doc.PrimaryTerm + 1000).
Do(context.Background())
if err == nil {
t.Fatal("expected error, got nil")
}
if !IsConflict(err) {
t.Fatalf("expected conflict error, got %v (%T)", err, err)
}
// Update with seqNo == doc.SeqNo and primaryTerm == doc.PrimaryTerm
res, err := client.Update().
Index(testIndexName).Id(doc.Id).
Doc(map[string]interface{}{"user": "sandrae"}).
IfSeqNo(*doc.SeqNo).
IfPrimaryTerm(*doc.PrimaryTerm).
FetchSource(true).
Do(context.Background())
if err != nil {
t.Fatal(err)
}
if res == nil {
t.Fatal("expected response != nil")
}
if res.GetResult == nil {
t.Fatal("expected GetResult != nil")
}
data, err := json.Marshal(res.GetResult.Source)
if err != nil {
t.Fatalf("expected to marshal body as JSON, got: %v", err)
}
got := string(data)
expected := `{"user":"sandrae","message":"Welcome to Golang and Elasticsearch.","retweets":108,"created":"0001-01-01T00:00:00Z","tags":["golang","elasticsearch"]}`
if got != expected {
t.Errorf("expected\n%s\ngot:\n%s", expected, got)
}
}
================================================
FILE: uritemplates/LICENSE
================================================
Copyright (c) 2013 Joshua Tacoma
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: uritemplates/uritemplates.go
================================================
// Copyright 2013 Joshua Tacoma. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package uritemplates is a level 4 implementation of RFC 6570 (URI
// Template, http://tools.ietf.org/html/rfc6570).
//
// To use uritemplates, parse a template string and expand it with a value
// map:
//
// template, _ := uritemplates.Parse("https://api.github.com/repos{/user,repo}")
// values := make(map[string]interface{})
// values["user"] = "jtacoma"
// values["repo"] = "uritemplates"
// expanded, _ := template.Expand(values)
// fmt.Printf(expanded)
//
package uritemplates
import (
"bytes"
"errors"
"fmt"
"reflect"
"regexp"
"strconv"
"strings"
)
var (
unreserved = regexp.MustCompile("[^A-Za-z0-9\\-._~]")
reserved = regexp.MustCompile("[^A-Za-z0-9\\-._~:/?#[\\]@!$&'()*+,;=]")
validname = regexp.MustCompile("^([A-Za-z0-9_\\.]|%[0-9A-Fa-f][0-9A-Fa-f])+$")
hex = []byte("0123456789ABCDEF")
)
func pctEncode(src []byte) []byte {
dst := make([]byte, len(src)*3)
for i, b := range src {
buf := dst[i*3 : i*3+3]
buf[0] = 0x25
buf[1] = hex[b/16]
buf[2] = hex[b%16]
}
return dst
}
func escape(s string, allowReserved bool) (escaped string) {
if allowReserved {
escaped = string(reserved.ReplaceAllFunc([]byte(s), pctEncode))
} else {
escaped = string(unreserved.ReplaceAllFunc([]byte(s), pctEncode))
}
return escaped
}
// A UriTemplate is a parsed representation of a URI template.
type UriTemplate struct {
raw string
parts []templatePart
}
// Parse parses a URI template string into a UriTemplate object.
func Parse(rawtemplate string) (template *UriTemplate, err error) {
template = new(UriTemplate)
template.raw = rawtemplate
split := strings.Split(rawtemplate, "{")
template.parts = make([]templatePart, len(split)*2-1)
for i, s := range split {
if i == 0 {
if strings.Contains(s, "}") {
err = errors.New("unexpected }")
break
}
template.parts[i].raw = s
} else {
subsplit := strings.Split(s, "}")
if len(subsplit) != 2 {
err = errors.New("malformed template")
break
}
expression := subsplit[0]
template.parts[i*2-1], err = parseExpression(expression)
if err != nil {
break
}
template.parts[i*2].raw = subsplit[1]
}
}
if err != nil {
template = nil
}
return template, err
}
type templatePart struct {
raw string
terms []templateTerm
first string
sep string
named bool
ifemp string
allowReserved bool
}
type templateTerm struct {
name string
explode bool
truncate int
}
func parseExpression(expression string) (result templatePart, err error) {
switch expression[0] {
case '+':
result.sep = ","
result.allowReserved = true
expression = expression[1:]
case '.':
result.first = "."
result.sep = "."
expression = expression[1:]
case '/':
result.first = "/"
result.sep = "/"
expression = expression[1:]
case ';':
result.first = ";"
result.sep = ";"
result.named = true
expression = expression[1:]
case '?':
result.first = "?"
result.sep = "&"
result.named = true
result.ifemp = "="
expression = expression[1:]
case '&':
result.first = "&"
result.sep = "&"
result.named = true
result.ifemp = "="
expression = expression[1:]
case '#':
result.first = "#"
result.sep = ","
result.allowReserved = true
expression = expression[1:]
default:
result.sep = ","
}
rawterms := strings.Split(expression, ",")
result.terms = make([]templateTerm, len(rawterms))
for i, raw := range rawterms {
result.terms[i], err = parseTerm(raw)
if err != nil {
break
}
}
return result, err
}
func parseTerm(term string) (result templateTerm, err error) {
if strings.HasSuffix(term, "*") {
result.explode = true
term = term[:len(term)-1]
}
split := strings.Split(term, ":")
if len(split) == 1 {
result.name = term
} else if len(split) == 2 {
result.name = split[0]
var parsed int64
parsed, err = strconv.ParseInt(split[1], 10, 0)
result.truncate = int(parsed)
} else {
err = errors.New("multiple colons in same term")
}
if !validname.MatchString(result.name) {
err = errors.New("not a valid name: " + result.name)
}
if result.explode && result.truncate > 0 {
err = errors.New("both explode and prefix modifers on same term")
}
return result, err
}
// Expand expands a URI template with a set of values to produce a string.
func (self *UriTemplate) Expand(value interface{}) (string, error) {
values, ismap := value.(map[string]interface{})
if !ismap {
if m, ismap := struct2map(value); !ismap {
return "", errors.New("expected map[string]interface{}, struct, or pointer to struct.")
} else {
return self.Expand(m)
}
}
var buf bytes.Buffer
for _, p := range self.parts {
err := p.expand(&buf, values)
if err != nil {
return "", err
}
}
return buf.String(), nil
}
func (self *templatePart) expand(buf *bytes.Buffer, values map[string]interface{}) error {
if len(self.raw) > 0 {
buf.WriteString(self.raw)
return nil
}
var zeroLen = buf.Len()
buf.WriteString(self.first)
var firstLen = buf.Len()
for _, term := range self.terms {
value, exists := values[term.name]
if !exists {
continue
}
if buf.Len() != firstLen {
buf.WriteString(self.sep)
}
switch v := value.(type) {
case string:
self.expandString(buf, term, v)
case []interface{}:
self.expandArray(buf, term, v)
case map[string]interface{}:
if term.truncate > 0 {
return errors.New("cannot truncate a map expansion")
}
self.expandMap(buf, term, v)
default:
if m, ismap := struct2map(value); ismap {
if term.truncate > 0 {
return errors.New("cannot truncate a map expansion")
}
self.expandMap(buf, term, m)
} else {
str := fmt.Sprintf("%v", value)
self.expandString(buf, term, str)
}
}
}
if buf.Len() == firstLen {
original := buf.Bytes()[:zeroLen]
buf.Reset()
buf.Write(original)
}
return nil
}
func (self *templatePart) expandName(buf *bytes.Buffer, name string, empty bool) {
if self.named {
buf.WriteString(name)
if empty {
buf.WriteString(self.ifemp)
} else {
buf.WriteString("=")
}
}
}
func (self *templatePart) expandString(buf *bytes.Buffer, t templateTerm, s string) {
if len(s) > t.truncate && t.truncate > 0 {
s = s[:t.truncate]
}
self.expandName(buf, t.name, len(s) == 0)
buf.WriteString(escape(s, self.allowReserved))
}
func (self *templatePart) expandArray(buf *bytes.Buffer, t templateTerm, a []interface{}) {
if len(a) == 0 {
return
} else if !t.explode {
self.expandName(buf, t.name, false)
}
for i, value := range a {
if t.explode && i > 0 {
buf.WriteString(self.sep)
} else if i > 0 {
buf.WriteString(",")
}
var s string
switch v := value.(type) {
case string:
s = v
default:
s = fmt.Sprintf("%v", v)
}
if len(s) > t.truncate && t.truncate > 0 {
s = s[:t.truncate]
}
if self.named && t.explode {
self.expandName(buf, t.name, len(s) == 0)
}
buf.WriteString(escape(s, self.allowReserved))
}
}
func (self *templatePart) expandMap(buf *bytes.Buffer, t templateTerm, m map[string]interface{}) {
if len(m) == 0 {
return
}
if !t.explode {
self.expandName(buf, t.name, len(m) == 0)
}
var firstLen = buf.Len()
for k, value := range m {
if firstLen != buf.Len() {
if t.explode {
buf.WriteString(self.sep)
} else {
buf.WriteString(",")
}
}
var s string
switch v := value.(type) {
case string:
s = v
default:
s = fmt.Sprintf("%v", v)
}
if t.explode {
buf.WriteString(escape(k, self.allowReserved))
buf.WriteRune('=')
buf.WriteString(escape(s, self.allowReserved))
} else {
buf.WriteString(escape(k, self.allowReserved))
buf.WriteRune(',')
buf.WriteString(escape(s, self.allowReserved))
}
}
}
func struct2map(v interface{}) (map[string]interface{}, bool) {
value := reflect.ValueOf(v)
switch value.Type().Kind() {
case reflect.Ptr:
return struct2map(value.Elem().Interface())
case reflect.Struct:
m := make(map[string]interface{})
for i := 0; i < value.NumField(); i++ {
tag := value.Type().Field(i).Tag
var name string
if strings.Contains(string(tag), ":") {
name = tag.Get("uri")
} else {
name = strings.TrimSpace(string(tag))
}
if len(name) == 0 {
name = value.Type().Field(i).Name
}
m[name] = value.Field(i).Interface()
}
return m, true
}
return nil, false
}
================================================
FILE: uritemplates/utils.go
================================================
package uritemplates
func Expand(path string, expansions map[string]string) (string, error) {
template, err := Parse(path)
if err != nil {
return "", err
}
values := make(map[string]interface{})
for k, v := range expansions {
values[k] = v
}
return template.Expand(values)
}
================================================
FILE: uritemplates/utils_test.go
================================================
package uritemplates
import (
"testing"
)
type ExpandTest struct {
in string
expansions map[string]string
want string
}
var expandTests = []ExpandTest{
// #0: no expansions
{
"http://www.golang.org/",
map[string]string{},
"http://www.golang.org/",
},
// #1: one expansion, no escaping
{
"http://www.golang.org/{bucket}/delete",
map[string]string{
"bucket": "red",
},
"http://www.golang.org/red/delete",
},
// #2: one expansion, with hex escapes
{
"http://www.golang.org/{bucket}/delete",
map[string]string{
"bucket": "red/blue",
},
"http://www.golang.org/red%2Fblue/delete",
},
// #3: one expansion, with space
{
"http://www.golang.org/{bucket}/delete",
map[string]string{
"bucket": "red or blue",
},
"http://www.golang.org/red%20or%20blue/delete",
},
// #4: expansion not found
{
"http://www.golang.org/{object}/delete",
map[string]string{
"bucket": "red or blue",
},
"http://www.golang.org//delete",
},
// #5: multiple expansions
{
"http://www.golang.org/{one}/{two}/{three}/get",
map[string]string{
"one": "ONE",
"two": "TWO",
"three": "THREE",
},
"http://www.golang.org/ONE/TWO/THREE/get",
},
// #6: utf-8 characters
{
"http://www.golang.org/{bucket}/get",
map[string]string{
"bucket": "£100",
},
"http://www.golang.org/%C2%A3100/get",
},
// #7: punctuations
{
"http://www.golang.org/{bucket}/get",
map[string]string{
"bucket": `/\@:,.*~`,
},
"http://www.golang.org/%2F%5C%40%3A%2C.%2A~/get",
},
// #8: mis-matched brackets
{
"http://www.golang.org/{bucket/get",
map[string]string{
"bucket": "red",
},
"",
},
// #9: "+" prefix for suppressing escape
// See also: http://tools.ietf.org/html/rfc6570#section-3.2.3
{
"http://www.golang.org/{+topic}",
map[string]string{
"topic": "/topics/myproject/mytopic",
},
// The double slashes here look weird, but it's intentional
"http://www.golang.org//topics/myproject/mytopic",
},
}
func TestExpand(t *testing.T) {
for i, test := range expandTests {
got, _ := Expand(test.in, test.expansions)
if got != test.want {
t.Errorf("got %q expected %q in test %d", got, test.want, i)
}
}
}
================================================
FILE: validate.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// ValidateService allows a user to validate a potentially
// expensive query without executing it.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-validate.html.
type ValidateService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
index []string
typ []string
q string
explain *bool
rewrite *bool
allShards *bool
lenient *bool
analyzer string
df string
analyzeWildcard *bool
defaultOperator string
ignoreUnavailable *bool
allowNoIndices *bool
expandWildcards string
bodyJson interface{}
bodyString string
}
// NewValidateService creates a new ValidateService.
func NewValidateService(client *Client) *ValidateService {
return &ValidateService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *ValidateService) Pretty(pretty bool) *ValidateService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *ValidateService) Human(human bool) *ValidateService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *ValidateService) ErrorTrace(errorTrace bool) *ValidateService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *ValidateService) FilterPath(filterPath ...string) *ValidateService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *ValidateService) Header(name string, value string) *ValidateService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *ValidateService) Headers(headers http.Header) *ValidateService {
s.headers = headers
return s
}
// Index sets the names of the indices to use for search.
func (s *ValidateService) Index(index ...string) *ValidateService {
s.index = append(s.index, index...)
return s
}
// Type adds search restrictions for a list of types.
//
// Deprecated: Types are in the process of being removed. Instead of using a type, prefer to
// filter on a field on the document.
func (s *ValidateService) Type(typ ...string) *ValidateService {
s.typ = append(s.typ, typ...)
return s
}
// Lenient specifies whether format-based query failures
// (such as providing text to a numeric field) should be ignored.
func (s *ValidateService) Lenient(lenient bool) *ValidateService {
s.lenient = &lenient
return s
}
// Query in the Lucene query string syntax.
func (s *ValidateService) Q(q string) *ValidateService {
s.q = q
return s
}
// An explain parameter can be specified to get more detailed information about why a query failed.
func (s *ValidateService) Explain(explain *bool) *ValidateService {
s.explain = explain
return s
}
// Provide a more detailed explanation showing the actual Lucene query that will be executed.
func (s *ValidateService) Rewrite(rewrite *bool) *ValidateService {
s.rewrite = rewrite
return s
}
// Execute validation on all shards instead of one random shard per index.
func (s *ValidateService) AllShards(allShards *bool) *ValidateService {
s.allShards = allShards
return s
}
// AnalyzeWildcard specifies whether wildcards and prefix queries
// in the query string query should be analyzed (default: false).
func (s *ValidateService) AnalyzeWildcard(analyzeWildcard bool) *ValidateService {
s.analyzeWildcard = &analyzeWildcard
return s
}
// Analyzer is the analyzer for the query string query.
func (s *ValidateService) Analyzer(analyzer string) *ValidateService {
s.analyzer = analyzer
return s
}
// Df is the default field for query string query (default: _all).
func (s *ValidateService) Df(df string) *ValidateService {
s.df = df
return s
}
// DefaultOperator is the default operator for query string query (AND or OR).
func (s *ValidateService) DefaultOperator(defaultOperator string) *ValidateService {
s.defaultOperator = defaultOperator
return s
}
// Query sets a query definition using the Query DSL.
func (s *ValidateService) Query(query Query) *ValidateService {
src, err := query.Source()
if err != nil {
// Do nothing in case of an error
return s
}
body := make(map[string]interface{})
body["query"] = src
s.bodyJson = body
return s
}
// IgnoreUnavailable indicates whether the specified concrete indices
// should be ignored when unavailable (missing or closed).
func (s *ValidateService) IgnoreUnavailable(ignoreUnavailable bool) *ValidateService {
s.ignoreUnavailable = &ignoreUnavailable
return s
}
// AllowNoIndices indicates whether to ignore if a wildcard indices
// expression resolves into no concrete indices. (This includes `_all` string
// or when no indices have been specified).
func (s *ValidateService) AllowNoIndices(allowNoIndices bool) *ValidateService {
s.allowNoIndices = &allowNoIndices
return s
}
// ExpandWildcards indicates whether to expand wildcard expression to
// concrete indices that are open, closed or both.
func (s *ValidateService) ExpandWildcards(expandWildcards string) *ValidateService {
s.expandWildcards = expandWildcards
return s
}
// BodyJson sets the query definition using the Query DSL.
func (s *ValidateService) BodyJson(body interface{}) *ValidateService {
s.bodyJson = body
return s
}
// BodyString sets the query definition using the Query DSL as a string.
func (s *ValidateService) BodyString(body string) *ValidateService {
s.bodyString = body
return s
}
// buildURL builds the URL for the operation.
func (s *ValidateService) buildURL() (string, url.Values, error) {
var err error
var path string
// Build URL
if len(s.index) > 0 && len(s.typ) > 0 {
path, err = uritemplates.Expand("/{index}/{type}/_validate/query", map[string]string{
"index": strings.Join(s.index, ","),
"type": strings.Join(s.typ, ","),
})
} else if len(s.index) > 0 {
path, err = uritemplates.Expand("/{index}/_validate/query", map[string]string{
"index": strings.Join(s.index, ","),
})
} else {
path, err = uritemplates.Expand("/_validate/query", map[string]string{
"type": strings.Join(s.typ, ","),
})
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.explain != nil {
params.Set("explain", fmt.Sprintf("%v", *s.explain))
}
if s.rewrite != nil {
params.Set("rewrite", fmt.Sprintf("%v", *s.rewrite))
}
if s.allShards != nil {
params.Set("all_shards", fmt.Sprintf("%v", *s.allShards))
}
if s.defaultOperator != "" {
params.Set("default_operator", s.defaultOperator)
}
if v := s.lenient; v != nil {
params.Set("lenient", fmt.Sprint(*v))
}
if s.q != "" {
params.Set("q", s.q)
}
if v := s.analyzeWildcard; v != nil {
params.Set("analyze_wildcard", fmt.Sprint(*v))
}
if s.analyzer != "" {
params.Set("analyzer", s.analyzer)
}
if s.df != "" {
params.Set("df", s.df)
}
if v := s.allowNoIndices; v != nil {
params.Set("allow_no_indices", fmt.Sprint(*v))
}
if s.expandWildcards != "" {
params.Set("expand_wildcards", s.expandWildcards)
}
if v := s.ignoreUnavailable; v != nil {
params.Set("ignore_unavailable", fmt.Sprint(*v))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *ValidateService) Validate() error {
return nil
}
// Do executes the operation.
func (s *ValidateService) Do(ctx context.Context) (*ValidateResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Setup HTTP request body
var body interface{}
if s.bodyJson != nil {
body = s.bodyJson
} else {
body = s.bodyString
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Body: body,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(ValidateResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// ValidateResponse is the response of ValidateService.Do.
type ValidateResponse struct {
Valid bool `json:"valid"`
Shards map[string]interface{} `json:"_shards"`
Explanations []interface{} `json:"explanations"`
}
================================================
FILE: validate_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestValidate(t *testing.T) {
client := setupTestClientAndCreateIndex(t)
tweet1 := tweet{User: "olivere", Message: "Welcome to Golang and Elasticsearch."}
// Add a document
indexResult, err := client.Index().
Index(testIndexName).
BodyJson(&tweet1).
Refresh("true").
Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if indexResult == nil {
t.Errorf("expected result to be != nil; got: %v", indexResult)
}
query := NewTermQuery("user", "olivere")
explain := true
valid, err := client.Validate(testIndexName).Explain(&explain).Query(query).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if valid == nil {
t.Fatal("expected to return an validation")
}
if !valid.Valid {
t.Errorf("expected valid to be %v; got: %v", true, valid.Valid)
}
invalidQuery := NewTermQuery("", false)
valid, err = client.Validate(testIndexName).Query(invalidQuery).Do(context.TODO())
if err != nil {
t.Fatal(err)
}
if valid == nil {
t.Fatal("expected to return an validation")
}
if valid.Valid {
t.Errorf("expected valid to be %v; got: %v", false, valid.Valid)
}
}
================================================
FILE: xpack_async_search_delete.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
)
// XPackAsyncSearchDelete allows removing an asynchronous search result,
// previously being started with XPackAsyncSearchSubmit service.
//
// For more details, see the documentation at
// https://www.elastic.co/guide/en/elasticsearch/reference/7.9/async-search.html
type XPackAsyncSearchDelete struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
// ID of asynchronous search as returned by XPackAsyncSearchSubmit.Do.
id string
}
// NewXPackAsyncSearchDelete creates a new XPackAsyncSearchDelete.
func NewXPackAsyncSearchDelete(client *Client) *XPackAsyncSearchDelete {
return &XPackAsyncSearchDelete{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *XPackAsyncSearchDelete) Pretty(pretty bool) *XPackAsyncSearchDelete {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *XPackAsyncSearchDelete) Human(human bool) *XPackAsyncSearchDelete {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *XPackAsyncSearchDelete) ErrorTrace(errorTrace bool) *XPackAsyncSearchDelete {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *XPackAsyncSearchDelete) FilterPath(filterPath ...string) *XPackAsyncSearchDelete {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *XPackAsyncSearchDelete) Header(name string, value string) *XPackAsyncSearchDelete {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *XPackAsyncSearchDelete) Headers(headers http.Header) *XPackAsyncSearchDelete {
s.headers = headers
return s
}
// ID of the asynchronous search.
func (s *XPackAsyncSearchDelete) ID(id string) *XPackAsyncSearchDelete {
s.id = id
return s
}
// buildURL builds the URL for the operation.
func (s *XPackAsyncSearchDelete) buildURL() (string, url.Values, error) {
path := fmt.Sprintf("/_async_search/%s", url.PathEscape(s.id))
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *XPackAsyncSearchDelete) Validate() error {
var invalid []string
if s.id == "" {
invalid = append(invalid, "ID")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *XPackAsyncSearchDelete) Do(ctx context.Context) (*XPackAsyncSearchDeleteResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "DELETE",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(XPackAsyncSearchDeleteResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// XPackAsyncSearchDeleteResponse is the outcome of calling XPackAsyncSearchDelete.Do.
type XPackAsyncSearchDeleteResponse struct {
Acknowledged bool `json:"acknowledged"`
}
================================================
FILE: xpack_async_search_get.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
)
// XPackAsyncSearchGet allows retrieving an asynchronous search result,
// previously being started with XPackAsyncSearchSubmit service.
//
// For more details, see the documentation at
// https://www.elastic.co/guide/en/elasticsearch/reference/7.9/async-search.html
type XPackAsyncSearchGet struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
// ID of asynchronous search as returned by XPackAsyncSearchSubmit.Do.
id string
// waitForCompletionTimeout is the duration the call should wait for a result
// before timing out. The default is 1 second.
waitForCompletionTimeout string
// keepAlive asks Elasticsearch to keep the ID and its results even
// after the search has been completed.
keepAlive string
}
// NewXPackAsyncSearchGet creates a new XPackAsyncSearchGet.
func NewXPackAsyncSearchGet(client *Client) *XPackAsyncSearchGet {
return &XPackAsyncSearchGet{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *XPackAsyncSearchGet) Pretty(pretty bool) *XPackAsyncSearchGet {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *XPackAsyncSearchGet) Human(human bool) *XPackAsyncSearchGet {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *XPackAsyncSearchGet) ErrorTrace(errorTrace bool) *XPackAsyncSearchGet {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *XPackAsyncSearchGet) FilterPath(filterPath ...string) *XPackAsyncSearchGet {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *XPackAsyncSearchGet) Header(name string, value string) *XPackAsyncSearchGet {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *XPackAsyncSearchGet) Headers(headers http.Header) *XPackAsyncSearchGet {
s.headers = headers
return s
}
// ID of the asynchronous search.
func (s *XPackAsyncSearchGet) ID(id string) *XPackAsyncSearchGet {
s.id = id
return s
}
// WaitForCompletionTimeout specifies the time the service waits for retrieving
// a complete result. If the timeout expires, you'll get the current results which
// might not be complete.
func (s *XPackAsyncSearchGet) WaitForCompletionTimeout(waitForCompletionTimeout string) *XPackAsyncSearchGet {
s.waitForCompletionTimeout = waitForCompletionTimeout
return s
}
// KeepAlive is the time the search results are kept by Elasticsearch before
// being garbage collected.
func (s *XPackAsyncSearchGet) KeepAlive(keepAlive string) *XPackAsyncSearchGet {
s.keepAlive = keepAlive
return s
}
// buildURL builds the URL for the operation.
func (s *XPackAsyncSearchGet) buildURL() (string, url.Values, error) {
path := fmt.Sprintf("/_async_search/%s", url.PathEscape(s.id))
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.waitForCompletionTimeout != "" {
params.Set("wait_for_completion_timeout", s.waitForCompletionTimeout)
}
if s.keepAlive != "" {
params.Set("keep_alive", s.keepAlive)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *XPackAsyncSearchGet) Validate() error {
var invalid []string
if s.id == "" {
invalid = append(invalid, "ID")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *XPackAsyncSearchGet) Do(ctx context.Context) (*XPackAsyncSearchResult, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(XPackAsyncSearchResult)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
ret.Header = res.Header
return nil, err
}
ret.Header = res.Header
return ret, nil
}
================================================
FILE: xpack_async_search_submit.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"reflect"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// XPackAsyncSearchSubmit is an XPack API for asynchronously
// searching for documents in Elasticsearch.
//
// For more details, see the documentation at
// https://www.elastic.co/guide/en/elasticsearch/reference/7.9/async-search.html
type XPackAsyncSearchSubmit struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
searchSource *SearchSource // q
source interface{}
searchType string // search_type
index []string
typ []string
routing string // routing
preference string // preference
requestCache *bool // request_cache
ignoreUnavailable *bool // ignore_unavailable
ignoreThrottled *bool // ignore_throttled
allowNoIndices *bool // allow_no_indices
expandWildcards string // expand_wildcards
lenient *bool // lenient
maxResponseSize int64
allowPartialSearchResults *bool // allow_partial_search_results
typedKeys *bool // typed_keys
seqNoPrimaryTerm *bool // seq_no_primary_term
batchedReduceSize *int // batched_reduce_size
maxConcurrentShardRequests *int // max_concurrent_shard_requests
preFilterShardSize *int // pre_filter_shard_size
restTotalHitsAsInt *bool // rest_total_hits_as_int
ccsMinimizeRoundtrips *bool // ccs_minimize_roundtrips
waitForCompletionTimeout string // e.g. "1s"
keepOnCompletion *bool
keepAlive string // e.g. "1h"
}
// NewXPackAsyncSearchSubmit creates a new service for asynchronously
// searching in Elasticsearch.
func NewXPackAsyncSearchSubmit(client *Client) *XPackAsyncSearchSubmit {
builder := &XPackAsyncSearchSubmit{
client: client,
searchSource: NewSearchSource(),
}
return builder
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *XPackAsyncSearchSubmit) Pretty(pretty bool) *XPackAsyncSearchSubmit {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *XPackAsyncSearchSubmit) Human(human bool) *XPackAsyncSearchSubmit {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *XPackAsyncSearchSubmit) ErrorTrace(errorTrace bool) *XPackAsyncSearchSubmit {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *XPackAsyncSearchSubmit) FilterPath(filterPath ...string) *XPackAsyncSearchSubmit {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *XPackAsyncSearchSubmit) Header(name string, value string) *XPackAsyncSearchSubmit {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *XPackAsyncSearchSubmit) Headers(headers http.Header) *XPackAsyncSearchSubmit {
s.headers = headers
return s
}
// SearchSource sets the search source builder to use with this service.
func (s *XPackAsyncSearchSubmit) SearchSource(searchSource *SearchSource) *XPackAsyncSearchSubmit {
s.searchSource = searchSource
if s.searchSource == nil {
s.searchSource = NewSearchSource()
}
return s
}
// Source allows the user to set the request body manually without using
// any of the structs and interfaces in Elastic.
func (s *XPackAsyncSearchSubmit) Source(source interface{}) *XPackAsyncSearchSubmit {
s.source = source
return s
}
// Index sets the names of the indices to use for search.
func (s *XPackAsyncSearchSubmit) Index(index ...string) *XPackAsyncSearchSubmit {
s.index = append(s.index, index...)
return s
}
// Type adds search restrictions for a list of types.
//
// Deprecated: Types are in the process of being removed. Instead of using a type, prefer to
// filter on a field on the document.
func (s *XPackAsyncSearchSubmit) Type(typ ...string) *XPackAsyncSearchSubmit {
s.typ = append(s.typ, typ...)
return s
}
// Timeout sets the timeout to use, e.g. "1s" or "1000ms".
func (s *XPackAsyncSearchSubmit) Timeout(timeout string) *XPackAsyncSearchSubmit {
s.searchSource = s.searchSource.Timeout(timeout)
return s
}
// Profile sets the Profile API flag on the search source.
// When enabled, a search executed by this service will return query
// profiling data.
func (s *XPackAsyncSearchSubmit) Profile(profile bool) *XPackAsyncSearchSubmit {
s.searchSource = s.searchSource.Profile(profile)
return s
}
// Collapse adds field collapsing.
func (s *XPackAsyncSearchSubmit) Collapse(collapse *CollapseBuilder) *XPackAsyncSearchSubmit {
s.searchSource = s.searchSource.Collapse(collapse)
return s
}
// TimeoutInMillis sets the timeout in milliseconds.
func (s *XPackAsyncSearchSubmit) TimeoutInMillis(timeoutInMillis int) *XPackAsyncSearchSubmit {
s.searchSource = s.searchSource.TimeoutInMillis(timeoutInMillis)
return s
}
// TerminateAfter specifies the maximum number of documents to collect for
// each shard, upon reaching which the query execution will terminate early.
func (s *XPackAsyncSearchSubmit) TerminateAfter(terminateAfter int) *XPackAsyncSearchSubmit {
s.searchSource = s.searchSource.TerminateAfter(terminateAfter)
return s
}
// SearchType sets the search operation type. Valid values are:
// "dfs_query_then_fetch" and "query_then_fetch".
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-request-search-type.html
// for details.
func (s *XPackAsyncSearchSubmit) SearchType(searchType string) *XPackAsyncSearchSubmit {
s.searchType = searchType
return s
}
// Routing is a list of specific routing values to control the shards
// the search will be executed on.
func (s *XPackAsyncSearchSubmit) Routing(routings ...string) *XPackAsyncSearchSubmit {
s.routing = strings.Join(routings, ",")
return s
}
// Preference sets the preference to execute the search. Defaults to
// randomize across shards ("random"). Can be set to "_local" to prefer
// local shards, "_primary" to execute on primary shards only,
// or a custom value which guarantees that the same order will be used
// across different requests.
func (s *XPackAsyncSearchSubmit) Preference(preference string) *XPackAsyncSearchSubmit {
s.preference = preference
return s
}
// RequestCache indicates whether the cache should be used for this
// request or not, defaults to index level setting.
func (s *XPackAsyncSearchSubmit) RequestCache(requestCache bool) *XPackAsyncSearchSubmit {
s.requestCache = &requestCache
return s
}
// Query sets the query to perform, e.g. MatchAllQuery.
func (s *XPackAsyncSearchSubmit) Query(query Query) *XPackAsyncSearchSubmit {
s.searchSource = s.searchSource.Query(query)
return s
}
// PostFilter will be executed after the query has been executed and
// only affects the search hits, not the aggregations.
// This filter is always executed as the last filtering mechanism.
func (s *XPackAsyncSearchSubmit) PostFilter(postFilter Query) *XPackAsyncSearchSubmit {
s.searchSource = s.searchSource.PostFilter(postFilter)
return s
}
// FetchSource indicates whether the response should contain the stored
// _source for every hit.
func (s *XPackAsyncSearchSubmit) FetchSource(fetchSource bool) *XPackAsyncSearchSubmit {
s.searchSource = s.searchSource.FetchSource(fetchSource)
return s
}
// FetchSourceContext indicates how the _source should be fetched.
func (s *XPackAsyncSearchSubmit) FetchSourceContext(fetchSourceContext *FetchSourceContext) *XPackAsyncSearchSubmit {
s.searchSource = s.searchSource.FetchSourceContext(fetchSourceContext)
return s
}
// Highlight adds highlighting to the search.
func (s *XPackAsyncSearchSubmit) Highlight(highlight *Highlight) *XPackAsyncSearchSubmit {
s.searchSource = s.searchSource.Highlight(highlight)
return s
}
// GlobalSuggestText defines the global text to use with all suggesters.
// This avoids repetition.
func (s *XPackAsyncSearchSubmit) GlobalSuggestText(globalText string) *XPackAsyncSearchSubmit {
s.searchSource = s.searchSource.GlobalSuggestText(globalText)
return s
}
// Suggester adds a suggester to the search.
func (s *XPackAsyncSearchSubmit) Suggester(suggester Suggester) *XPackAsyncSearchSubmit {
s.searchSource = s.searchSource.Suggester(suggester)
return s
}
// Aggregation adds an aggreation to perform as part of the search.
func (s *XPackAsyncSearchSubmit) Aggregation(name string, aggregation Aggregation) *XPackAsyncSearchSubmit {
s.searchSource = s.searchSource.Aggregation(name, aggregation)
return s
}
// MinScore sets the minimum score below which docs will be filtered out.
func (s *XPackAsyncSearchSubmit) MinScore(minScore float64) *XPackAsyncSearchSubmit {
s.searchSource = s.searchSource.MinScore(minScore)
return s
}
// From index to start the search from. Defaults to 0.
func (s *XPackAsyncSearchSubmit) From(from int) *XPackAsyncSearchSubmit {
s.searchSource = s.searchSource.From(from)
return s
}
// Size is the number of search hits to return. Defaults to 10.
func (s *XPackAsyncSearchSubmit) Size(size int) *XPackAsyncSearchSubmit {
s.searchSource = s.searchSource.Size(size)
return s
}
// Explain indicates whether each search hit should be returned with
// an explanation of the hit (ranking).
func (s *XPackAsyncSearchSubmit) Explain(explain bool) *XPackAsyncSearchSubmit {
s.searchSource = s.searchSource.Explain(explain)
return s
}
// Version indicates whether each search hit should be returned with
// a version associated to it.
func (s *XPackAsyncSearchSubmit) Version(version bool) *XPackAsyncSearchSubmit {
s.searchSource = s.searchSource.Version(version)
return s
}
// Sort adds a sort order.
func (s *XPackAsyncSearchSubmit) Sort(field string, ascending bool) *XPackAsyncSearchSubmit {
s.searchSource = s.searchSource.Sort(field, ascending)
return s
}
// SortWithInfo adds a sort order.
func (s *XPackAsyncSearchSubmit) SortWithInfo(info SortInfo) *XPackAsyncSearchSubmit {
s.searchSource = s.searchSource.SortWithInfo(info)
return s
}
// SortBy adds a sort order.
func (s *XPackAsyncSearchSubmit) SortBy(sorter ...Sorter) *XPackAsyncSearchSubmit {
s.searchSource = s.searchSource.SortBy(sorter...)
return s
}
// DocvalueField adds a single field to load from the field data cache
// and return as part of the search.
func (s *XPackAsyncSearchSubmit) DocvalueField(docvalueField string) *XPackAsyncSearchSubmit {
s.searchSource = s.searchSource.DocvalueField(docvalueField)
return s
}
// DocvalueFieldWithFormat adds a single field to load from the field data cache
// and return as part of the search.
func (s *XPackAsyncSearchSubmit) DocvalueFieldWithFormat(docvalueField DocvalueField) *XPackAsyncSearchSubmit {
s.searchSource = s.searchSource.DocvalueFieldWithFormat(docvalueField)
return s
}
// DocvalueFields adds one or more fields to load from the field data cache
// and return as part of the search.
func (s *XPackAsyncSearchSubmit) DocvalueFields(docvalueFields ...string) *XPackAsyncSearchSubmit {
s.searchSource = s.searchSource.DocvalueFields(docvalueFields...)
return s
}
// DocvalueFieldsWithFormat adds one or more fields to load from the field data cache
// and return as part of the search.
func (s *XPackAsyncSearchSubmit) DocvalueFieldsWithFormat(docvalueFields ...DocvalueField) *XPackAsyncSearchSubmit {
s.searchSource = s.searchSource.DocvalueFieldsWithFormat(docvalueFields...)
return s
}
// NoStoredFields indicates that no stored fields should be loaded, resulting in only
// id and type to be returned per field.
func (s *XPackAsyncSearchSubmit) NoStoredFields() *XPackAsyncSearchSubmit {
s.searchSource = s.searchSource.NoStoredFields()
return s
}
// StoredField adds a single field to load and return (note, must be stored) as
// part of the search request. If none are specified, the source of the
// document will be returned.
func (s *XPackAsyncSearchSubmit) StoredField(fieldName string) *XPackAsyncSearchSubmit {
s.searchSource = s.searchSource.StoredField(fieldName)
return s
}
// StoredFields sets the fields to load and return as part of the search request.
// If none are specified, the source of the document will be returned.
func (s *XPackAsyncSearchSubmit) StoredFields(fields ...string) *XPackAsyncSearchSubmit {
s.searchSource = s.searchSource.StoredFields(fields...)
return s
}
// TrackScores is applied when sorting and controls if scores will be
// tracked as well. Defaults to false.
func (s *XPackAsyncSearchSubmit) TrackScores(trackScores bool) *XPackAsyncSearchSubmit {
s.searchSource = s.searchSource.TrackScores(trackScores)
return s
}
// TrackTotalHits controls if the total hit count for the query should be tracked.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.1/search-request-track-total-hits.html
// for details.
func (s *XPackAsyncSearchSubmit) TrackTotalHits(trackTotalHits interface{}) *XPackAsyncSearchSubmit {
s.searchSource = s.searchSource.TrackTotalHits(trackTotalHits)
return s
}
// SearchAfter allows a different form of pagination by using a live cursor,
// using the results of the previous page to help the retrieval of the next.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-request-search-after.html
func (s *XPackAsyncSearchSubmit) SearchAfter(sortValues ...interface{}) *XPackAsyncSearchSubmit {
s.searchSource = s.searchSource.SearchAfter(sortValues...)
return s
}
// DefaultRescoreWindowSize sets the rescore window size for rescores
// that don't specify their window.
func (s *XPackAsyncSearchSubmit) DefaultRescoreWindowSize(defaultRescoreWindowSize int) *XPackAsyncSearchSubmit {
s.searchSource = s.searchSource.DefaultRescoreWindowSize(defaultRescoreWindowSize)
return s
}
// Rescorer adds a rescorer to the search.
func (s *XPackAsyncSearchSubmit) Rescorer(rescore *Rescore) *XPackAsyncSearchSubmit {
s.searchSource = s.searchSource.Rescorer(rescore)
return s
}
// IgnoreUnavailable indicates whether the specified concrete indices
// should be ignored when unavailable (missing or closed).
func (s *XPackAsyncSearchSubmit) IgnoreUnavailable(ignoreUnavailable bool) *XPackAsyncSearchSubmit {
s.ignoreUnavailable = &ignoreUnavailable
return s
}
// IgnoreThrottled indicates whether specified concrete, expanded or aliased
// indices should be ignored when throttled.
func (s *XPackAsyncSearchSubmit) IgnoreThrottled(ignoreThrottled bool) *XPackAsyncSearchSubmit {
s.ignoreThrottled = &ignoreThrottled
return s
}
// AllowNoIndices indicates whether to ignore if a wildcard indices
// expression resolves into no concrete indices. (This includes `_all` string
// or when no indices have been specified).
func (s *XPackAsyncSearchSubmit) AllowNoIndices(allowNoIndices bool) *XPackAsyncSearchSubmit {
s.allowNoIndices = &allowNoIndices
return s
}
// ExpandWildcards indicates whether to expand wildcard expression to
// concrete indices that are open, closed or both.
func (s *XPackAsyncSearchSubmit) ExpandWildcards(expandWildcards string) *XPackAsyncSearchSubmit {
s.expandWildcards = expandWildcards
return s
}
// Lenient specifies whether format-based query failures (such as providing
// text to a numeric field) should be ignored.
func (s *XPackAsyncSearchSubmit) Lenient(lenient bool) *XPackAsyncSearchSubmit {
s.lenient = &lenient
return s
}
// MaxResponseSize sets an upper limit on the response body size that we accept,
// to guard against OOM situations.
func (s *XPackAsyncSearchSubmit) MaxResponseSize(maxResponseSize int64) *XPackAsyncSearchSubmit {
s.maxResponseSize = maxResponseSize
return s
}
// AllowPartialSearchResults indicates if an error should be returned if
// there is a partial search failure or timeout.
func (s *XPackAsyncSearchSubmit) AllowPartialSearchResults(enabled bool) *XPackAsyncSearchSubmit {
s.allowPartialSearchResults = &enabled
return s
}
// TypedKeys specifies whether aggregation and suggester names should be
// prefixed by their respective types in the response.
func (s *XPackAsyncSearchSubmit) TypedKeys(enabled bool) *XPackAsyncSearchSubmit {
s.typedKeys = &enabled
return s
}
// SeqNoPrimaryTerm is an alias for SeqNoAndPrimaryTerm.
//
// Deprecated: Use SeqNoAndPrimaryTerm instead.
func (s *XPackAsyncSearchSubmit) SeqNoPrimaryTerm(enabled bool) *XPackAsyncSearchSubmit {
return s.SeqNoAndPrimaryTerm(enabled)
}
// SeqNoAndPrimaryTerm specifies whether to return sequence number and
// primary term of the last modification of each hit.
func (s *XPackAsyncSearchSubmit) SeqNoAndPrimaryTerm(enabled bool) *XPackAsyncSearchSubmit {
s.seqNoPrimaryTerm = &enabled
return s
}
// BatchedReduceSize specifies the number of shard results that should be reduced
// at once on the coordinating node. This value should be used as a protection
// mechanism to reduce the memory overhead per search request if the potential
// number of shards in the request can be large.
func (s *XPackAsyncSearchSubmit) BatchedReduceSize(size int) *XPackAsyncSearchSubmit {
s.batchedReduceSize = &size
return s
}
// MaxConcurrentShardRequests specifies the number of concurrent shard requests
// this search executes concurrently. This value should be used to limit the
// impact of the search on the cluster in order to limit the number of
// concurrent shard requests.
func (s *XPackAsyncSearchSubmit) MaxConcurrentShardRequests(max int) *XPackAsyncSearchSubmit {
s.maxConcurrentShardRequests = &max
return s
}
// PreFilterShardSize specifies a threshold that enforces a pre-filter roundtrip
// to prefilter search shards based on query rewriting if the number of shards
// the search request expands to exceeds the threshold. This filter roundtrip
// can limit the number of shards significantly if for instance a shard can
// not match any documents based on it's rewrite method i.e. if date filters are
// mandatory to match but the shard bounds and the query are disjoint.
func (s *XPackAsyncSearchSubmit) PreFilterShardSize(threshold int) *XPackAsyncSearchSubmit {
s.preFilterShardSize = &threshold
return s
}
// RestTotalHitsAsInt indicates whether hits.total should be rendered as an
// integer or an object in the rest search response.
func (s *XPackAsyncSearchSubmit) RestTotalHitsAsInt(enabled bool) *XPackAsyncSearchSubmit {
s.restTotalHitsAsInt = &enabled
return s
}
// CCSMinimizeRoundtrips indicates whether network round-trips should be minimized
// as part of cross-cluster search requests execution.
func (s *XPackAsyncSearchSubmit) CCSMinimizeRoundtrips(enabled bool) *XPackAsyncSearchSubmit {
s.ccsMinimizeRoundtrips = &enabled
return s
}
// WaitForCompletionTimeout is suitable for DoAsync only. It specifies the
// timeout for the Search to wait for completion before returning an ID to
// return the results asynchronously. In other words: If the search takes
// longer than this value (default is 1 second), then you need to call
// GetAsync to retrieve its final results.
func (s *XPackAsyncSearchSubmit) WaitForCompletionTimeout(timeout string) *XPackAsyncSearchSubmit {
s.waitForCompletionTimeout = timeout
return s
}
// KeepOnCompletion is suitable for DoAsync only. It indicates whether the
// asynchronous search ID and its results should be kept even after the
// search (and its results) are completed and retrieved.
func (s *XPackAsyncSearchSubmit) KeepOnCompletion(keepOnCompletion bool) *XPackAsyncSearchSubmit {
s.keepOnCompletion = &keepOnCompletion
return s
}
// KeepAlive can only be used with DoAsync. If set, KeepAlive specifies the
// duration after which search ID and its results are removed from the
// Elasticsearch cluster and hence can no longer be retrieved with GetAsync.
func (s *XPackAsyncSearchSubmit) KeepAlive(keepAlive string) *XPackAsyncSearchSubmit {
s.keepAlive = keepAlive
return s
}
// buildURL builds the URL for the operation.
func (s *XPackAsyncSearchSubmit) buildURL() (string, url.Values, error) {
var err error
var path string
if len(s.index) > 0 && len(s.typ) > 0 {
path, err = uritemplates.Expand("/{index}/{type}/_async_search", map[string]string{
"index": strings.Join(s.index, ","),
"type": strings.Join(s.typ, ","),
})
} else if len(s.index) > 0 {
path, err = uritemplates.Expand("/{index}/_async_search", map[string]string{
"index": strings.Join(s.index, ","),
})
} else if len(s.typ) > 0 {
path, err = uritemplates.Expand("/_all/{type}/_async_search", map[string]string{
"type": strings.Join(s.typ, ","),
})
} else {
path = "/_async_search"
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.searchType != "" {
params.Set("search_type", s.searchType)
}
if s.routing != "" {
params.Set("routing", s.routing)
}
if s.preference != "" {
params.Set("preference", s.preference)
}
if v := s.requestCache; v != nil {
params.Set("request_cache", fmt.Sprint(*v))
}
if v := s.allowNoIndices; v != nil {
params.Set("allow_no_indices", fmt.Sprint(*v))
}
if s.expandWildcards != "" {
params.Set("expand_wildcards", s.expandWildcards)
}
if v := s.lenient; v != nil {
params.Set("lenient", fmt.Sprint(*v))
}
if v := s.ignoreUnavailable; v != nil {
params.Set("ignore_unavailable", fmt.Sprint(*v))
}
if v := s.ignoreThrottled; v != nil {
params.Set("ignore_throttled", fmt.Sprint(*v))
}
if s.seqNoPrimaryTerm != nil {
params.Set("seq_no_primary_term", fmt.Sprint(*s.seqNoPrimaryTerm))
}
if v := s.allowPartialSearchResults; v != nil {
params.Set("allow_partial_search_results", fmt.Sprint(*v))
}
if v := s.typedKeys; v != nil {
params.Set("typed_keys", fmt.Sprint(*v))
}
if v := s.batchedReduceSize; v != nil {
params.Set("batched_reduce_size", fmt.Sprint(*v))
}
if v := s.maxConcurrentShardRequests; v != nil {
params.Set("max_concurrent_shard_requests", fmt.Sprint(*v))
}
if v := s.preFilterShardSize; v != nil {
params.Set("pre_filter_shard_size", fmt.Sprint(*v))
}
if v := s.restTotalHitsAsInt; v != nil {
params.Set("rest_total_hits_as_int", fmt.Sprint(*v))
}
if v := s.ccsMinimizeRoundtrips; v != nil {
params.Set("ccs_minimize_roundtrips", fmt.Sprint(*v))
}
if s.waitForCompletionTimeout != "" {
params.Set("wait_for_completion_timeout", s.waitForCompletionTimeout)
}
if v := s.keepOnCompletion; v != nil {
params.Set("keep_on_completion", fmt.Sprint(*v))
}
if s.keepAlive != "" {
params.Set("keep_alive", s.keepAlive)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *XPackAsyncSearchSubmit) Validate() error {
return nil
}
// Do executes the search and returns a XPackAsyncSearchResult.
func (s *XPackAsyncSearchSubmit) Do(ctx context.Context) (*XPackAsyncSearchResult, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Perform request
var body interface{}
if s.source != nil {
body = s.source
} else {
src, err := s.searchSource.Source()
if err != nil {
return nil, err
}
body = src
}
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "POST",
Path: path,
Params: params,
Body: body,
Headers: s.headers,
MaxResponseSize: s.maxResponseSize,
})
if err != nil {
return nil, err
}
// Return search results
ret := new(XPackAsyncSearchResult)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
ret.Header = res.Header
return nil, err
}
ret.Header = res.Header
return ret, nil
}
// XPackAsyncSearchResult is the outcome of starting an asynchronous search
// or retrieving a search result with XPackAsyncSearchGet.
type XPackAsyncSearchResult struct {
Header http.Header `json:"-"`
ID string `json:"id,omitempty"`
IsRunning bool `json:"is_running"`
IsPartial bool `json:"is_partial"`
StartTimeMillis int64 `json:"start_time_in_millis,omitempty"`
ExpirationTimeMillis int64 `json:"expiration_time_in_millis,omitempty"`
Response *SearchResult `json:"response,omitempty"`
Error *ErrorDetails `json:"error,omitempty"`
}
// Each is a utility function to iterate over all hits. It saves you from
// checking for nil values. Notice that Each will ignore errors in
// serializing JSON and hits with empty/nil _source will get an empty
// value
func (r *XPackAsyncSearchResult) Each(typ reflect.Type) []interface{} {
if r == nil || r.Response == nil || r.Response.Hits == nil || r.Response.Hits.Hits == nil || len(r.Response.Hits.Hits) == 0 {
return nil
}
var slice []interface{}
for _, hit := range r.Response.Hits.Hits {
v := reflect.New(typ).Elem()
if hit.Source == nil {
slice = append(slice, v.Interface())
continue
}
if err := json.Unmarshal(hit.Source, v.Addr().Interface()); err == nil {
slice = append(slice, v.Interface())
}
}
return slice
}
================================================
FILE: xpack_async_search_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"testing"
)
func TestXPackAsyncSearchLifecycle(t *testing.T) {
//client := setupTestClientAndCreateIndexAndAddDocs(t, SetURL("http://elastic:elastic@localhost:9210"), SetTraceLog(log.New(os.Stdout, "", log.LstdFlags)))
client := setupTestClientAndCreateIndexAndAddDocs(t, SetURL("http://elastic:elastic@localhost:9210"))
// Match all should return all documents
resp, err := client.XPackAsyncSearchSubmit().
Index(testIndexName).
Query(NewMatchAllQuery()).
Size(100).
Pretty(true).
WaitForCompletionTimeout("10s"). // should be ready by then
KeepOnCompletion(true). // keep even after completion
KeepAlive("2m"). // keep for at least 2 minutes
Do(context.Background())
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("expected response, got nil")
}
if want, have := false, resp.IsRunning; want != have {
t.Errorf("expected IsRunning=%v; got %v", want, have)
}
if want, have := false, resp.IsPartial; want != have {
t.Errorf("expected IsPartial=%v; got %v", want, have)
}
if resp.ID == "" {
t.Error(`expected ID!=""`)
}
if resp.Response == nil {
t.Fatal("expected Response; got nil")
}
if want, have := int64(3), resp.Response.TotalHits(); want != have {
t.Errorf("expected TotalHits=%v; got %v", want, have)
}
for _, hit := range resp.Response.Hits.Hits {
if hit.Index != testIndexName {
t.Errorf("expected SearchResult.Hits.Hit.Index = %q; got %q", testIndexName, hit.Index)
}
item := make(map[string]interface{})
err := json.Unmarshal(hit.Source, &item)
if err != nil {
t.Fatal(err)
}
}
// Get the search results with the given ID
get, err := client.XPackAsyncSearchGet().ID(resp.ID).Pretty(true).Do(context.Background())
if err != nil {
t.Fatal(err)
}
if get == nil {
t.Fatal("expected response, got nil")
}
if want, have := false, get.IsRunning; want != have {
t.Errorf("expected IsRunning=%v; got %v", want, have)
}
if want, have := false, get.IsPartial; want != have {
t.Errorf("expected IsPartial=%v; got %v", want, have)
}
if want, have := get.ID, get.ID; want != have {
t.Errorf("expected ID!=%q; got %q", want, have)
}
if get.Response == nil {
t.Fatal("expected Response; got nil")
}
if want, have := int64(3), get.Response.TotalHits(); want != have {
t.Errorf("expected TotalHits=%v; got %v", want, have)
}
// Delete the search results with the given ID
del, err := client.XPackAsyncSearchDelete().ID(resp.ID).Do(context.Background())
if err != nil {
t.Fatal(err)
}
if del == nil {
t.Fatal("expected response, got nil")
}
if want, have := true, del.Acknowledged; want != have {
t.Errorf("expected Acknowledged=%v; got %v", want, have)
}
}
================================================
FILE: xpack_ilm_delete_lifecycle.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// See the documentation at
// https://www.elastic.co/guide/en/elasticsearch/reference/6.7/ilm-get-lifecycle.html.
type XPackIlmDeleteLifecycleService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
policy string
timeout string
masterTimeout string
flatSettings *bool
local *bool
}
// NewXPackIlmDeleteLifecycleService creates a new XPackIlmDeleteLifecycleService.
func NewXPackIlmDeleteLifecycleService(client *Client) *XPackIlmDeleteLifecycleService {
return &XPackIlmDeleteLifecycleService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *XPackIlmDeleteLifecycleService) Pretty(pretty bool) *XPackIlmDeleteLifecycleService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *XPackIlmDeleteLifecycleService) Human(human bool) *XPackIlmDeleteLifecycleService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *XPackIlmDeleteLifecycleService) ErrorTrace(errorTrace bool) *XPackIlmDeleteLifecycleService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *XPackIlmDeleteLifecycleService) FilterPath(filterPath ...string) *XPackIlmDeleteLifecycleService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *XPackIlmDeleteLifecycleService) Header(name string, value string) *XPackIlmDeleteLifecycleService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *XPackIlmDeleteLifecycleService) Headers(headers http.Header) *XPackIlmDeleteLifecycleService {
s.headers = headers
return s
}
// Policy is the name of the index lifecycle policy.
func (s *XPackIlmDeleteLifecycleService) Policy(policy string) *XPackIlmDeleteLifecycleService {
s.policy = policy
return s
}
// Timeout is an explicit operation timeout.
func (s *XPackIlmDeleteLifecycleService) Timeout(timeout string) *XPackIlmDeleteLifecycleService {
s.timeout = timeout
return s
}
// MasterTimeout specifies the timeout for connection to master.
func (s *XPackIlmDeleteLifecycleService) MasterTimeout(masterTimeout string) *XPackIlmDeleteLifecycleService {
s.masterTimeout = masterTimeout
return s
}
// FlatSettings is returns settings in flat format (default: false).
func (s *XPackIlmDeleteLifecycleService) FlatSettings(flatSettings bool) *XPackIlmDeleteLifecycleService {
s.flatSettings = &flatSettings
return s
}
// buildURL builds the URL for the operation.
func (s *XPackIlmDeleteLifecycleService) buildURL() (string, url.Values, error) {
// Build URL
var err error
var path string
path, err = uritemplates.Expand("/_ilm/policy/{policy}", map[string]string{
"policy": s.policy,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if v := s.flatSettings; v != nil {
params.Set("flat_settings", fmt.Sprint(*v))
}
if s.timeout != "" {
params.Set("timeout", s.timeout)
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
if v := s.local; v != nil {
params.Set("local", fmt.Sprint(*v))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *XPackIlmDeleteLifecycleService) Validate() error {
var invalid []string
if s.policy == "" {
invalid = append(invalid, "Policy")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *XPackIlmDeleteLifecycleService) Do(ctx context.Context) (*XPackIlmDeleteLifecycleResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Delete URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Delete HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "DELETE",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(XPackIlmDeleteLifecycleResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// XPackIlmDeleteLifecycleResponse is the response of XPackIlmDeleteLifecycleService.Do.
type XPackIlmDeleteLifecycleResponse struct {
Acknowledged bool `json:"acknowledged"`
}
================================================
FILE: xpack_ilm_get_lifecycle.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// See the documentation at
// https://www.elastic.co/guide/en/elasticsearch/reference/6.7/ilm-get-lifecycle.html.
type XPackIlmGetLifecycleService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
policy []string
timeout string
masterTimeout string
flatSettings *bool
local *bool
}
// NewXPackIlmGetLifecycleService creates a new XPackIlmGetLifecycleService.
func NewXPackIlmGetLifecycleService(client *Client) *XPackIlmGetLifecycleService {
return &XPackIlmGetLifecycleService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *XPackIlmGetLifecycleService) Pretty(pretty bool) *XPackIlmGetLifecycleService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *XPackIlmGetLifecycleService) Human(human bool) *XPackIlmGetLifecycleService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *XPackIlmGetLifecycleService) ErrorTrace(errorTrace bool) *XPackIlmGetLifecycleService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *XPackIlmGetLifecycleService) FilterPath(filterPath ...string) *XPackIlmGetLifecycleService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *XPackIlmGetLifecycleService) Header(name string, value string) *XPackIlmGetLifecycleService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *XPackIlmGetLifecycleService) Headers(headers http.Header) *XPackIlmGetLifecycleService {
s.headers = headers
return s
}
// Policy is the name of the index lifecycle policy.
func (s *XPackIlmGetLifecycleService) Policy(policies ...string) *XPackIlmGetLifecycleService {
s.policy = append(s.policy, policies...)
return s
}
// Timeout is an explicit operation timeout.
func (s *XPackIlmGetLifecycleService) Timeout(timeout string) *XPackIlmGetLifecycleService {
s.timeout = timeout
return s
}
// MasterTimeout specifies the timeout for connection to master.
func (s *XPackIlmGetLifecycleService) MasterTimeout(masterTimeout string) *XPackIlmGetLifecycleService {
s.masterTimeout = masterTimeout
return s
}
// FlatSettings is returns settings in flat format (default: false).
func (s *XPackIlmGetLifecycleService) FlatSettings(flatSettings bool) *XPackIlmGetLifecycleService {
s.flatSettings = &flatSettings
return s
}
// buildURL builds the URL for the operation.
func (s *XPackIlmGetLifecycleService) buildURL() (string, url.Values, error) {
// Build URL
var err error
var path string
if len(s.policy) > 0 {
path, err = uritemplates.Expand("/_ilm/policy/{policy}", map[string]string{
"policy": strings.Join(s.policy, ","),
})
} else {
path = "/_ilm/policy"
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if v := s.flatSettings; v != nil {
params.Set("flat_settings", fmt.Sprint(*v))
}
if s.timeout != "" {
params.Set("timeout", s.timeout)
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
if v := s.local; v != nil {
params.Set("local", fmt.Sprint(*v))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *XPackIlmGetLifecycleService) Validate() error {
return nil
}
// Do executes the operation.
func (s *XPackIlmGetLifecycleService) Do(ctx context.Context) (map[string]*XPackIlmGetLifecycleResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
var ret map[string]*XPackIlmGetLifecycleResponse
if err := s.client.decoder.Decode(res.Body, &ret); err != nil {
return nil, err
}
return ret, nil
}
// XPackIlmGetLifecycleResponse is the response of XPackIlmGetLifecycleService.Do.
type XPackIlmGetLifecycleResponse struct {
Version int `json:"version,omitempty"`
ModifiedDate string `json:"modified_date,omitempty"` // e.g. "2019-10-03T17:43:42.720Z"
Policy map[string]interface{} `json:"policy,omitempty"`
InUseBy *ClusterMetadataItemUsage `json:"in_use_by,omitempty"`
}
// ClusterMetadataItemUsage encapsulates the usage of a particular "thing"
// by something else. In Elasticsearch, this is in the
// org.elasticsearch.cluster.metadata package.
type ClusterMetadataItemUsage struct {
Indices []string `json:"indices,omitempty"`
DataStreams []string `json:"data_streams,omitempty"`
ComposableTemplates []string `json:"composable_templates,omitempty"`
}
================================================
FILE: xpack_ilm_put_lifecycle.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// See the documentation at
// https://www.elastic.co/guide/en/elasticsearch/reference/6.7/ilm-put-lifecycle.html
type XPackIlmPutLifecycleService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
policy string
timeout string
masterTimeout string
flatSettings *bool
bodyJson interface{}
bodyString string
}
// NewXPackIlmPutLifecycleService creates a new XPackIlmPutLifecycleService.
func NewXPackIlmPutLifecycleService(client *Client) *XPackIlmPutLifecycleService {
return &XPackIlmPutLifecycleService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *XPackIlmPutLifecycleService) Pretty(pretty bool) *XPackIlmPutLifecycleService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *XPackIlmPutLifecycleService) Human(human bool) *XPackIlmPutLifecycleService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *XPackIlmPutLifecycleService) ErrorTrace(errorTrace bool) *XPackIlmPutLifecycleService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *XPackIlmPutLifecycleService) FilterPath(filterPath ...string) *XPackIlmPutLifecycleService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *XPackIlmPutLifecycleService) Header(name string, value string) *XPackIlmPutLifecycleService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *XPackIlmPutLifecycleService) Headers(headers http.Header) *XPackIlmPutLifecycleService {
s.headers = headers
return s
}
// Policy is the name of the index lifecycle policy.
func (s *XPackIlmPutLifecycleService) Policy(policy string) *XPackIlmPutLifecycleService {
s.policy = policy
return s
}
// Timeout is an explicit operation timeout.
func (s *XPackIlmPutLifecycleService) Timeout(timeout string) *XPackIlmPutLifecycleService {
s.timeout = timeout
return s
}
// MasterTimeout specifies the timeout for connection to master.
func (s *XPackIlmPutLifecycleService) MasterTimeout(masterTimeout string) *XPackIlmPutLifecycleService {
s.masterTimeout = masterTimeout
return s
}
// FlatSettings indicates whether to return settings in flat format (default: false).
func (s *XPackIlmPutLifecycleService) FlatSettings(flatSettings bool) *XPackIlmPutLifecycleService {
s.flatSettings = &flatSettings
return s
}
// BodyJson is documented as: The template definition.
func (s *XPackIlmPutLifecycleService) BodyJson(body interface{}) *XPackIlmPutLifecycleService {
s.bodyJson = body
return s
}
// BodyString is documented as: The template definition.
func (s *XPackIlmPutLifecycleService) BodyString(body string) *XPackIlmPutLifecycleService {
s.bodyString = body
return s
}
// buildURL builds the URL for the operation.
func (s *XPackIlmPutLifecycleService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/_ilm/policy/{policy}", map[string]string{
"policy": s.policy,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.timeout != "" {
params.Set("timeout", s.timeout)
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
if v := s.flatSettings; v != nil {
params.Set("flat_settings", fmt.Sprint(*v))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *XPackIlmPutLifecycleService) Validate() error {
var invalid []string
if s.policy == "" {
invalid = append(invalid, "Policy")
}
if s.bodyString == "" && s.bodyJson == nil {
invalid = append(invalid, "BodyJson")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *XPackIlmPutLifecycleService) Do(ctx context.Context) (*XPackIlmPutLifecycleResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Setup HTTP request body
var body interface{}
if s.bodyJson != nil {
body = s.bodyJson
} else {
body = s.bodyString
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "PUT",
Path: path,
Params: params,
Body: body,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(XPackIlmPutLifecycleResponse)
if err := s.client.decoder.Decode(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// XPackIlmPutLifecycleSResponse is the response of XPackIlmPutLifecycleService.Do.
type XPackIlmPutLifecycleResponse struct {
Acknowledged bool `json:"acknowledged"`
}
================================================
FILE: xpack_ilm_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"testing"
)
func TestXPackIlmPolicyLifecycle(t *testing.T) {
client := setupTestClient(t, SetURL("http://elastic:elastic@localhost:9210")) //, SetTraceLog(log.New(os.Stdout, "", 0)))
testPolicyName := "test-policy"
body := `{
"policy": {
"phases": {
"delete": {
"min_age": "20m",
"actions": {
"delete": {}
}
}
}
}
}`
// Create the policy
putilm, err := client.XPackIlmPutLifecycle().Policy(testPolicyName).BodyString(body).Do(context.TODO())
if err != nil {
t.Fatalf("expected put lifecycle to succeed; got: %v", err)
}
if putilm == nil {
t.Fatalf("expected put lifecycle response; got: %v", putilm)
}
if !putilm.Acknowledged {
t.Fatalf("expected put lifecycle ack; got: %v", putilm.Acknowledged)
}
// Get the policy
getilm, err := client.XPackIlmGetLifecycle().Policy(testPolicyName).Pretty(true).Do(context.TODO())
if err != nil {
t.Fatalf("expected get lifecycle to succeed; got: %v", err)
}
if getilm == nil {
t.Fatalf("expected get lifecycle response; got: %v", getilm)
}
// Check the policy exists
_, found := getilm[testPolicyName]
if !found {
t.Fatalf("expected to get policy for %q", testPolicyName)
}
// Delete the policy
delilm, err := client.XPackIlmDeleteLifecycle().Policy(testPolicyName).Do(context.TODO())
if err != nil {
t.Fatalf("expected deletelifecycle to succeed; got: %v", err)
}
if delilm == nil {
t.Fatalf("expected delete lifecycle response; got: %v", delilm)
}
if !delilm.Acknowledged {
t.Fatalf("expected delete lifecycle ack; got: %v", delilm.Acknowledged)
}
// Get the policy
getilm, err = client.XPackIlmGetLifecycle().Policy(testPolicyName).Do(context.TODO())
if err == nil {
t.Fatalf("expected lifecycle to be deleted; got: %v", getilm)
}
}
================================================
FILE: xpack_info.go
================================================
// Copyright 2012-2018 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// XPackInfoService retrieves xpack info.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/info-api.html.
type XPackInfoService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
}
// NewXPackInfoService creates a new XPackInfoService.
func NewXPackInfoService(client *Client) *XPackInfoService {
return &XPackInfoService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *XPackInfoService) Pretty(pretty bool) *XPackInfoService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *XPackInfoService) Human(human bool) *XPackInfoService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *XPackInfoService) ErrorTrace(errorTrace bool) *XPackInfoService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *XPackInfoService) FilterPath(filterPath ...string) *XPackInfoService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *XPackInfoService) Header(name string, value string) *XPackInfoService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *XPackInfoService) Headers(headers http.Header) *XPackInfoService {
s.headers = headers
return s
}
// buildURL builds the URL for the operation.
func (s *XPackInfoService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/_xpack", map[string]string{})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *XPackInfoService) Validate() error {
var invalid []string
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *XPackInfoService) Do(ctx context.Context) (*XPackInfoServiceResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := XPackInfoServiceResponse{}
if err := json.Unmarshal(res.Body, &ret); err != nil {
return nil, err
}
return &ret, nil
}
// XPackInfoServiceResponse is the response of XPackInfoService.Do.
type XPackInfoServiceResponse struct {
Build XPackInfoBuild `json:"build"`
License XPackInfoLicense `json:"license"`
Features XPackInfoFeatures `json:"features"`
Tagline string `json:"tagline"`
}
// XPackInfoBuild is the xpack build info
type XPackInfoBuild struct {
Hash string `json:"hash"`
Date string `json:"date"`
}
// XPackInfoLicense is the xpack license info
type XPackInfoLicense struct {
UID string `json:"uid"`
Type string `json:"type"`
Mode string `json:"mode"`
Status string `json:"status"`
ExpiryMilis int `json:"expiry_date_in_millis"`
}
// XPackInfoFeatures is the xpack feature info object
type XPackInfoFeatures struct {
Graph XPackInfoGraph `json:"graph"`
Logstash XPackInfoLogstash `json:"logstash"`
MachineLearning XPackInfoML `json:"ml"`
Monitoring XPackInfoMonitoring `json:"monitoring"`
Rollup XPackInfoRollup `json:"rollup"`
Security XPackInfoSecurity `json:"security"`
Watcher XPackInfoWatcher `json:"watcher"`
}
// XPackInfoGraph is the xpack graph plugin info
type XPackInfoGraph struct {
Description string `json:"description"`
Available bool `json:"available"`
Enabled bool `json:"enabled"`
}
// XPackInfoLogstash is the xpack logstash plugin info
type XPackInfoLogstash struct {
Description string `json:"description"`
Available bool `json:"available"`
Enabled bool `json:"enabled"`
}
// XPackInfoML is the xpack machine learning plugin info
type XPackInfoML struct {
Description string `json:"description"`
Available bool `json:"available"`
Enabled bool `json:"enabled"`
NativeCodeInfo map[string]string `json:"native_code_info"`
}
// XPackInfoMonitoring is the xpack monitoring plugin info
type XPackInfoMonitoring struct {
Description string `json:"description"`
Available bool `json:"available"`
Enabled bool `json:"enabled"`
}
// XPackInfoRollup is the xpack rollup plugin info
type XPackInfoRollup struct {
Description string `json:"description"`
Available bool `json:"available"`
Enabled bool `json:"enabled"`
}
// XPackInfoSecurity is the xpack security plugin info
type XPackInfoSecurity struct {
Description string `json:"description"`
Available bool `json:"available"`
Enabled bool `json:"enabled"`
}
// XPackInfoWatcher is the xpack watcher plugin info
type XPackInfoWatcher struct {
Description string `json:"description"`
Available bool `json:"available"`
Enabled bool `json:"enabled"`
}
================================================
FILE: xpack_info_test.go
================================================
// Copyright 2012-2018 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"testing"
)
func TestXPackInfoBuildURL(t *testing.T) {
client := setupTestClientForXpackSecurity(t)
tests := []struct {
Expected string
ExpectErr bool
}{
{
"/_xpack",
false,
},
}
for i, test := range tests {
builder := client.XPackInfo()
err := builder.Validate()
if err != nil {
if !test.ExpectErr {
t.Errorf("case #%d: %v", i+1, err)
continue
}
} else {
// err == nil
if test.ExpectErr {
t.Errorf("case #%d: expected error", i+1)
continue
}
path, _, _ := builder.buildURL()
if path != test.Expected {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.Expected, path)
}
}
}
}
================================================
FILE: xpack_rollup_delete.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// XPackRollupDeleteService delete a rollup job by its job id.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/rollup-delete-job.html.
type XPackRollupDeleteService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
jobId string
}
// XPackRollupDeleteService creates a new XPackRollupDeleteService.
func NewXPackRollupDeleteService(client *Client) *XPackRollupDeleteService {
return &XPackRollupDeleteService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *XPackRollupDeleteService) Pretty(pretty bool) *XPackRollupDeleteService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *XPackRollupDeleteService) Human(human bool) *XPackRollupDeleteService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *XPackRollupDeleteService) ErrorTrace(errorTrace bool) *XPackRollupDeleteService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *XPackRollupDeleteService) FilterPath(filterPath ...string) *XPackRollupDeleteService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *XPackRollupDeleteService) Header(name string, value string) *XPackRollupDeleteService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *XPackRollupDeleteService) Headers(headers http.Header) *XPackRollupDeleteService {
s.headers = headers
return s
}
// JobId is id of the rollup to delete.
func (s *XPackRollupDeleteService) JobId(jobId string) *XPackRollupDeleteService {
s.jobId = jobId
return s
}
// buildURL builds the URL for the operation.
func (s *XPackRollupDeleteService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/_rollup/job/{job_id}", map[string]string{
"job_id": s.jobId,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *XPackRollupDeleteService) Validate() error {
var invalid []string
if s.jobId == "" {
invalid = append(invalid, "Job ID")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *XPackRollupDeleteService) Do(ctx context.Context) (*XPackRollupDeleteResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "DELETE",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(XPackRollupDeleteResponse)
if err := json.Unmarshal(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// XPackRollupDeleteResponse is the response of XPackRollupDeleteService.Do.
type XPackRollupDeleteResponse struct {
Acknowledged bool `json:"acknowledged"`
}
================================================
FILE: xpack_rollup_delete_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"testing"
)
func TestXPackRollupDeleteBuildURL(t *testing.T) {
client := setupTestClient(t)
tests := []struct {
JobId string
ExpectedPath string
ExpectErr bool
}{
{
"",
"",
true,
},
{
"my-job",
"/_rollup/job/my-job",
false,
},
}
for i, test := range tests {
builder := client.XPackRollupDelete(test.JobId)
err := builder.Validate()
if err != nil {
if !test.ExpectErr {
t.Errorf("case #%d: %v", i+1, err)
continue
}
} else {
// err == nil
if test.ExpectErr {
t.Errorf("case #%d: expected error", i+1)
continue
}
path, _, _ := builder.buildURL()
if path != test.ExpectedPath {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.ExpectedPath, path)
}
}
}
}
================================================
FILE: xpack_rollup_get.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// XPackRollupGetService retrieves a role by its name.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/rollup-apis.html.
type XPackRollupGetService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
jobId string
}
// NewXPackRollupGetService creates a new XPackRollupGetService.
func NewXPackRollupGetService(client *Client) *XPackRollupGetService {
return &XPackRollupGetService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *XPackRollupGetService) Pretty(pretty bool) *XPackRollupGetService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *XPackRollupGetService) Human(human bool) *XPackRollupGetService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *XPackRollupGetService) ErrorTrace(errorTrace bool) *XPackRollupGetService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *XPackRollupGetService) FilterPath(filterPath ...string) *XPackRollupGetService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *XPackRollupGetService) Header(name string, value string) *XPackRollupGetService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *XPackRollupGetService) Headers(headers http.Header) *XPackRollupGetService {
s.headers = headers
return s
}
// JobId is id of the rollup to retrieve.
func (s *XPackRollupGetService) JobId(jobId string) *XPackRollupGetService {
s.jobId = jobId
return s
}
// buildURL builds the URL for the operation.
func (s *XPackRollupGetService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/_rollup/job/{job_id}", map[string]string{
"job_id": s.jobId,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *XPackRollupGetService) Validate() error {
var invalid []string
if s.jobId == "" {
invalid = append(invalid, "Job ID")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *XPackRollupGetService) Do(ctx context.Context) (*XPackRollupGetResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := XPackRollupGetResponse{}
if err := json.Unmarshal(res.Body, &ret); err != nil {
return nil, err
}
return &ret, nil
}
// XPackRollupGetResponse is the response of XPackRollupGetService.Do.
type XPackRollupGetResponse struct {
Jobs []XPackRollup `json:"jobs"`
}
// XPackRollup is the role object.
type XPackRollup struct {
Config XPackRollupConfig `json:"config"`
Status XPackRollupStatus `json:"status"`
Stats XPackRollupStats `json:"stats"`
}
type XPackRollupConfig struct {
Id string `json:"id"`
Cron string `json:"cron"`
IndexPattern string `json:"index_pattern"`
RollupIndex string `json:"rollup_index"`
Groups map[string]interface{} `json:"groups"`
Metrics []XPackRollupMetrics `json:"metrics"`
Timeout string `json:"timeout"`
PageSize int `json:"page_size"`
}
type XPackRollupMetrics struct {
Field string `json:"field"`
Metrics []string `json:"metrics"`
}
type XPackRollupStatus struct {
JobState string `json:"job_state"`
UpgradedDocId bool `json:"upgraded_doc_id"`
}
type XPackRollupStats struct {
PageProcessed int `json:"pages_processed"`
DocumentsProcessed int `json:"documents_processed"`
RollupsIndexed int `json:"rollups_indexed"`
TriggerCount int `json:"trigger_count"`
IndexFailures int `json:"index_failures"`
IndexTimeInMs int `json:"index_time_in_ms"`
IndexTotal int `json:"index_total"`
SearchFailures int `json:"search_failures"`
SearchTimeInMs int `json:"search_time_in_ms"`
SearchTotal int `json:"search_total"`
ProcessingTimeInMs int `json:"processing_time_in_ms"`
ProcessingTotal int `json:"processing_total"`
}
================================================
FILE: xpack_rollup_get_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import "testing"
func TestXPackRollupGetBuildURL(t *testing.T) {
client := setupTestClient(t)
tests := []struct {
JobId string
ExpectedPath string
ExpectErr bool
}{
{
"",
"",
true,
},
{
"my-job",
"/_rollup/job/my-job",
false,
},
}
for i, test := range tests {
builder := client.XPackRollupGet(test.JobId)
err := builder.Validate()
if err != nil {
if !test.ExpectErr {
t.Errorf("case #%d: %v", i+1, err)
continue
}
} else {
// err == nil
if test.ExpectErr {
t.Errorf("case #%d: expected error", i+1)
continue
}
path, _, _ := builder.buildURL()
if path != test.ExpectedPath {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.ExpectedPath, path)
}
}
}
}
================================================
FILE: xpack_rollup_put.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// XPackRollupPutService create or update a rollup job by its job id.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/rollup-put-job.html.
type XPackRollupPutService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
jobId string
body interface{}
}
// NewXPackRollupPutService creates a new XPackRollupPutService.
func NewXPackRollupPutService(client *Client) *XPackRollupPutService {
return &XPackRollupPutService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *XPackRollupPutService) Pretty(pretty bool) *XPackRollupPutService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *XPackRollupPutService) Human(human bool) *XPackRollupPutService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *XPackRollupPutService) ErrorTrace(errorTrace bool) *XPackRollupPutService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *XPackRollupPutService) FilterPath(filterPath ...string) *XPackRollupPutService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *XPackRollupPutService) Header(name string, value string) *XPackRollupPutService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *XPackRollupPutService) Headers(headers http.Header) *XPackRollupPutService {
s.headers = headers
return s
}
// JobId is id of the rollup to create.
func (s *XPackRollupPutService) JobId(jobId string) *XPackRollupPutService {
s.jobId = jobId
return s
}
// Body specifies the role. Use a string or a type that will get serialized as JSON.
func (s *XPackRollupPutService) Body(body interface{}) *XPackRollupPutService {
s.body = body
return s
}
// buildURL builds the URL for the operation.
func (s *XPackRollupPutService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/_rollup/job/{job_id}", map[string]string{
"job_id": s.jobId,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *XPackRollupPutService) Validate() error {
var invalid []string
if s.jobId == "" {
invalid = append(invalid, "Job ID")
}
if s.body == nil {
invalid = append(invalid, "Body")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *XPackRollupPutService) Do(ctx context.Context) (*XPackRollupPutResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "PUT",
Path: path,
Params: params,
Body: s.body,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(XPackRollupPutResponse)
if err := json.Unmarshal(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// XPackRollupPutResponse is the response of XPackRollupPutService.Do.
type XPackRollupPutResponse struct {
Acknowledged bool `json:"acknowledged"`
}
================================================
FILE: xpack_rollup_put_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"testing"
)
func TestXPackRollupPutBuildURL(t *testing.T) {
client := setupTestClient(t)
tests := []struct {
JobId string
Body interface{}
ExpectedPath string
ExpectErr bool
}{
{
"",
nil,
"",
true,
},
{
"my-job",
nil,
"",
true,
},
{
"",
`{}`,
"",
true,
},
{
"my-job",
`{}`,
"/_rollup/job/my-job",
false,
},
}
for i, test := range tests {
builder := client.XPackRollupPut(test.JobId).Body(test.Body)
err := builder.Validate()
if err != nil {
if !test.ExpectErr {
t.Errorf("case #%d: %v", i+1, err)
continue
}
} else {
// err == nil
if test.ExpectErr {
t.Errorf("case #%d: expected error", i+1)
continue
}
path, _, _ := builder.buildURL()
if path != test.ExpectedPath {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.ExpectedPath, path)
}
}
}
}
================================================
FILE: xpack_rollup_start.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// XPackRollupStartService starts the rollup job if it is not already running.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/rollup-start-job.html.
type XPackRollupStartService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
jobId string
}
// NewXPackRollupStartService creates a new XPackRollupStartService.
func NewXPackRollupStartService(client *Client) *XPackRollupStartService {
return &XPackRollupStartService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *XPackRollupStartService) Pretty(pretty bool) *XPackRollupStartService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *XPackRollupStartService) Human(human bool) *XPackRollupStartService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *XPackRollupStartService) ErrorTrace(errorTrace bool) *XPackRollupStartService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *XPackRollupStartService) FilterPath(filterPath ...string) *XPackRollupStartService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *XPackRollupStartService) Header(name string, value string) *XPackRollupStartService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *XPackRollupStartService) Headers(headers http.Header) *XPackRollupStartService {
s.headers = headers
return s
}
// JobId is id of the rollup to retrieve.
func (s *XPackRollupStartService) JobId(jobId string) *XPackRollupStartService {
s.jobId = jobId
return s
}
// buildURL builds the URL for the operation.
func (s *XPackRollupStartService) buildURL() (string, url.Values, error) {
// Build URL path
path, err := uritemplates.Expand("/_rollup/job/{job_id}/_start", map[string]string{
"job_id": s.jobId,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *XPackRollupStartService) Validate() error {
var invalid []string
if s.jobId == "" {
invalid = append(invalid, "Job ID")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *XPackRollupStartService) Do(ctx context.Context) (*XPackRollupStartResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "POST",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(XPackRollupStartResponse)
if err := json.Unmarshal(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// XPackRollupStartResponse is the response of XPackRollupStartService.Do.
type XPackRollupStartResponse struct {
Started bool `json:"started"`
}
================================================
FILE: xpack_rollup_start_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"testing"
)
func TestXPackRollupStartBuildURL(t *testing.T) {
client := setupTestClient(t)
tests := []struct {
Expected string
ExpectErr bool
}{
{
"/_rollup/job/my-job/_start",
false,
},
}
for i, test := range tests {
builder := client.XPackRollupStart("my-job")
err := builder.Validate()
if err != nil {
if !test.ExpectErr {
t.Errorf("case #%d: %v", i+1, err)
continue
}
} else {
// err == nil
if test.ExpectErr {
t.Errorf("case #%d: expected error", i+1)
continue
}
path, _, _ := builder.buildURL()
if path != test.Expected {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.Expected, path)
}
}
}
}
================================================
FILE: xpack_rollup_stop.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// XPackRollupStartService stops the rollup job if it is running.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/rollup-stop-job.html.
type XPackRollupStopService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
jobId string
}
// NewXPackRollupStopService creates a new XPackRollupStopService.
func NewXPackRollupStopService(client *Client) *XPackRollupStopService {
return &XPackRollupStopService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *XPackRollupStopService) Pretty(pretty bool) *XPackRollupStopService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *XPackRollupStopService) Human(human bool) *XPackRollupStopService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *XPackRollupStopService) ErrorTrace(errorTrace bool) *XPackRollupStopService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *XPackRollupStopService) FilterPath(filterPath ...string) *XPackRollupStopService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *XPackRollupStopService) Header(name string, value string) *XPackRollupStopService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *XPackRollupStopService) Headers(headers http.Header) *XPackRollupStopService {
s.headers = headers
return s
}
// JobId is id of the rollup to retrieve.
func (s *XPackRollupStopService) JobId(jobId string) *XPackRollupStopService {
s.jobId = jobId
return s
}
// buildURL builds the URL for the operation.
func (s *XPackRollupStopService) buildURL() (string, url.Values, error) {
// Build URL path
path, err := uritemplates.Expand("/_rollup/job/{job_id}/_stop", map[string]string{
"job_id": s.jobId,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *XPackRollupStopService) Validate() error {
var invalid []string
if s.jobId == "" {
invalid = append(invalid, "Job ID")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *XPackRollupStopService) Do(ctx context.Context) (*XPackRollupStopResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "POST",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(XPackRollupStopResponse)
if err := json.Unmarshal(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// XPackRollupStopResponse is the response of XPackRollupStopService.Do.
type XPackRollupStopResponse struct {
Stoppeed bool `json:"stopped"`
}
================================================
FILE: xpack_rollup_stop_test.go
================================================
// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"testing"
)
func TestXPackRollupStopBuildURL(t *testing.T) {
client := setupTestClient(t)
tests := []struct {
Expected string
ExpectErr bool
}{
{
"/_rollup/job/my-job/_stop",
false,
},
}
for i, test := range tests {
builder := client.XPackRollupStop("my-job")
err := builder.Validate()
if err != nil {
if !test.ExpectErr {
t.Errorf("case #%d: %v", i+1, err)
continue
}
} else {
// err == nil
if test.ExpectErr {
t.Errorf("case #%d: expected error", i+1)
continue
}
path, _, _ := builder.buildURL()
if path != test.Expected {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.Expected, path)
}
}
}
}
================================================
FILE: xpack_security_change_password.go
================================================
// Copyright 2012-2018 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// XPackSecurityChangePasswordService changes a native user's password.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.1/security-api-change-password.html.
type XPackSecurityChangePasswordService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
username string
password string
refresh string
body interface{}
}
// NewXPackSecurityChangePasswordService creates a new XPackSecurityChangePasswordService.
func NewXPackSecurityChangePasswordService(client *Client) *XPackSecurityChangePasswordService {
return &XPackSecurityChangePasswordService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *XPackSecurityChangePasswordService) Pretty(pretty bool) *XPackSecurityChangePasswordService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *XPackSecurityChangePasswordService) Human(human bool) *XPackSecurityChangePasswordService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *XPackSecurityChangePasswordService) ErrorTrace(errorTrace bool) *XPackSecurityChangePasswordService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *XPackSecurityChangePasswordService) FilterPath(filterPath ...string) *XPackSecurityChangePasswordService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *XPackSecurityChangePasswordService) Header(name string, value string) *XPackSecurityChangePasswordService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *XPackSecurityChangePasswordService) Headers(headers http.Header) *XPackSecurityChangePasswordService {
s.headers = headers
return s
}
// Username is name of the user to change.
func (s *XPackSecurityChangePasswordService) Username(username string) *XPackSecurityChangePasswordService {
s.username = username
return s
}
// Password is the new value of the password.
func (s *XPackSecurityChangePasswordService) Password(password string) *XPackSecurityChangePasswordService {
s.password = password
return s
}
// Refresh, if "true" (the default), refreshes the affected shards to make this operation
// visible to search, if "wait_for" then wait for a refresh to make this operation visible
// to search, if "false" then do nothing with refreshes.
func (s *XPackSecurityChangePasswordService) Refresh(refresh string) *XPackSecurityChangePasswordService {
s.refresh = refresh
return s
}
// Body specifies the password. Use a string or a type that will get serialized as JSON.
func (s *XPackSecurityChangePasswordService) Body(body interface{}) *XPackSecurityChangePasswordService {
s.body = body
return s
}
// buildURL builds the URL for the operation.
func (s *XPackSecurityChangePasswordService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/_xpack/security/user/{username}/_password", map[string]string{
"username": s.username,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if v := s.refresh; v != "" {
params.Set("refresh", v)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *XPackSecurityChangePasswordService) Validate() error {
var invalid []string
if s.username == "" {
invalid = append(invalid, "Userame")
}
if s.password == "" && s.body == nil {
invalid = append(invalid, "Body")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *XPackSecurityChangePasswordService) Do(ctx context.Context) (*XPackSecurityChangeUserPasswordResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
var body interface{}
if s.body != nil {
body = s.body
} else {
body = map[string]interface{}{
"password": s.password,
}
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "POST",
Path: path,
Params: params,
Body: body,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(XPackSecurityChangeUserPasswordResponse)
if err := json.Unmarshal(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// XPackSecurityChangeUserPasswordResponse is the response of
// XPackSecurityChangePasswordService.Do.
//
// A successful call returns an empty JSON structure: {}.
type XPackSecurityChangeUserPasswordResponse struct {
}
================================================
FILE: xpack_security_change_password_test.go
================================================
// Copyright 2012-2018 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"net/url"
"testing"
"github.com/google/go-cmp/cmp"
)
func TestXPackSecurityChangePasswordBuildURL(t *testing.T) {
client := setupTestClientForXpackSecurity(t)
tests := []struct {
Username string
Password string
Refresh string
Body interface{}
ExpectedPath string
ExpectedParams url.Values
ExpectedErr bool
}{
// #0 No username
{
Username: "",
Password: "",
Refresh: "",
Body: nil,
ExpectedPath: "",
ExpectedParams: url.Values{},
ExpectedErr: true,
},
// #1 No username (but body)
{
Username: "",
Password: "",
Refresh: "",
Body: `{}`,
ExpectedPath: "",
ExpectedParams: url.Values{},
ExpectedErr: true,
},
// #2 No body or password
{
Username: "my-user",
Password: "",
Refresh: "",
Body: nil,
ExpectedPath: "",
ExpectedParams: url.Values{},
ExpectedErr: true,
},
// #3 No password but body
{
Username: "my-user",
Password: "",
Refresh: "",
Body: `{"password":"secret"}`,
ExpectedPath: "/_xpack/security/user/my-user/_password",
ExpectedParams: url.Values{},
ExpectedErr: false,
},
// #4 No body but password
{
Username: "my-user",
Password: "secret",
Refresh: "",
Body: nil,
ExpectedPath: "/_xpack/security/user/my-user/_password",
ExpectedParams: url.Values{},
ExpectedErr: false,
},
// #5 With refresh option
{
Username: "my-user",
Password: "secret",
Refresh: "wait_for",
Body: nil,
ExpectedPath: "/_xpack/security/user/my-user/_password",
ExpectedParams: url.Values{
"refresh": []string{"wait_for"},
},
ExpectedErr: false,
},
}
for i, tt := range tests {
builder := client.XPackSecurityChangePassword(tt.Username).
Password(tt.Password).
Refresh(tt.Refresh).
Body(tt.Body)
err := builder.Validate()
if err != nil {
if !tt.ExpectedErr {
t.Errorf("case #%d: %v", i, err)
continue
}
} else {
// err == nil
if tt.ExpectedErr {
t.Errorf("case #%d: expected error", i)
continue
}
path, params, err := builder.buildURL()
if err != nil {
t.Fatalf("case #%d: %v", i, err)
}
if path != tt.ExpectedPath {
t.Errorf("case #%d: expected %q; got: %q", i, tt.ExpectedPath, path)
}
if want, have := tt.ExpectedParams, params; !cmp.Equal(want, have) {
t.Errorf("case #%d: want Params=%#v, have %#v\n\tdiff: %s", i, want, have, cmp.Diff(want, have))
}
}
}
}
================================================
FILE: xpack_security_delete_role.go
================================================
// Copyright 2012-2018 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// XPackSecurityDeleteRoleService delete a role by its name.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/security-api-delete-role.html.
type XPackSecurityDeleteRoleService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
name string
}
// NewXPackSecurityDeleteRoleService creates a new XPackSecurityDeleteRoleService.
func NewXPackSecurityDeleteRoleService(client *Client) *XPackSecurityDeleteRoleService {
return &XPackSecurityDeleteRoleService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *XPackSecurityDeleteRoleService) Pretty(pretty bool) *XPackSecurityDeleteRoleService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *XPackSecurityDeleteRoleService) Human(human bool) *XPackSecurityDeleteRoleService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *XPackSecurityDeleteRoleService) ErrorTrace(errorTrace bool) *XPackSecurityDeleteRoleService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *XPackSecurityDeleteRoleService) FilterPath(filterPath ...string) *XPackSecurityDeleteRoleService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *XPackSecurityDeleteRoleService) Header(name string, value string) *XPackSecurityDeleteRoleService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *XPackSecurityDeleteRoleService) Headers(headers http.Header) *XPackSecurityDeleteRoleService {
s.headers = headers
return s
}
// Name is name of the role to delete.
func (s *XPackSecurityDeleteRoleService) Name(name string) *XPackSecurityDeleteRoleService {
s.name = name
return s
}
// buildURL builds the URL for the operation.
func (s *XPackSecurityDeleteRoleService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/_security/role/{name}", map[string]string{
"name": s.name,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *XPackSecurityDeleteRoleService) Validate() error {
var invalid []string
if s.name == "" {
invalid = append(invalid, "Name")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *XPackSecurityDeleteRoleService) Do(ctx context.Context) (*XPackSecurityDeleteRoleResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "DELETE",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(XPackSecurityDeleteRoleResponse)
if err := json.Unmarshal(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// XPackSecurityDeleteRoleResponse is the response of XPackSecurityDeleteRoleService.Do.
type XPackSecurityDeleteRoleResponse struct {
Found bool `json:"found"`
}
================================================
FILE: xpack_security_delete_role_mapping.go
================================================
// Copyright 2012-2018 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// XPackSecurityDeleteRoleMappingService delete a role mapping by its name.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/security-api-delete-role-mapping.html.
type XPackSecurityDeleteRoleMappingService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
name string
}
// NewXPackSecurityDeleteRoleMappingService creates a new XPackSecurityDeleteRoleMappingService.
func NewXPackSecurityDeleteRoleMappingService(client *Client) *XPackSecurityDeleteRoleMappingService {
return &XPackSecurityDeleteRoleMappingService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *XPackSecurityDeleteRoleMappingService) Pretty(pretty bool) *XPackSecurityDeleteRoleMappingService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *XPackSecurityDeleteRoleMappingService) Human(human bool) *XPackSecurityDeleteRoleMappingService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *XPackSecurityDeleteRoleMappingService) ErrorTrace(errorTrace bool) *XPackSecurityDeleteRoleMappingService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *XPackSecurityDeleteRoleMappingService) FilterPath(filterPath ...string) *XPackSecurityDeleteRoleMappingService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *XPackSecurityDeleteRoleMappingService) Header(name string, value string) *XPackSecurityDeleteRoleMappingService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *XPackSecurityDeleteRoleMappingService) Headers(headers http.Header) *XPackSecurityDeleteRoleMappingService {
s.headers = headers
return s
}
// Name is name of the role mapping to delete.
func (s *XPackSecurityDeleteRoleMappingService) Name(name string) *XPackSecurityDeleteRoleMappingService {
s.name = name
return s
}
// buildURL builds the URL for the operation.
func (s *XPackSecurityDeleteRoleMappingService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/_security/role_mapping/{name}", map[string]string{
"name": s.name,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *XPackSecurityDeleteRoleMappingService) Validate() error {
var invalid []string
if s.name == "" {
invalid = append(invalid, "Name")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *XPackSecurityDeleteRoleMappingService) Do(ctx context.Context) (*XPackSecurityDeleteRoleMappingResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "DELETE",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(XPackSecurityDeleteRoleMappingResponse)
if err := json.Unmarshal(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// XPackSecurityDeleteRoleMappingResponse is the response of XPackSecurityDeleteRoleMappingService.Do.
type XPackSecurityDeleteRoleMappingResponse struct {
Found bool `json:"found"`
}
================================================
FILE: xpack_security_delete_role_mapping_test.go
================================================
// Copyright 2012-2018 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"testing"
)
func TestXPackSecurityDeleteRoleMappingBuildURL(t *testing.T) {
client := setupTestClientForXpackSecurity(t)
tests := []struct {
Name string
ExpectedPath string
ExpectErr bool
}{
{
"",
"",
true,
},
{
"my-role-mapping",
"/_security/role_mapping/my-role-mapping",
false,
},
}
for i, test := range tests {
builder := client.XPackSecurityDeleteRoleMapping(test.Name)
err := builder.Validate()
if err != nil {
if !test.ExpectErr {
t.Errorf("case #%d: %v", i+1, err)
continue
}
} else {
// err == nil
if test.ExpectErr {
t.Errorf("case #%d: expected error", i+1)
continue
}
path, _, _ := builder.buildURL()
if path != test.ExpectedPath {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.ExpectedPath, path)
}
}
}
}
================================================
FILE: xpack_security_delete_role_test.go
================================================
// Copyright 2012-2018 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"testing"
)
func TestXPackSecurityDeleteRoleBuildURL(t *testing.T) {
client := setupTestClientForXpackSecurity(t)
tests := []struct {
Name string
ExpectedPath string
ExpectErr bool
}{
{
"",
"",
true,
},
{
"my-role",
"/_security/role/my-role",
false,
},
}
for i, test := range tests {
builder := client.XPackSecurityDeleteRole(test.Name)
err := builder.Validate()
if err != nil {
if !test.ExpectErr {
t.Errorf("case #%d: %v", i+1, err)
continue
}
} else {
// err == nil
if test.ExpectErr {
t.Errorf("case #%d: expected error", i+1)
continue
}
path, _, _ := builder.buildURL()
if path != test.ExpectedPath {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.ExpectedPath, path)
}
}
}
}
================================================
FILE: xpack_security_delete_user.go
================================================
// Copyright 2012-2019 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// XPackSecurityDeleteUserService delete a user by its name.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.4/security-api-delete-user.html.
type XPackSecurityDeleteUserService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
username string
refresh string
}
// NewXPackSecurityDeleteUserService creates a new XPackSecurityDeleteUserService.
func NewXPackSecurityDeleteUserService(client *Client) *XPackSecurityDeleteUserService {
return &XPackSecurityDeleteUserService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *XPackSecurityDeleteUserService) Pretty(pretty bool) *XPackSecurityDeleteUserService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *XPackSecurityDeleteUserService) Human(human bool) *XPackSecurityDeleteUserService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *XPackSecurityDeleteUserService) ErrorTrace(errorTrace bool) *XPackSecurityDeleteUserService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *XPackSecurityDeleteUserService) FilterPath(filterPath ...string) *XPackSecurityDeleteUserService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *XPackSecurityDeleteUserService) Header(name string, value string) *XPackSecurityDeleteUserService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *XPackSecurityDeleteUserService) Headers(headers http.Header) *XPackSecurityDeleteUserService {
s.headers = headers
return s
}
// Username is name of the user to delete.
func (s *XPackSecurityDeleteUserService) Username(username string) *XPackSecurityDeleteUserService {
s.username = username
return s
}
// Refresh specifies if and how to wait for refreshing the shards after the request.
// Possible values are "true" (default), "false" and "wait_for", all of type string.
func (s *XPackSecurityDeleteUserService) Refresh(refresh string) *XPackSecurityDeleteUserService {
s.refresh = refresh
return s
}
// buildURL builds the URL for the operation.
func (s *XPackSecurityDeleteUserService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/_security/user/{username}", map[string]string{
"username": s.username,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if v := s.refresh; v != "" {
params.Set("refresh", v)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *XPackSecurityDeleteUserService) Validate() error {
var invalid []string
if s.username == "" {
invalid = append(invalid, "Username")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *XPackSecurityDeleteUserService) Do(ctx context.Context) (*XPackSecurityDeleteUserResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "DELETE",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(XPackSecurityDeleteUserResponse)
if err := json.Unmarshal(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// XPackSecurityDeleteUserResponse is the response of XPackSecurityDeleteUserService.Do.
type XPackSecurityDeleteUserResponse struct {
Found bool `json:"found"`
}
================================================
FILE: xpack_security_delete_user_test.go
================================================
// Copyright 2012-2019 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"testing"
)
func TestXPackSecurityDeleteUserBuildURL(t *testing.T) {
client := setupTestClientForXpackSecurity(t)
tests := []struct {
Name string
ExpectedPath string
ExpectErr bool
}{
{
"",
"",
true,
},
{
"my-user",
"/_security/user/my-user",
false,
},
}
for i, test := range tests {
builder := client.XPackSecurityDeleteUser(test.Name)
err := builder.Validate()
if err != nil {
if !test.ExpectErr {
t.Errorf("case #%d: %v", i+1, err)
continue
}
} else {
// err == nil
if test.ExpectErr {
t.Errorf("case #%d: expected error", i+1)
continue
}
path, _, _ := builder.buildURL()
if path != test.ExpectedPath {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.ExpectedPath, path)
}
}
}
}
================================================
FILE: xpack_security_disable_user.go
================================================
// Copyright 2012-2019 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// XPackSecurityDisableUserService retrieves a user by its name.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/security-api-get-user.html.
type XPackSecurityDisableUserService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
username string
refresh string
}
// NewXPackSecurityDisableUserService creates a new XPackSecurityDisableUserService.
func NewXPackSecurityDisableUserService(client *Client) *XPackSecurityDisableUserService {
return &XPackSecurityDisableUserService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *XPackSecurityDisableUserService) Pretty(pretty bool) *XPackSecurityDisableUserService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *XPackSecurityDisableUserService) Human(human bool) *XPackSecurityDisableUserService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *XPackSecurityDisableUserService) ErrorTrace(errorTrace bool) *XPackSecurityDisableUserService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *XPackSecurityDisableUserService) FilterPath(filterPath ...string) *XPackSecurityDisableUserService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *XPackSecurityDisableUserService) Header(name string, value string) *XPackSecurityDisableUserService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *XPackSecurityDisableUserService) Headers(headers http.Header) *XPackSecurityDisableUserService {
s.headers = headers
return s
}
// Username is name of the user to disable.
func (s *XPackSecurityDisableUserService) Username(username string) *XPackSecurityDisableUserService {
s.username = username
return s
}
// Refresh specifies if and how to wait for refreshing the shards after the request.
// Possible values are "true" (default), "false" and "wait_for", all of type string.
func (s *XPackSecurityDisableUserService) Refresh(refresh string) *XPackSecurityDisableUserService {
s.refresh = refresh
return s
}
// buildURL builds the URL for the operation.
func (s *XPackSecurityDisableUserService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/_security/user/{username}/_disable", map[string]string{
"username": s.username,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if v := s.refresh; v != "" {
params.Set("refresh", v)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *XPackSecurityDisableUserService) Validate() error {
var invalid []string
if s.username == "" {
invalid = append(invalid, "Username")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *XPackSecurityDisableUserService) Do(ctx context.Context) (*XPackSecurityDisableUserResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "PUT",
Path: path,
Params: params,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(XPackSecurityDisableUserResponse)
if err := json.Unmarshal(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// XPackSecurityDisableUserResponse is the response of XPackSecurityDisableUserService.Do.
type XPackSecurityDisableUserResponse struct {
}
================================================
FILE: xpack_security_disable_user_test.go
================================================
// Copyright 2012-2019 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"testing"
)
func TestXPackSecurityDisableUserBuildURL(t *testing.T) {
client := setupTestClientForXpackSecurity(t)
tests := []struct {
Name string
ExpectedPath string
ExpectErr bool
}{
{
"",
"",
true,
},
{
"my-user",
"/_security/user/my-user/_disable",
false,
},
}
for i, test := range tests {
builder := client.XPackSecurityDisableUser(test.Name)
err := builder.Validate()
if err != nil {
if !test.ExpectErr {
t.Errorf("case #%d: %v", i+1, err)
continue
}
} else {
// err == nil
if test.ExpectErr {
t.Errorf("case #%d: expected error", i+1)
continue
}
path, _, _ := builder.buildURL()
if path != test.ExpectedPath {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.ExpectedPath, path)
}
}
}
}
================================================
FILE: xpack_security_enable_user.go
================================================
// Copyright 2012-2019 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// XPackSecurityEnableUserService retrieves a user by its name.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/security-api-get-user.html.
type XPackSecurityEnableUserService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
username string
refresh string
}
// NewXPackSecurityEnableUserService creates a new XPackSecurityEnableUserService.
func NewXPackSecurityEnableUserService(client *Client) *XPackSecurityEnableUserService {
return &XPackSecurityEnableUserService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *XPackSecurityEnableUserService) Pretty(pretty bool) *XPackSecurityEnableUserService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *XPackSecurityEnableUserService) Human(human bool) *XPackSecurityEnableUserService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *XPackSecurityEnableUserService) ErrorTrace(errorTrace bool) *XPackSecurityEnableUserService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *XPackSecurityEnableUserService) FilterPath(filterPath ...string) *XPackSecurityEnableUserService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *XPackSecurityEnableUserService) Header(name string, value string) *XPackSecurityEnableUserService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *XPackSecurityEnableUserService) Headers(headers http.Header) *XPackSecurityEnableUserService {
s.headers = headers
return s
}
// Username is name of the user to enable.
func (s *XPackSecurityEnableUserService) Username(username string) *XPackSecurityEnableUserService {
s.username = username
return s
}
// Refresh specifies if and how to wait for refreshing the shards after the request.
// Possible values are "true" (default), "false" and "wait_for", all of type string.
func (s *XPackSecurityEnableUserService) Refresh(refresh string) *XPackSecurityEnableUserService {
s.refresh = refresh
return s
}
// buildURL builds the URL for the operation.
func (s *XPackSecurityEnableUserService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/_security/user/{username}/_enable", map[string]string{
"username": s.username,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if v := s.refresh; v != "" {
params.Set("refresh", v)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *XPackSecurityEnableUserService) Validate() error {
var invalid []string
if s.username == "" {
invalid = append(invalid, "Username")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *XPackSecurityEnableUserService) Do(ctx context.Context) (*XPackSecurityEnableUserResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "PUT",
Path: path,
Params: params,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(XPackSecurityEnableUserResponse)
if err := json.Unmarshal(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// XPackSecurityEnableUserResponse is the response of XPackSecurityEnableUserService.Do.
type XPackSecurityEnableUserResponse struct {
}
================================================
FILE: xpack_security_enable_user_test.go
================================================
// Copyright 2012-2019 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"testing"
)
func TestXPackSecurityEnableUserBuildURL(t *testing.T) {
client := setupTestClientForXpackSecurity(t)
tests := []struct {
Name string
ExpectedPath string
ExpectErr bool
}{
{
"",
"",
true,
},
{
"my-user",
"/_security/user/my-user/_enable",
false,
},
}
for i, test := range tests {
builder := client.XPackSecurityEnableUser(test.Name)
err := builder.Validate()
if err != nil {
if !test.ExpectErr {
t.Errorf("case #%d: %v", i+1, err)
continue
}
} else {
// err == nil
if test.ExpectErr {
t.Errorf("case #%d: expected error", i+1)
continue
}
path, _, _ := builder.buildURL()
if path != test.ExpectedPath {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.ExpectedPath, path)
}
}
}
}
================================================
FILE: xpack_security_get_role.go
================================================
// Copyright 2012-2018 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// XPackSecurityGetRoleService retrieves a role by its name.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/security-api-get-role.html.
type XPackSecurityGetRoleService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
name string
}
// NewXPackSecurityGetRoleService creates a new XPackSecurityGetRoleService.
func NewXPackSecurityGetRoleService(client *Client) *XPackSecurityGetRoleService {
return &XPackSecurityGetRoleService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *XPackSecurityGetRoleService) Pretty(pretty bool) *XPackSecurityGetRoleService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *XPackSecurityGetRoleService) Human(human bool) *XPackSecurityGetRoleService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *XPackSecurityGetRoleService) ErrorTrace(errorTrace bool) *XPackSecurityGetRoleService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *XPackSecurityGetRoleService) FilterPath(filterPath ...string) *XPackSecurityGetRoleService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *XPackSecurityGetRoleService) Header(name string, value string) *XPackSecurityGetRoleService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *XPackSecurityGetRoleService) Headers(headers http.Header) *XPackSecurityGetRoleService {
s.headers = headers
return s
}
// Name is name of the role to retrieve.
func (s *XPackSecurityGetRoleService) Name(name string) *XPackSecurityGetRoleService {
s.name = name
return s
}
// buildURL builds the URL for the operation.
func (s *XPackSecurityGetRoleService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/_security/role/{name}", map[string]string{
"name": s.name,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *XPackSecurityGetRoleService) Validate() error {
var invalid []string
if s.name == "" {
invalid = append(invalid, "Name")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *XPackSecurityGetRoleService) Do(ctx context.Context) (*XPackSecurityGetRoleResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := XPackSecurityGetRoleResponse{}
if err := json.Unmarshal(res.Body, &ret); err != nil {
return nil, err
}
return &ret, nil
}
// XPackSecurityGetRoleResponse is the response of XPackSecurityGetRoleService.Do.
type XPackSecurityGetRoleResponse map[string]XPackSecurityRole
// XPackSecurityRole is the role object.
//
// The Java source for this struct is defined here:
// https://github.com/elastic/elasticsearch/blob/6.7/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/RoleDescriptor.java
type XPackSecurityRole struct {
Cluster []string `json:"cluster"`
Indices []XPackSecurityIndicesPermissions `json:"indices"`
Applications []XPackSecurityApplicationPrivileges `json:"applications"`
RunAs []string `json:"run_as"`
Global map[string]interface{} `json:"global"`
Metadata map[string]interface{} `json:"metadata"`
TransientMetadata map[string]interface{} `json:"transient_metadata"`
}
// XPackSecurityApplicationPrivileges is the application privileges object
type XPackSecurityApplicationPrivileges struct {
Application string `json:"application"`
Privileges []string `json:"privileges"`
Resources []string `json:"resources"`
}
// XPackSecurityIndicesPermissions is the indices permission object
type XPackSecurityIndicesPermissions struct {
Names []string `json:"names"`
Privileges []string `json:"privileges"`
FieldSecurity interface{} `json:"field_security,omitempty"`
Query string `json:"query"`
}
================================================
FILE: xpack_security_get_role_mapping.go
================================================
// Copyright 2012-2018 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// XPackSecurityGetRoleMappingService retrieves a role mapping by its name.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/security-api-get-role-mapping.html.
type XPackSecurityGetRoleMappingService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
name string
}
// NewXPackSecurityGetRoleMappingService creates a new XPackSecurityGetRoleMappingService.
func NewXPackSecurityGetRoleMappingService(client *Client) *XPackSecurityGetRoleMappingService {
return &XPackSecurityGetRoleMappingService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *XPackSecurityGetRoleMappingService) Pretty(pretty bool) *XPackSecurityGetRoleMappingService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *XPackSecurityGetRoleMappingService) Human(human bool) *XPackSecurityGetRoleMappingService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *XPackSecurityGetRoleMappingService) ErrorTrace(errorTrace bool) *XPackSecurityGetRoleMappingService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *XPackSecurityGetRoleMappingService) FilterPath(filterPath ...string) *XPackSecurityGetRoleMappingService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *XPackSecurityGetRoleMappingService) Header(name string, value string) *XPackSecurityGetRoleMappingService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *XPackSecurityGetRoleMappingService) Headers(headers http.Header) *XPackSecurityGetRoleMappingService {
s.headers = headers
return s
}
// Name is name of the role mapping to retrieve.
func (s *XPackSecurityGetRoleMappingService) Name(name string) *XPackSecurityGetRoleMappingService {
s.name = name
return s
}
// buildURL builds the URL for the operation.
func (s *XPackSecurityGetRoleMappingService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/_security/role_mapping/{name}", map[string]string{
"name": s.name,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *XPackSecurityGetRoleMappingService) Validate() error {
var invalid []string
if s.name == "" {
invalid = append(invalid, "Name")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *XPackSecurityGetRoleMappingService) Do(ctx context.Context) (*XPackSecurityGetRoleMappingResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := XPackSecurityGetRoleMappingResponse{}
if err := json.Unmarshal(res.Body, &ret); err != nil {
return nil, err
}
return &ret, nil
}
// XPackSecurityGetRoleMappingResponse is the response of XPackSecurityGetRoleMappingService.Do.
type XPackSecurityGetRoleMappingResponse map[string]XPackSecurityRoleMapping
// XPackSecurityRoleMapping is the role mapping object
type XPackSecurityRoleMapping struct {
Enabled bool `json:"enabled"`
Roles []string `json:"roles"`
Rules map[string]interface{} `json:"rules"`
Metadata interface{} `json:"metadata"`
}
================================================
FILE: xpack_security_get_role_mapping_test.go
================================================
// Copyright 2012-2018 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"testing"
)
func TestXPackSecurityGetRoleMappingBuildURL(t *testing.T) {
client := setupTestClientForXpackSecurity(t)
tests := []struct {
Name string
ExpectedPath string
ExpectErr bool
}{
{
"",
"",
true,
},
{
"my-role-mapping",
"/_security/role_mapping/my-role-mapping",
false,
},
}
for i, test := range tests {
builder := client.XPackSecurityGetRoleMapping(test.Name)
err := builder.Validate()
if err != nil {
if !test.ExpectErr {
t.Errorf("case #%d: %v", i+1, err)
continue
}
} else {
// err == nil
if test.ExpectErr {
t.Errorf("case #%d: expected error", i+1)
continue
}
path, _, _ := builder.buildURL()
if path != test.ExpectedPath {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.ExpectedPath, path)
}
}
}
}
================================================
FILE: xpack_security_get_role_test.go
================================================
// Copyright 2012-2018 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"testing"
)
func TestXPackSecurityGetRoleBuildURL(t *testing.T) {
client := setupTestClientForXpackSecurity(t)
tests := []struct {
Name string
ExpectedPath string
ExpectErr bool
}{
{
"",
"",
true,
},
{
"my-role",
"/_security/role/my-role",
false,
},
}
for i, test := range tests {
builder := client.XPackSecurityGetRole(test.Name)
err := builder.Validate()
if err != nil {
if !test.ExpectErr {
t.Errorf("case #%d: %v", i+1, err)
continue
}
} else {
// err == nil
if test.ExpectErr {
t.Errorf("case #%d: expected error", i+1)
continue
}
path, _, _ := builder.buildURL()
if path != test.ExpectedPath {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.ExpectedPath, path)
}
}
}
}
================================================
FILE: xpack_security_get_user.go
================================================
// Copyright 2012-2019 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// XPackSecurityGetUserService retrieves a user by its name.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/security-api-get-user.html.
type XPackSecurityGetUserService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
usernames []string
}
// NewXPackSecurityGetUserService creates a new XPackSecurityGetUserService.
func NewXPackSecurityGetUserService(client *Client) *XPackSecurityGetUserService {
return &XPackSecurityGetUserService{
client: client,
}
}
// Pretty indicates that the JSON response be indented and human readable.
func (s *XPackSecurityGetUserService) Pretty(pretty bool) *XPackSecurityGetUserService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *XPackSecurityGetUserService) Human(human bool) *XPackSecurityGetUserService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *XPackSecurityGetUserService) ErrorTrace(errorTrace bool) *XPackSecurityGetUserService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *XPackSecurityGetUserService) FilterPath(filterPath ...string) *XPackSecurityGetUserService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *XPackSecurityGetUserService) Header(name string, value string) *XPackSecurityGetUserService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *XPackSecurityGetUserService) Headers(headers http.Header) *XPackSecurityGetUserService {
s.headers = headers
return s
}
// Usernames are the names of one or more users to retrieve.
func (s *XPackSecurityGetUserService) Usernames(usernames ...string) *XPackSecurityGetUserService {
for _, username := range usernames {
if v := strings.TrimSpace(username); v != "" {
s.usernames = append(s.usernames, v)
}
}
return s
}
// buildURL builds the URL for the operation.
func (s *XPackSecurityGetUserService) buildURL() (string, url.Values, error) {
// Build URL
var (
path string
err error
)
if len(s.usernames) > 0 {
path, err = uritemplates.Expand("/_security/user/{username}", map[string]string{
"username": strings.Join(s.usernames, ","),
})
} else {
path = "/_security/user"
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *XPackSecurityGetUserService) Validate() error {
return nil
}
// Do executes the operation.
func (s *XPackSecurityGetUserService) Do(ctx context.Context) (*XPackSecurityGetUserResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := XPackSecurityGetUserResponse{}
if err := json.Unmarshal(res.Body, &ret); err != nil {
return nil, err
}
return &ret, nil
}
// XPackSecurityGetUserResponse is the response of XPackSecurityGetUserService.Do.
type XPackSecurityGetUserResponse map[string]XPackSecurityUser
// XPackSecurityUser is the user object.
//
// The Java source for this struct is defined here:
// https://github.com/elastic/elasticsearch/blob/7.3/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/user/User.java
type XPackSecurityUser struct {
Username string `json:"username"`
Roles []string `json:"roles"`
Fullname string `json:"full_name"`
Email string `json:"email"`
Metadata map[string]interface{} `json:"metadata"`
Enabled bool `json:"enabled"`
}
================================================
FILE: xpack_security_get_user_test.go
================================================
// Copyright 2012-2019 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"testing"
)
func TestXPackSecurityGetUserBuildURL(t *testing.T) {
client := setupTestClientForXpackSecurity(t)
tests := []struct {
Name string
ExpectedPath string
ExpectErr bool
}{
{
"",
"/_security/user",
false,
},
{
"my-user",
"/_security/user/my-user",
false,
},
}
for i, test := range tests {
builder := client.XPackSecurityGetUser(test.Name)
err := builder.Validate()
if err != nil {
if !test.ExpectErr {
t.Errorf("case #%d: %v", i+1, err)
continue
}
} else {
// err == nil
if test.ExpectErr {
t.Errorf("case #%d: expected error", i+1)
continue
}
path, _, _ := builder.buildURL()
if path != test.ExpectedPath {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.ExpectedPath, path)
}
}
}
}
================================================
FILE: xpack_security_put_role.go
================================================
// Copyright 2012-2018 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// XPackSecurityPutRoleService retrieves a role by its name.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/security-api-put-role.html.
type XPackSecurityPutRoleService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
name string
body interface{}
}
// NewXPackSecurityPutRoleService creates a new XPackSecurityPutRoleService.
func NewXPackSecurityPutRoleService(client *Client) *XPackSecurityPutRoleService {
return &XPackSecurityPutRoleService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *XPackSecurityPutRoleService) Pretty(pretty bool) *XPackSecurityPutRoleService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *XPackSecurityPutRoleService) Human(human bool) *XPackSecurityPutRoleService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *XPackSecurityPutRoleService) ErrorTrace(errorTrace bool) *XPackSecurityPutRoleService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *XPackSecurityPutRoleService) FilterPath(filterPath ...string) *XPackSecurityPutRoleService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *XPackSecurityPutRoleService) Header(name string, value string) *XPackSecurityPutRoleService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *XPackSecurityPutRoleService) Headers(headers http.Header) *XPackSecurityPutRoleService {
s.headers = headers
return s
}
// Name is name of the role to create.
func (s *XPackSecurityPutRoleService) Name(name string) *XPackSecurityPutRoleService {
s.name = name
return s
}
// Body specifies the role. Use a string or a type that will get serialized as JSON.
func (s *XPackSecurityPutRoleService) Body(body interface{}) *XPackSecurityPutRoleService {
s.body = body
return s
}
// buildURL builds the URL for the operation.
func (s *XPackSecurityPutRoleService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/_security/role/{name}", map[string]string{
"name": s.name,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *XPackSecurityPutRoleService) Validate() error {
var invalid []string
if s.name == "" {
invalid = append(invalid, "Name")
}
if s.body == nil {
invalid = append(invalid, "Body")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *XPackSecurityPutRoleService) Do(ctx context.Context) (*XPackSecurityPutRoleResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "PUT",
Path: path,
Params: params,
Body: s.body,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(XPackSecurityPutRoleResponse)
if err := json.Unmarshal(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// XPackSecurityPutRoleResponse is the response of XPackSecurityPutRoleService.Do.
type XPackSecurityPutRoleResponse struct {
Role XPackSecurityPutRole
}
type XPackSecurityPutRole struct {
Created bool `json:"created"`
}
================================================
FILE: xpack_security_put_role_mapping.go
================================================
// Copyright 2012-2018 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// XPackSecurityPutRoleMappingService create or update a role mapping by its name.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/security-api-put-role-mapping.html.
type XPackSecurityPutRoleMappingService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
name string
body interface{}
}
// NewXPackSecurityPutRoleMappingService creates a new XPackSecurityPutRoleMappingService.
func NewXPackSecurityPutRoleMappingService(client *Client) *XPackSecurityPutRoleMappingService {
return &XPackSecurityPutRoleMappingService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *XPackSecurityPutRoleMappingService) Pretty(pretty bool) *XPackSecurityPutRoleMappingService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *XPackSecurityPutRoleMappingService) Human(human bool) *XPackSecurityPutRoleMappingService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *XPackSecurityPutRoleMappingService) ErrorTrace(errorTrace bool) *XPackSecurityPutRoleMappingService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *XPackSecurityPutRoleMappingService) FilterPath(filterPath ...string) *XPackSecurityPutRoleMappingService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *XPackSecurityPutRoleMappingService) Header(name string, value string) *XPackSecurityPutRoleMappingService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *XPackSecurityPutRoleMappingService) Headers(headers http.Header) *XPackSecurityPutRoleMappingService {
s.headers = headers
return s
}
// Name is name of the role mapping to create/update.
func (s *XPackSecurityPutRoleMappingService) Name(name string) *XPackSecurityPutRoleMappingService {
s.name = name
return s
}
// Body specifies the role mapping. Use a string or a type that will get serialized as JSON.
func (s *XPackSecurityPutRoleMappingService) Body(body interface{}) *XPackSecurityPutRoleMappingService {
s.body = body
return s
}
// buildURL builds the URL for the operation.
func (s *XPackSecurityPutRoleMappingService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/_security/role_mapping/{name}", map[string]string{
"name": s.name,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *XPackSecurityPutRoleMappingService) Validate() error {
var invalid []string
if s.name == "" {
invalid = append(invalid, "Name")
}
if s.body == nil {
invalid = append(invalid, "Body")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *XPackSecurityPutRoleMappingService) Do(ctx context.Context) (*XPackSecurityPutRoleMappingResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "PUT",
Path: path,
Params: params,
Body: s.body,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(XPackSecurityPutRoleMappingResponse)
if err := json.Unmarshal(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// XPackSecurityPutRoleMappingResponse is the response of XPackSecurityPutRoleMappingService.Do.
type XPackSecurityPutRoleMappingResponse struct {
Role_Mapping XPackSecurityPutRoleMapping
}
type XPackSecurityPutRoleMapping struct {
Created bool `json:"created"`
}
================================================
FILE: xpack_security_put_role_mapping_test.go
================================================
// Copyright 2012-2018 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"testing"
)
func TestXPackSecurityPutRoleMappingBuildURL(t *testing.T) {
client := setupTestClientForXpackSecurity(t)
tests := []struct {
Name string
Body interface{}
ExpectedPath string
ExpectErr bool
}{
{
"",
nil,
"",
true,
},
{
"my-role-mapping",
nil,
"",
true,
},
{
"",
`{}`,
"",
true,
},
{
"my-role-mapping",
`{}`,
"/_security/role_mapping/my-role-mapping",
false,
},
}
for i, test := range tests {
builder := client.XPackSecurityPutRoleMapping(test.Name).Body(test.Body)
err := builder.Validate()
if err != nil {
if !test.ExpectErr {
t.Errorf("case #%d: %v", i+1, err)
continue
}
} else {
// err == nil
if test.ExpectErr {
t.Errorf("case #%d: expected error", i+1)
continue
}
path, _, _ := builder.buildURL()
if path != test.ExpectedPath {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.ExpectedPath, path)
}
}
}
}
================================================
FILE: xpack_security_put_role_test.go
================================================
// Copyright 2012-2018 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"testing"
)
func TestXPackSecurityPutRoleBuildURL(t *testing.T) {
client := setupTestClientForXpackSecurity(t)
tests := []struct {
Name string
Body interface{}
ExpectedPath string
ExpectErr bool
}{
{
"",
nil,
"",
true,
},
{
"my-role",
nil,
"",
true,
},
{
"",
`{}`,
"",
true,
},
{
"my-role",
`{}`,
"/_security/role/my-role",
false,
},
}
for i, test := range tests {
builder := client.XPackSecurityPutRole(test.Name).Body(test.Body)
err := builder.Validate()
if err != nil {
if !test.ExpectErr {
t.Errorf("case #%d: %v", i+1, err)
continue
}
} else {
// err == nil
if test.ExpectErr {
t.Errorf("case #%d: expected error", i+1)
continue
}
path, _, _ := builder.buildURL()
if path != test.ExpectedPath {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.ExpectedPath, path)
}
}
}
}
================================================
FILE: xpack_security_put_user.go
================================================
// Copyright 2012-2019 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// XPackSecurityPutUserService adds a user.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.4/security-api-put-user.html.
type XPackSecurityPutUserService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
username string
refresh string
user *XPackSecurityPutUserRequest
body interface{}
}
// NewXPackSecurityPutUserService creates a new XPackSecurityPutUserService.
func NewXPackSecurityPutUserService(client *Client) *XPackSecurityPutUserService {
return &XPackSecurityPutUserService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *XPackSecurityPutUserService) Pretty(pretty bool) *XPackSecurityPutUserService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *XPackSecurityPutUserService) Human(human bool) *XPackSecurityPutUserService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *XPackSecurityPutUserService) ErrorTrace(errorTrace bool) *XPackSecurityPutUserService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *XPackSecurityPutUserService) FilterPath(filterPath ...string) *XPackSecurityPutUserService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *XPackSecurityPutUserService) Header(name string, value string) *XPackSecurityPutUserService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *XPackSecurityPutUserService) Headers(headers http.Header) *XPackSecurityPutUserService {
s.headers = headers
return s
}
// Username is the name of the user to add.
func (s *XPackSecurityPutUserService) Username(username string) *XPackSecurityPutUserService {
s.username = username
return s
}
// User specifies the data of the new user.
//
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.4/security-api-put-user.html
// for details.
func (s *XPackSecurityPutUserService) User(user *XPackSecurityPutUserRequest) *XPackSecurityPutUserService {
s.user = user
return s
}
// Refresh specifies if and how to wait for refreshing the shards after the request.
// Possible values are "true" (default), "false" and "wait_for", all of type string.
func (s *XPackSecurityPutUserService) Refresh(refresh string) *XPackSecurityPutUserService {
s.refresh = refresh
return s
}
// Body specifies the user. Use a string or a type that will get serialized as JSON.
func (s *XPackSecurityPutUserService) Body(body interface{}) *XPackSecurityPutUserService {
s.body = body
return s
}
// buildURL builds the URL for the operation.
func (s *XPackSecurityPutUserService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/_security/user/{username}", map[string]string{
"username": s.username,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if v := s.refresh; v != "" {
params.Set("refresh", v)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *XPackSecurityPutUserService) Validate() error {
var invalid []string
if s.username == "" {
invalid = append(invalid, "Username")
}
if s.user == nil && s.body == nil {
invalid = append(invalid, "User")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *XPackSecurityPutUserService) Do(ctx context.Context) (*XPackSecurityPutUserResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
var body interface{}
if s.user != nil {
body = s.user
} else {
body = s.body
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "PUT",
Path: path,
Params: params,
Body: body,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(XPackSecurityPutUserResponse)
if err := json.Unmarshal(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// XPackSecurityPutUserRequest specifies the data required/allowed to add
// a new user.
type XPackSecurityPutUserRequest struct {
Enabled bool `json:"enabled"`
Email string `json:"email,omitempty"`
FullName string `json:"full_name,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
Password string `json:"password,omitempty"`
PasswordHash string `json:"password_hash,omitempty"`
Roles []string `json:"roles"`
}
// XPackSecurityPutUserResponse is the response of XPackSecurityPutUserService.Do.
type XPackSecurityPutUserResponse struct {
Created bool `json:"created"`
}
================================================
FILE: xpack_security_put_user_test.go
================================================
// Copyright 2012-2019 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"testing"
)
func TestXPackSecurityPutUserBuildURL(t *testing.T) {
client := setupTestClientForXpackSecurity(t)
tests := []struct {
Name string
Body interface{}
ExpectedPath string
ExpectErr bool
}{
{
"",
nil,
"",
true,
},
{
"my-user",
nil,
"",
true,
},
{
"",
`{}`,
"",
true,
},
{
"my-user",
`{}`,
"/_security/user/my-user",
false,
},
}
for i, test := range tests {
builder := client.XPackSecurityPutUser(test.Name).Body(test.Body)
err := builder.Validate()
if err != nil {
if !test.ExpectErr {
t.Errorf("case #%d: %v", i+1, err)
continue
}
} else {
// err == nil
if test.ExpectErr {
t.Errorf("case #%d: expected error", i+1)
continue
}
path, _, _ := builder.buildURL()
if path != test.ExpectedPath {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.ExpectedPath, path)
}
}
}
}
================================================
FILE: xpack_test.go
================================================
package elastic
import (
"context"
"fmt"
"testing"
"time"
)
const (
testRoleBody = `{
"cluster" : [ "all" ],
"indices" : [
{
"names" : [ "index1", "index2" ],
"privileges" : [ "all" ],
"field_security" : {
"grant" : [ "title", "body" ]
}
}
],
"applications" : [ ],
"run_as" : [ "other_user" ],
"global" : {
"application": {
"manage": {
"applications": [ "my-test-app" ]
}
}
},
"metadata" : {
"version" : 1
},
"transient_metadata": {
"enabled": true
}
}`
testRoleMappingBody = `{
"enabled": false,
"roles": [
"user"
],
"rules": {
"all": [
{
"field": {
"username": "esadmin"
}
},
{
"field": {
"groups": "cn=admins,dc=example,dc=com"
}
}
]
},
"metadata": {
"version": 1
}
}`
testUserBody = `{
"password": "secret",
"roles": ["admin"]
}`
testWatchBody = `{
"trigger" : {
"schedule" : { "cron" : "0 0/1 * * * ?" }
},
"input" : {
"search" : {
"request" : {
"indices" : [
"elastic-test"
],
"body" : {
"query" : {
"bool" : {
"must" : {
"match": {
"response": 404
}
},
"filter" : {
"range": {
"@timestamp": {
"from": "{{ctx.trigger.scheduled_time}}||-5m",
"to": "{{ctx.trigger.triggered_time}}"
}
}
}
}
}
}
}
}
},
"condition" : {
"compare" : { "ctx.payload.hits.total" : { "gt" : 0 }}
},
"actions" : {
"email_admin" : {
"email" : {
"to" : "admin@domain.host.com",
"subject" : "404 recently encountered"
}
}
}
}`
testRollupBody = `{
"index_pattern": "elastic-orders",
"rollup_index": "orders-rollup",
"cron": "*/30 * * * * ?",
"page_size" :1000,
"groups" : {
"date_histogram": {
"field": "time",
"interval": "1h",
"delay": "7d"
},
"terms": {
"fields": ["manufacturer"]
}
},
"metrics": [
{
"field": "price",
"metrics": ["min", "max", "sum"]
}
]
}`
)
func TestXpackInfo(t *testing.T) {
client := setupTestClientForXpackSecurity(t)
tagline := "You know, for X"
// Get xpack info
info, err := client.XPackInfo().Do(context.Background())
if err != nil {
t.Fatal(err)
}
if info == &(XPackInfoServiceResponse{}) {
t.Errorf("expected data from response; got empty response")
}
if info.Tagline != tagline {
t.Errorf("expected %s as a tagline; received %s", tagline, info.Tagline)
}
}
func TestXPackSecurityRole(t *testing.T) {
client := setupTestClientForXpackSecurity(t)
xpack_info, err := client.XPackInfo().Do(context.Background())
if err != nil {
t.Fatal(err)
}
if !xpack_info.Features.Security.Enabled {
t.Skip("skip due to deactivated xpack security")
}
roleName := "my-role"
// Add a role
_, err = client.XPackSecurityPutRole(roleName).Body(testRoleBody).Do(context.Background())
if err != nil {
t.Fatal(err)
}
defer func() {
client.XPackSecurityDeleteRole(roleName).Do(context.Background())
}()
// Get a role
role, err := client.XPackSecurityGetRole(roleName).Do(context.Background())
if err != nil {
t.Fatal(err)
}
if len(*role) == 0 {
t.Errorf("expected len(Mappings) > 0; got empty")
}
if _, ok := (*role)[roleName]; !ok {
t.Errorf("expected role mapping %s; key did not exist", roleName)
}
if role == &(XPackSecurityGetRoleResponse{}) {
t.Errorf("expected data from response; got empty response")
}
// Delete a role
deletedRole, err := client.XPackSecurityDeleteRole(roleName).Do(context.Background())
if err != nil {
t.Fatal(err)
}
if !deletedRole.Found {
t.Error("expected test role to be found; was not found")
}
}
func TestXPackSecurityRoleMapping(t *testing.T) {
client := setupTestClientForXpackSecurity(t)
xpack_info, err := client.XPackInfo().Do(context.Background())
if err != nil {
t.Fatal(err)
}
if !xpack_info.Features.Security.Enabled {
t.Skip("skip due to deactivated xpack security")
}
roleMappingName := "my-role-mapping"
// Add a role mapping
_, err = client.XPackSecurityPutRoleMapping(roleMappingName).Body(testRoleMappingBody).Do(context.Background())
if err != nil {
t.Fatal(err)
}
defer func() {
client.XPackSecurityDeleteRoleMapping(roleMappingName).Do(context.Background())
}()
// Get a role mapping
roleMappings, err := client.XPackSecurityGetRoleMapping(roleMappingName).Do(context.Background())
if err != nil {
t.Fatal(err)
}
if len(*roleMappings) == 0 {
t.Errorf("expected len(Mappings) > 0; got empty")
}
if _, ok := (*roleMappings)[roleMappingName]; !ok {
t.Errorf("expected role mapping %s; key did not exist", roleMappingName)
}
if roleMappings == &(XPackSecurityGetRoleMappingResponse{}) {
t.Errorf("expected data from response; got empty response")
}
// Delete a role mapping
_, err = client.XPackSecurityDeleteRoleMapping(roleMappingName).Do(context.Background())
if err != nil {
t.Fatal(err)
}
}
func TestXPackSecurityUser(t *testing.T) {
client := setupTestClientForXpackSecurity(t)
xpackInfo, err := client.XPackInfo().Do(context.Background())
if err != nil {
t.Fatal(err)
}
if !xpackInfo.Features.Security.Enabled {
t.Skip("skip due to deactivated xpack security")
}
username := "john"
// Add a user
createResp, err := client.XPackSecurityPutUser(username).Body(testUserBody).Do(context.Background())
if err != nil {
t.Fatal(err)
}
if createResp == nil {
t.Fatal("expected to create user")
}
if want, have := true, createResp.Created; want != have {
t.Fatalf("want Created=%v, have %v", want, have)
}
defer func() {
client.XPackSecurityDeleteUser(username).Do(context.Background())
}()
// Get a user
user, err := client.XPackSecurityGetUser(username).Do(context.Background())
if err != nil {
t.Fatal(err)
}
if len(*user) == 0 {
t.Errorf("expected len(Mappings) > 0; got empty")
}
if _, ok := (*user)[username]; !ok {
t.Errorf("expected user mapping %s; key did not exist", username)
}
if user == &(XPackSecurityGetUserResponse{}) {
t.Errorf("expected data from response; got empty response")
}
// Disable a user
_, err = client.XPackSecurityDisableUser(username).Do(context.Background())
if err != nil {
t.Fatal(err)
}
user, err = client.XPackSecurityGetUser(username).Do(context.Background())
if err != nil {
t.Fatal(err)
}
if (*user)[username].Enabled {
t.Error("expected test user to be disabled; was still enabled")
}
// Enable a user
_, err = client.XPackSecurityEnableUser(username).Do(context.Background())
if err != nil {
t.Fatal(err)
}
user, err = client.XPackSecurityGetUser(username).Do(context.Background())
if err != nil {
t.Fatal(err)
}
if !(*user)[username].Enabled {
t.Error("expected test user to be enabled; was still disabled")
}
// Delete a user
deletedUser, err := client.XPackSecurityDeleteUser(username).Do(context.Background())
if err != nil {
t.Fatal(err)
}
if !deletedUser.Found {
t.Error("expected test user to be found; was not found")
}
}
func TestXPackWatcher(t *testing.T) {
client := setupTestClientAndCreateIndex(t, SetURL("http://elastic:elastic@localhost:9210"))
xpack_info, err := client.XPackInfo().Do(context.Background())
if err != nil {
t.Fatal(err)
}
if !xpack_info.Features.Watcher.Enabled {
t.Skip("skip due to deactivated xpack watcher")
}
// Add a watch
watchName := "my-watch"
_, err = client.XPackWatchPut(watchName).Body(testWatchBody).Do(context.Background())
if err != nil {
if IsForbidden(err) {
t.Skipf("skip due to missing license: %v", err)
}
t.Fatal(err)
}
defer func() {
client.XPackWatchDelete(watchName).Do(context.Background())
}()
// Get a watch
watch, err := client.XPackWatchGet(watchName).Do(context.Background())
if err != nil {
t.Fatal(err)
}
if watch.Found == false {
t.Errorf("expected watch.Found == true; got false")
}
if want, have := watchName, watch.Id; want != have {
t.Errorf("expected watch.Id == %q; got %q", want, have)
}
// Exec a watch
execution, err := client.XPackWatchExecute().Id(watchName).Do(context.Background())
if err != nil {
t.Fatal(err)
}
if want, have := watchName, execution.WatchRecord.WatchId; want != have {
t.Errorf("expected execution.WatchId == %q; got %q", want, have)
}
if want, have := "execution_not_needed", execution.WatchRecord.State; want != have {
t.Errorf("expected execution.state == %q; got %q", want, have)
}
// Ack a watch
ack, err := client.XPackWatchAck(watchName).Do(context.Background())
if err != nil {
t.Fatal(err)
}
if ack.Status.State == nil {
t.Errorf("expected ack.status != nil; got %v", ack.Status.State)
}
// Activate a watch
_, err = client.XPackWatchActivate(watchName).Do(context.Background())
if err != nil {
t.Fatal(err)
}
watch, err = client.XPackWatchGet(watchName).Do(context.Background())
if err != nil {
t.Fatal(err)
}
if want, have := true, watch.Status.State.Active; want != have {
t.Errorf("expected watch.Status.State.Active == %v; got %v", want, have)
}
// Deactivate the watch
_, err = client.XPackWatchDeactivate(watchName).Do(context.Background())
if err != nil {
t.Fatal(err)
}
watch, err = client.XPackWatchGet(watchName).Do(context.Background())
if err != nil {
t.Fatal(err)
}
if want, have := false, watch.Status.State.Active; want != have {
t.Errorf("expected watch.Status.State.Active == %v; got %v", want, have)
}
// Stop the watch
_, err = client.XPackWatchStop().Do(context.Background())
if err != nil {
t.Fatal(err)
}
stats, err := client.XPackWatchStats().Do(context.Background())
if err != nil {
t.Fatal(err)
}
if have := stats.Stats[0].WatcherState; have != "stopping" && have != "stopped" {
t.Errorf("expected stats.WatcherState == %q (or %q); got %q", "stopping", "stopped", have)
}
// Start again
start, err := client.XPackWatchStart().Do(context.Background())
if err != nil {
t.Fatal(err)
}
_, err = client.XPackWatchStats().Do(context.Background())
if err != nil {
t.Fatal(err)
}
if want, have := true, start.Acknowledged; want != have {
t.Errorf("expected start.Acknowledged == %v; got %v", want, have)
}
}
func TestXPackRollup(t *testing.T) {
client := setupTestClientAndCreateIndexAndAddDocs(t, SetURL("http://elastic:elastic@localhost:9210"))
xpack_info, err := client.XPackInfo().Do(context.Background())
if err != nil {
t.Fatal(err)
}
if !xpack_info.Features.Rollup.Enabled {
t.Skip("skip due to deactivated xpack rollup")
}
// Adding timestamp to the job id here to improve test re-run ablity, relates to issue where rollup jobs are
// not cleanly removed leaving _meta behind. https://github.com/elastic/elasticsearch/issues/31347
jobId := fmt.Sprintf("my-job-%d", time.Now().Unix())
// Add a rollup job
_, err = client.XPackRollupPut(jobId).Body(testRollupBody).Do(context.Background())
if err != nil {
if IsForbidden(err) {
t.Skipf("skip due to missing license: %v", err)
}
t.Fatal(err)
}
defer func() {
client.XPackRollupDelete(jobId).Do(context.Background())
}()
// Get rollup jobs
jobs, err := client.XPackRollupGet(jobId).Do(context.Background())
if err != nil {
t.Fatal(err)
}
if len(jobs.Jobs) != 1 {
t.Errorf("expected len(jobs.Jobs) == 1; got %d", len(jobs.Jobs))
}
if want, have := jobs.Jobs[0].Config.IndexPattern, "elastic-orders"; want != have {
t.Errorf("expected IndexPattern == %q; got %q", want, have)
}
// Start rollup job
_, err = client.XPackRollupStart(jobId).Do(context.Background())
if err != nil {
t.Fatal(err)
}
jobs, err = client.XPackRollupGet(jobId).Do(context.Background())
if err != nil {
t.Fatal(err)
}
if want, have := "started", jobs.Jobs[0].Status.JobState; want != have {
t.Errorf("expected job.Status.JobState == %v; got %v", want, have)
}
// Stop rollup job
_, err = client.XPackRollupStop(jobId).Do(context.Background())
if err != nil {
t.Fatal(err)
}
jobs, err = client.XPackRollupGet(jobId).Do(context.Background())
if err != nil {
t.Fatal(err)
}
if want, have := "stopped", jobs.Jobs[0].Status.JobState; want != have {
t.Errorf("expected job.Status.JobState == %v; got %v", want, have)
}
// Delete rollup job
_, err = client.XPackRollupDelete(jobId).Do(context.Background())
if err != nil {
t.Fatal(err)
}
jobs, err = client.XPackRollupGet(jobId).Do(context.Background())
if err != nil {
t.Fatal(err)
}
if len(jobs.Jobs) > 0 {
t.Errorf("expected len(jobs.Jobs) == 0; got %d", len(jobs.Jobs))
}
}
================================================
FILE: xpack_watcher_ack_watch.go
================================================
// Copyright 2012-2018 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// XPackWatcherAckWatchService enables you to manually throttle execution of the watch’s actions.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/watcher-api-ack-watch.html.
type XPackWatcherAckWatchService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
watchId string
actionId []string
masterTimeout string
}
// NewXPackWatcherAckWatchService creates a new XPackWatcherAckWatchService.
func NewXPackWatcherAckWatchService(client *Client) *XPackWatcherAckWatchService {
return &XPackWatcherAckWatchService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *XPackWatcherAckWatchService) Pretty(pretty bool) *XPackWatcherAckWatchService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *XPackWatcherAckWatchService) Human(human bool) *XPackWatcherAckWatchService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *XPackWatcherAckWatchService) ErrorTrace(errorTrace bool) *XPackWatcherAckWatchService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *XPackWatcherAckWatchService) FilterPath(filterPath ...string) *XPackWatcherAckWatchService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *XPackWatcherAckWatchService) Header(name string, value string) *XPackWatcherAckWatchService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *XPackWatcherAckWatchService) Headers(headers http.Header) *XPackWatcherAckWatchService {
s.headers = headers
return s
}
// WatchId is the unique ID of the watch.
func (s *XPackWatcherAckWatchService) WatchId(watchId string) *XPackWatcherAckWatchService {
s.watchId = watchId
return s
}
// ActionId is a slice of action ids to be acked.
func (s *XPackWatcherAckWatchService) ActionId(actionId ...string) *XPackWatcherAckWatchService {
s.actionId = append(s.actionId, actionId...)
return s
}
// MasterTimeout indicates an explicit operation timeout for
// connection to master node.
func (s *XPackWatcherAckWatchService) MasterTimeout(masterTimeout string) *XPackWatcherAckWatchService {
s.masterTimeout = masterTimeout
return s
}
// buildURL builds the URL for the operation.
func (s *XPackWatcherAckWatchService) buildURL() (string, url.Values, error) {
// Build URL
var (
path string
err error
)
if len(s.actionId) > 0 {
path, err = uritemplates.Expand("/_watcher/watch/{watch_id}/_ack/{action_id}", map[string]string{
"watch_id": s.watchId,
"action_id": strings.Join(s.actionId, ","),
})
} else {
path, err = uritemplates.Expand("/_watcher/watch/{watch_id}/_ack", map[string]string{
"watch_id": s.watchId,
})
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *XPackWatcherAckWatchService) Validate() error {
var invalid []string
if s.watchId == "" {
invalid = append(invalid, "WatchId")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *XPackWatcherAckWatchService) Do(ctx context.Context) (*XPackWatcherAckWatchResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "PUT",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(XPackWatcherAckWatchResponse)
if err := json.Unmarshal(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// XPackWatcherAckWatchResponse is the response of XPackWatcherAckWatchService.Do.
type XPackWatcherAckWatchResponse struct {
Status *XPackWatcherAckWatchStatus `json:"status"`
}
// XPackWatcherAckWatchStatus is the status of a XPackWatcherAckWatchResponse.
type XPackWatcherAckWatchStatus struct {
State map[string]interface{} `json:"state"`
LastChecked string `json:"last_checked"`
LastMetCondition string `json:"last_met_condition"`
Actions map[string]map[string]interface{} `json:"actions"`
ExecutionState string `json:"execution_state"`
Version int `json:"version"`
}
================================================
FILE: xpack_watcher_ack_watch_test.go
================================================
// Copyright 2012-2018 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"testing"
)
func TestXPackWatcherAckWatchBuildURL(t *testing.T) {
client := setupTestClient(t) // , SetURL("http://elastic:elastic@localhost:9210"))
tests := []struct {
WatchId string
ActionId []string
Expected string
ExpectErr bool
}{
{
"",
[]string{},
"",
true,
},
{
"my-watch",
[]string{},
"/_watcher/watch/my-watch/_ack",
false,
},
{
"my-watch",
[]string{"action1"},
"/_watcher/watch/my-watch/_ack/action1",
false,
},
{
"my-watch",
[]string{"action1", "action2"},
"/_watcher/watch/my-watch/_ack/action1%2Caction2",
false,
},
}
for i, test := range tests {
builder := client.XPackWatchAck(test.WatchId).ActionId(test.ActionId...)
err := builder.Validate()
if err != nil {
if !test.ExpectErr {
t.Errorf("case #%d: %v", i+1, err)
continue
}
} else {
// err == nil
if test.ExpectErr {
t.Errorf("case #%d: expected error", i+1)
continue
}
path, _, _ := builder.buildURL()
if path != test.Expected {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.Expected, path)
}
}
}
}
================================================
FILE: xpack_watcher_activate_watch.go
================================================
// Copyright 2012-2018 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// XPackWatcherActivateWatchService enables you to activate a currently inactive watch.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/watcher-api-activate-watch.html.
type XPackWatcherActivateWatchService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
watchId string
masterTimeout string
}
// NewXPackWatcherActivateWatchService creates a new XPackWatcherActivateWatchService.
func NewXPackWatcherActivateWatchService(client *Client) *XPackWatcherActivateWatchService {
return &XPackWatcherActivateWatchService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *XPackWatcherActivateWatchService) Pretty(pretty bool) *XPackWatcherActivateWatchService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *XPackWatcherActivateWatchService) Human(human bool) *XPackWatcherActivateWatchService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *XPackWatcherActivateWatchService) ErrorTrace(errorTrace bool) *XPackWatcherActivateWatchService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *XPackWatcherActivateWatchService) FilterPath(filterPath ...string) *XPackWatcherActivateWatchService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *XPackWatcherActivateWatchService) Header(name string, value string) *XPackWatcherActivateWatchService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *XPackWatcherActivateWatchService) Headers(headers http.Header) *XPackWatcherActivateWatchService {
s.headers = headers
return s
}
// WatchId is the ID of the watch to activate.
func (s *XPackWatcherActivateWatchService) WatchId(watchId string) *XPackWatcherActivateWatchService {
s.watchId = watchId
return s
}
// MasterTimeout specifies an explicit operation timeout for connection to master node.
func (s *XPackWatcherActivateWatchService) MasterTimeout(masterTimeout string) *XPackWatcherActivateWatchService {
s.masterTimeout = masterTimeout
return s
}
// buildURL builds the URL for the operation.
func (s *XPackWatcherActivateWatchService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/_watcher/watch/{watch_id}/_activate", map[string]string{
"watch_id": s.watchId,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *XPackWatcherActivateWatchService) Validate() error {
var invalid []string
if s.watchId == "" {
invalid = append(invalid, "WatchId")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *XPackWatcherActivateWatchService) Do(ctx context.Context) (*XPackWatcherActivateWatchResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "PUT",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(XPackWatcherActivateWatchResponse)
if err := json.Unmarshal(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// XPackWatcherActivateWatchResponse is the response of XPackWatcherActivateWatchService.Do.
type XPackWatcherActivateWatchResponse struct {
Status *XPackWatchStatus `json:"status"`
}
================================================
FILE: xpack_watcher_activate_watch_test.go
================================================
// Copyright 2012-2018 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"testing"
)
func TestXPackWatcherActivateWatchBuildURL(t *testing.T) {
client := setupTestClient(t) // , SetURL("http://elastic:elastic@localhost:9210"))
tests := []struct {
WatchId string
Expected string
ExpectErr bool
}{
{
"",
"",
true,
},
{
"my-watch",
"/_watcher/watch/my-watch/_activate",
false,
},
}
for i, test := range tests {
builder := client.XPackWatchActivate(test.WatchId)
err := builder.Validate()
if err != nil {
if !test.ExpectErr {
t.Errorf("case #%d: %v", i+1, err)
continue
}
} else {
// err == nil
if test.ExpectErr {
t.Errorf("case #%d: expected error", i+1)
continue
}
path, _, _ := builder.buildURL()
if path != test.Expected {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.Expected, path)
}
}
}
}
================================================
FILE: xpack_watcher_deactivate_watch.go
================================================
// Copyright 2012-2018 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// XPackWatcherDeactivateWatchService enables you to deactivate a currently active watch.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/watcher-api-deactivate-watch.html.
type XPackWatcherDeactivateWatchService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
watchId string
masterTimeout string
}
// NewXPackWatcherDeactivateWatchService creates a new XPackWatcherDeactivateWatchService.
func NewXPackWatcherDeactivateWatchService(client *Client) *XPackWatcherDeactivateWatchService {
return &XPackWatcherDeactivateWatchService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *XPackWatcherDeactivateWatchService) Pretty(pretty bool) *XPackWatcherDeactivateWatchService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *XPackWatcherDeactivateWatchService) Human(human bool) *XPackWatcherDeactivateWatchService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *XPackWatcherDeactivateWatchService) ErrorTrace(errorTrace bool) *XPackWatcherDeactivateWatchService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *XPackWatcherDeactivateWatchService) FilterPath(filterPath ...string) *XPackWatcherDeactivateWatchService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *XPackWatcherDeactivateWatchService) Header(name string, value string) *XPackWatcherDeactivateWatchService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *XPackWatcherDeactivateWatchService) Headers(headers http.Header) *XPackWatcherDeactivateWatchService {
s.headers = headers
return s
}
// WatchId is the ID of the watch to deactivate.
func (s *XPackWatcherDeactivateWatchService) WatchId(watchId string) *XPackWatcherDeactivateWatchService {
s.watchId = watchId
return s
}
// MasterTimeout specifies an explicit operation timeout for connection to master node.
func (s *XPackWatcherDeactivateWatchService) MasterTimeout(masterTimeout string) *XPackWatcherDeactivateWatchService {
s.masterTimeout = masterTimeout
return s
}
// buildURL builds the URL for the operation.
func (s *XPackWatcherDeactivateWatchService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/_watcher/watch/{watch_id}/_deactivate", map[string]string{
"watch_id": s.watchId,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *XPackWatcherDeactivateWatchService) Validate() error {
var invalid []string
if s.watchId == "" {
invalid = append(invalid, "WatchId")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *XPackWatcherDeactivateWatchService) Do(ctx context.Context) (*XPackWatcherDeactivateWatchResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "PUT",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(XPackWatcherDeactivateWatchResponse)
if err := json.Unmarshal(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// XPackWatcherDeactivateWatchResponse is the response of XPackWatcherDeactivateWatchService.Do.
type XPackWatcherDeactivateWatchResponse struct {
Status *XPackWatchStatus `json:"status"`
}
================================================
FILE: xpack_watcher_deactivate_watch_test.go
================================================
// Copyright 2012-2018 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"testing"
)
func TestXPackWatcherDeactivateWatchBuildURL(t *testing.T) {
client := setupTestClient(t) // , SetURL("http://elastic:elastic@localhost:9210"))
tests := []struct {
WatchId string
Expected string
ExpectErr bool
}{
{
"",
"",
true,
},
{
"my-watch",
"/_watcher/watch/my-watch/_deactivate",
false,
},
}
for i, test := range tests {
builder := client.XPackWatchDeactivate(test.WatchId)
err := builder.Validate()
if err != nil {
if !test.ExpectErr {
t.Errorf("case #%d: %v", i+1, err)
continue
}
} else {
// err == nil
if test.ExpectErr {
t.Errorf("case #%d: expected error", i+1)
continue
}
path, _, _ := builder.buildURL()
if path != test.Expected {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.Expected, path)
}
}
}
}
================================================
FILE: xpack_watcher_delete_watch.go
================================================
// Copyright 2012-2018 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// XPackWatcherDeleteWatchService removes a watch.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/watcher-api-delete-watch.html.
type XPackWatcherDeleteWatchService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
id string
masterTimeout string
}
// NewXPackWatcherDeleteWatchService creates a new XPackWatcherDeleteWatchService.
func NewXPackWatcherDeleteWatchService(client *Client) *XPackWatcherDeleteWatchService {
return &XPackWatcherDeleteWatchService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *XPackWatcherDeleteWatchService) Pretty(pretty bool) *XPackWatcherDeleteWatchService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *XPackWatcherDeleteWatchService) Human(human bool) *XPackWatcherDeleteWatchService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *XPackWatcherDeleteWatchService) ErrorTrace(errorTrace bool) *XPackWatcherDeleteWatchService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *XPackWatcherDeleteWatchService) FilterPath(filterPath ...string) *XPackWatcherDeleteWatchService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *XPackWatcherDeleteWatchService) Header(name string, value string) *XPackWatcherDeleteWatchService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *XPackWatcherDeleteWatchService) Headers(headers http.Header) *XPackWatcherDeleteWatchService {
s.headers = headers
return s
}
// Id of the watch to delete.
func (s *XPackWatcherDeleteWatchService) Id(id string) *XPackWatcherDeleteWatchService {
s.id = id
return s
}
// MasterTimeout specifies an explicit operation timeout for connection to master node.
func (s *XPackWatcherDeleteWatchService) MasterTimeout(masterTimeout string) *XPackWatcherDeleteWatchService {
s.masterTimeout = masterTimeout
return s
}
// buildURL builds the URL for the operation.
func (s *XPackWatcherDeleteWatchService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/_watcher/watch/{id}", map[string]string{
"id": s.id,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *XPackWatcherDeleteWatchService) Validate() error {
var invalid []string
if s.id == "" {
invalid = append(invalid, "Id")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *XPackWatcherDeleteWatchService) Do(ctx context.Context) (*XPackWatcherDeleteWatchResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "DELETE",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(XPackWatcherDeleteWatchResponse)
if err := json.Unmarshal(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// XPackWatcherDeleteWatchResponse is the response of XPackWatcherDeleteWatchService.Do.
type XPackWatcherDeleteWatchResponse struct {
Found bool `json:"found"`
Id string `json:"_id"`
Version int `json:"_version"`
}
================================================
FILE: xpack_watcher_delete_watch_test.go
================================================
// Copyright 2012-2018 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"testing"
)
func TestXPackWatcherDeleteWatchBuildURL(t *testing.T) {
client := setupTestClient(t) // , SetURL("http://elastic:elastic@localhost:9210"))
tests := []struct {
Id string
Expected string
ExpectErr bool
}{
{
"",
"",
true,
},
{
"my-watch",
"/_watcher/watch/my-watch",
false,
},
}
for i, test := range tests {
builder := client.XPackWatchDelete(test.Id)
err := builder.Validate()
if err != nil {
if !test.ExpectErr {
t.Errorf("case #%d: %v", i+1, err)
continue
}
} else {
// err == nil
if test.ExpectErr {
t.Errorf("case #%d: expected error", i+1)
continue
}
path, _, _ := builder.buildURL()
if path != test.Expected {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.Expected, path)
}
}
}
}
================================================
FILE: xpack_watcher_execute_watch.go
================================================
// Copyright 2012-2018 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// XPackWatcherExecuteWatchService forces the execution of a stored watch.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/watcher-api-execute-watch.html.
type XPackWatcherExecuteWatchService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
id string
debug *bool
bodyJson interface{}
bodyString string
}
// NewXPackWatcherExecuteWatchService creates a new XPackWatcherExecuteWatchService.
func NewXPackWatcherExecuteWatchService(client *Client) *XPackWatcherExecuteWatchService {
return &XPackWatcherExecuteWatchService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *XPackWatcherExecuteWatchService) Pretty(pretty bool) *XPackWatcherExecuteWatchService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *XPackWatcherExecuteWatchService) Human(human bool) *XPackWatcherExecuteWatchService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *XPackWatcherExecuteWatchService) ErrorTrace(errorTrace bool) *XPackWatcherExecuteWatchService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *XPackWatcherExecuteWatchService) FilterPath(filterPath ...string) *XPackWatcherExecuteWatchService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *XPackWatcherExecuteWatchService) Header(name string, value string) *XPackWatcherExecuteWatchService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *XPackWatcherExecuteWatchService) Headers(headers http.Header) *XPackWatcherExecuteWatchService {
s.headers = headers
return s
}
// Id of the watch to execute on.
func (s *XPackWatcherExecuteWatchService) Id(id string) *XPackWatcherExecuteWatchService {
s.id = id
return s
}
// Debug indicates whether the watch should execute in debug mode.
func (s *XPackWatcherExecuteWatchService) Debug(debug bool) *XPackWatcherExecuteWatchService {
s.debug = &debug
return s
}
// BodyJson is documented as: Execution control.
func (s *XPackWatcherExecuteWatchService) BodyJson(body interface{}) *XPackWatcherExecuteWatchService {
s.bodyJson = body
return s
}
// BodyString is documented as: Execution control.
func (s *XPackWatcherExecuteWatchService) BodyString(body string) *XPackWatcherExecuteWatchService {
s.bodyString = body
return s
}
// buildURL builds the URL for the operation.
func (s *XPackWatcherExecuteWatchService) buildURL() (string, url.Values, error) {
// Build URL
var (
path string
err error
)
if s.id != "" {
path, err = uritemplates.Expand("/_watcher/watch/{id}/_execute", map[string]string{
"id": s.id,
})
} else {
path = "/_watcher/watch/_execute"
}
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if v := s.debug; v != nil {
params.Set("debug", fmt.Sprint(*v))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *XPackWatcherExecuteWatchService) Validate() error {
return nil
}
// Do executes the operation.
func (s *XPackWatcherExecuteWatchService) Do(ctx context.Context) (*XPackWatcherExecuteWatchResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Setup HTTP request body
var body interface{}
if s.bodyJson != nil {
body = s.bodyJson
} else {
body = s.bodyString
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "PUT",
Path: path,
Params: params,
Body: body,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(XPackWatcherExecuteWatchResponse)
if err := json.Unmarshal(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// XPackWatcherExecuteWatchResponse is the response of XPackWatcherExecuteWatchService.Do.
type XPackWatcherExecuteWatchResponse struct {
Id string `json:"_id"`
WatchRecord *XPackWatchRecord `json:"watch_record"`
}
type XPackWatchRecord struct {
WatchId string `json:"watch_id"`
Node string `json:"node"`
Messages []string `json:"messages"`
State string `json:"state"`
Status *XPackWatchRecordStatus `json:"status"`
Input map[string]map[string]interface{} `json:"input"`
Condition map[string]map[string]interface{} `json:"condition"`
Result map[string]interface{} `json:"Result"`
}
type XPackWatchRecordStatus struct {
Version int `json:"version"`
State map[string]interface{} `json:"state"`
LastChecked string `json:"last_checked"`
LastMetCondition string `json:"last_met_condition"`
Actions map[string]map[string]interface{} `json:"actions"`
ExecutionState string `json:"execution_state"`
}
================================================
FILE: xpack_watcher_execute_watch_test.go
================================================
// Copyright 2012-2018 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"testing"
)
func TestXPackWatcherExecuteWatchBuildURL(t *testing.T) {
client := setupTestClient(t) // , SetURL("http://elastic:elastic@localhost:9210"))
tests := []struct {
Id string
Expected string
ExpectErr bool
}{
{
"",
"/_watcher/watch/_execute",
false,
},
{
"my-watch",
"/_watcher/watch/my-watch/_execute",
false,
},
}
for i, test := range tests {
builder := client.XPackWatchExecute().Id(test.Id)
err := builder.Validate()
if err != nil {
if !test.ExpectErr {
t.Errorf("case #%d: %v", i+1, err)
continue
}
} else {
// err == nil
if test.ExpectErr {
t.Errorf("case #%d: expected error", i+1)
continue
}
path, _, _ := builder.buildURL()
if path != test.Expected {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.Expected, path)
}
}
}
}
================================================
FILE: xpack_watcher_get_watch.go
================================================
// Copyright 2012-2018 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"time"
"github.com/olivere/elastic/v7/uritemplates"
)
// XPackWatcherGetWatchService retrieves a watch by its ID.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/watcher-api-get-watch.html.
type XPackWatcherGetWatchService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
id string
}
// NewXPackWatcherGetWatchService creates a new XPackWatcherGetWatchService.
func NewXPackWatcherGetWatchService(client *Client) *XPackWatcherGetWatchService {
return &XPackWatcherGetWatchService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *XPackWatcherGetWatchService) Pretty(pretty bool) *XPackWatcherGetWatchService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *XPackWatcherGetWatchService) Human(human bool) *XPackWatcherGetWatchService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *XPackWatcherGetWatchService) ErrorTrace(errorTrace bool) *XPackWatcherGetWatchService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *XPackWatcherGetWatchService) FilterPath(filterPath ...string) *XPackWatcherGetWatchService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *XPackWatcherGetWatchService) Header(name string, value string) *XPackWatcherGetWatchService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *XPackWatcherGetWatchService) Headers(headers http.Header) *XPackWatcherGetWatchService {
s.headers = headers
return s
}
// Id is ID of the watch to retrieve.
func (s *XPackWatcherGetWatchService) Id(id string) *XPackWatcherGetWatchService {
s.id = id
return s
}
// buildURL builds the URL for the operation.
func (s *XPackWatcherGetWatchService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/_watcher/watch/{id}", map[string]string{
"id": s.id,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *XPackWatcherGetWatchService) Validate() error {
var invalid []string
if s.id == "" {
invalid = append(invalid, "Id")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *XPackWatcherGetWatchService) Do(ctx context.Context) (*XPackWatcherGetWatchResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(XPackWatcherGetWatchResponse)
if err := json.Unmarshal(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// XPackWatcherGetWatchResponse is the response of XPackWatcherGetWatchService.Do.
type XPackWatcherGetWatchResponse struct {
Found bool `json:"found"`
Id string `json:"_id"`
Version int64 `json:"_version,omitempty"`
Status *XPackWatchStatus `json:"status,omitempty"`
Watch *XPackWatch `json:"watch,omitempty"`
}
type XPackWatchStatus struct {
State *XPackWatchExecutionState `json:"state,omitempty"`
LastChecked *time.Time `json:"last_checked,omitempty"`
LastMetCondition *time.Time `json:"last_met_condition,omitempty"`
Actions map[string]*XPackWatchActionStatus `json:"actions,omitempty"`
ExecutionState string `json:"execution_state,omitempty"`
Headers map[string]string `json:"headers,omitempty"`
Version int64 `json:"version"`
}
type XPackWatchExecutionState struct {
Active bool `json:"active"`
Timestamp time.Time `json:"timestamp"`
}
type XPackWatchActionStatus struct {
AckStatus *XPackWatchActionAckStatus `json:"ack"`
LastExecution *XPackWatchActionExecutionState `json:"last_execution,omitempty"`
LastSuccessfulExecution *XPackWatchActionExecutionState `json:"last_successful_execution,omitempty"`
LastThrottle *XPackWatchActionThrottle `json:"last_throttle,omitempty"`
}
type XPackWatchActionAckStatus struct {
Timestamp time.Time `json:"timestamp"`
State string `json:"state"`
}
type XPackWatchActionExecutionState struct {
Timestamp time.Time `json:"timestamp"`
Successful bool `json:"successful"`
Reason string `json:"reason,omitempty"`
}
type XPackWatchActionThrottle struct {
Timestamp time.Time `json:"timestamp"`
Reason string `json:"reason,omitempty"`
}
type XPackWatch struct {
Trigger map[string]map[string]interface{} `json:"trigger"`
Input map[string]map[string]interface{} `json:"input"`
Condition map[string]map[string]interface{} `json:"condition"`
Transform map[string]interface{} `json:"transform,omitempty"`
ThrottlePeriod string `json:"throttle_period,omitempty"`
ThrottlePeriodInMillis int64 `json:"throttle_period_in_millis,omitempty"`
Actions map[string]map[string]interface{} `json:"actions"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
Status *XPackWatchStatus `json:"status,omitempty"`
}
================================================
FILE: xpack_watcher_get_watch_test.go
================================================
// Copyright 2012-2018 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"encoding/json"
"testing"
"time"
"github.com/google/go-cmp/cmp"
)
func TestXPackWatcherGetWatchBuildURL(t *testing.T) {
client := setupTestClient(t) // , SetURL("http://elastic:elastic@localhost:9210"))
tests := []struct {
Id string
Expected string
ExpectErr bool
}{
{
"",
"",
true,
},
{
"my-watch",
"/_watcher/watch/my-watch",
false,
},
}
for i, test := range tests {
builder := client.XPackWatchGet(test.Id)
err := builder.Validate()
if err != nil {
if !test.ExpectErr {
t.Errorf("case #%d: %v", i+1, err)
continue
}
} else {
// err == nil
if test.ExpectErr {
t.Errorf("case #%d: expected error", i+1)
continue
}
path, _, _ := builder.buildURL()
if path != test.Expected {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.Expected, path)
}
}
}
}
func TestXPackWatchActionStatus_UnmarshalJSON(t *testing.T) {
tests := []struct {
Input []byte
ExpectErr bool
}{
{
[]byte(`
{
"ack" : {
"timestamp" : "2019-10-22T15:01:12.163Z",
"state" : "ackable"
},
"last_execution" : {
"timestamp" : "2019-10-22T15:01:12.163Z",
"successful" : true
},
"last_successful_execution" : {
"timestamp" : "2019-10-22T15:01:12.163Z",
"successful" : true
}
}
`),
false,
},
}
for i, test := range tests {
var status XPackWatchActionStatus
err := json.Unmarshal(test.Input, &status)
if err != nil {
t.Errorf("#%d: expected no error, got %v", i+1, err)
}
if status.AckStatus == nil {
t.Errorf("#%d: expected AckStatus!=nil", i+1)
}
if status.LastExecution == nil {
t.Errorf("#%d: expected LastExecution!=nil", i+1)
}
if status.LastSuccessfulExecution == nil {
t.Errorf("#%d: expected LastSuccessfulExecution!=nil", i+1)
}
}
}
func TestXPackWatchResponseParser(t *testing.T) {
mustParseTime := func(layout, value string) time.Time {
dt, err := time.Parse(layout, value)
if err != nil {
t.Fatal(err)
}
return dt
}
tests := []struct {
Input []byte
ExpectErr bool
Response *XPackWatcherGetWatchResponse
}{
{
[]byte(`
{
"found": true,
"_id": "my_watch",
"_seq_no": 0,
"_primary_term": 1,
"_version": 1,
"status": {
"version": 1,
"state": {
"active": true,
"timestamp": "2015-05-26T18:21:08.630Z"
},
"actions": {
"test_index": {
"ack": {
"timestamp": "2015-05-26T18:21:08.630Z",
"state": "awaits_successful_execution"
}
}
}
},
"watch": {
"input": {
"simple": {
"payload": {
"send": "yes"
}
}
},
"condition": {
"always": {}
},
"trigger": {
"schedule": {
"hourly": {
"minute": [0, 5]
}
}
},
"actions": {
"test_index": {
"index": {
"index": "test"
}
}
}
}
}
`),
false,
&XPackWatcherGetWatchResponse{
Found: true,
Id: "my_watch",
Version: 1,
Status: &XPackWatchStatus{
State: &XPackWatchExecutionState{
Active: true,
Timestamp: mustParseTime(time.RFC3339, "2015-05-26T18:21:08.630Z"),
},
Actions: map[string]*XPackWatchActionStatus{
"test_index": {
AckStatus: &XPackWatchActionAckStatus{
State: "awaits_successful_execution",
Timestamp: mustParseTime(time.RFC3339, "2015-05-26T18:21:08.630Z"),
},
},
},
Version: 1,
},
Watch: &XPackWatch{
Trigger: map[string]map[string]interface{}{
"schedule": {
"hourly": map[string]interface{}{"minute": []interface{}{float64(0), float64(5)}},
},
},
Input: map[string]map[string]interface{}{"simple": {"payload": map[string]interface{}{"send": string("yes")}}},
Condition: map[string]map[string]interface{}{"always": {}},
Actions: map[string]map[string]interface{}{"test_index": {"index": map[string]interface{}{"index": string("test")}}},
},
},
},
}
for i, test := range tests {
var resp XPackWatcherGetWatchResponse
err := json.Unmarshal(test.Input, &resp)
if err != nil {
if !test.ExpectErr {
t.Errorf("#%d: expected no error, got %v", i+1, err)
}
continue
}
if test.ExpectErr {
t.Errorf("#%d: expected error, got none", i+1)
} else {
if want, have := *test.Response, resp; !cmp.Equal(want, have) {
t.Errorf("#%d: diff: %s\n", i+1, cmp.Diff(want, have))
}
}
}
}
================================================
FILE: xpack_watcher_put_watch.go
================================================
// Copyright 2012-2018 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/olivere/elastic/v7/uritemplates"
)
// XPackWatcherPutWatchService either registers a new watch in Watcher
// or update an existing one.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/watcher-api-put-watch.html.
type XPackWatcherPutWatchService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
id string
active *bool
masterTimeout string
ifSeqNo *int64
ifPrimaryTerm *int64
body interface{}
}
// NewXPackWatcherPutWatchService creates a new XPackWatcherPutWatchService.
func NewXPackWatcherPutWatchService(client *Client) *XPackWatcherPutWatchService {
return &XPackWatcherPutWatchService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *XPackWatcherPutWatchService) Pretty(pretty bool) *XPackWatcherPutWatchService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *XPackWatcherPutWatchService) Human(human bool) *XPackWatcherPutWatchService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *XPackWatcherPutWatchService) ErrorTrace(errorTrace bool) *XPackWatcherPutWatchService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *XPackWatcherPutWatchService) FilterPath(filterPath ...string) *XPackWatcherPutWatchService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *XPackWatcherPutWatchService) Header(name string, value string) *XPackWatcherPutWatchService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *XPackWatcherPutWatchService) Headers(headers http.Header) *XPackWatcherPutWatchService {
s.headers = headers
return s
}
// Id of the watch to upsert.
func (s *XPackWatcherPutWatchService) Id(id string) *XPackWatcherPutWatchService {
s.id = id
return s
}
// Active specifies whether the watch is in/active by default.
func (s *XPackWatcherPutWatchService) Active(active bool) *XPackWatcherPutWatchService {
s.active = &active
return s
}
// MasterTimeout is an explicit operation timeout for connection to master node.
func (s *XPackWatcherPutWatchService) MasterTimeout(masterTimeout string) *XPackWatcherPutWatchService {
s.masterTimeout = masterTimeout
return s
}
// IfSeqNo indicates to update the watch only if the last operation that
// has changed the watch has the specified sequence number.
func (s *XPackWatcherPutWatchService) IfSeqNo(seqNo int64) *XPackWatcherPutWatchService {
s.ifSeqNo = &seqNo
return s
}
// IfPrimaryTerm indicates to update the watch only if the last operation that
// has changed the watch has the specified primary term.
func (s *XPackWatcherPutWatchService) IfPrimaryTerm(primaryTerm int64) *XPackWatcherPutWatchService {
s.ifPrimaryTerm = &primaryTerm
return s
}
// Body specifies the watch. Use a string or a type that will get serialized as JSON.
func (s *XPackWatcherPutWatchService) Body(body interface{}) *XPackWatcherPutWatchService {
s.body = body
return s
}
// buildURL builds the URL for the operation.
func (s *XPackWatcherPutWatchService) buildURL() (string, url.Values, error) {
// Build URL
path, err := uritemplates.Expand("/_watcher/watch/{id}", map[string]string{
"id": s.id,
})
if err != nil {
return "", url.Values{}, err
}
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if v := s.active; v != nil {
params.Set("active", fmt.Sprint(*v))
}
if s.masterTimeout != "" {
params.Set("master_timeout", s.masterTimeout)
}
if v := s.ifSeqNo; v != nil {
params.Set("if_seq_no", fmt.Sprintf("%d", *v))
}
if v := s.ifPrimaryTerm; v != nil {
params.Set("if_primary_term", fmt.Sprintf("%d", *v))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *XPackWatcherPutWatchService) Validate() error {
var invalid []string
if s.id == "" {
invalid = append(invalid, "Id")
}
if s.body == nil {
invalid = append(invalid, "Body")
}
if len(invalid) > 0 {
return fmt.Errorf("missing required fields: %v", invalid)
}
return nil
}
// Do executes the operation.
func (s *XPackWatcherPutWatchService) Do(ctx context.Context) (*XPackWatcherPutWatchResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "PUT",
Path: path,
Params: params,
Body: s.body,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(XPackWatcherPutWatchResponse)
if err := json.Unmarshal(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// XPackWatcherPutWatchResponse is the response of XPackWatcherPutWatchService.Do.
type XPackWatcherPutWatchResponse struct {
}
================================================
FILE: xpack_watcher_put_watch_test.go
================================================
// Copyright 2012-2018 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"testing"
)
func TestXPackWatcherPutWatchBuildURL(t *testing.T) {
client := setupTestClient(t) // , SetURL("http://elastic:elastic@localhost:9210"))
tests := []struct {
Id string
Body interface{}
Expected string
ExpectErr bool
}{
{
"",
nil,
"",
true,
},
{
"my-watch",
nil,
"",
true,
},
{
"",
`{}`,
"",
true,
},
{
"my-watch",
`{}`,
"/_watcher/watch/my-watch",
false,
},
}
for i, test := range tests {
builder := client.XPackWatchPut(test.Id).Body(test.Body)
err := builder.Validate()
if err != nil {
if !test.ExpectErr {
t.Errorf("case #%d: %v", i+1, err)
continue
}
} else {
// err == nil
if test.ExpectErr {
t.Errorf("case #%d: expected error", i+1)
continue
}
path, _, _ := builder.buildURL()
if path != test.Expected {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.Expected, path)
}
}
}
}
================================================
FILE: xpack_watcher_start.go
================================================
// Copyright 2012-2018 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
)
// XPackWatcherStartService starts the watcher service if it is not already running.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/watcher-api-start.html.
type XPackWatcherStartService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
}
// NewXPackWatcherStartService creates a new XPackWatcherStartService.
func NewXPackWatcherStartService(client *Client) *XPackWatcherStartService {
return &XPackWatcherStartService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *XPackWatcherStartService) Pretty(pretty bool) *XPackWatcherStartService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *XPackWatcherStartService) Human(human bool) *XPackWatcherStartService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *XPackWatcherStartService) ErrorTrace(errorTrace bool) *XPackWatcherStartService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *XPackWatcherStartService) FilterPath(filterPath ...string) *XPackWatcherStartService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *XPackWatcherStartService) Header(name string, value string) *XPackWatcherStartService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *XPackWatcherStartService) Headers(headers http.Header) *XPackWatcherStartService {
s.headers = headers
return s
}
// buildURL builds the URL for the operation.
func (s *XPackWatcherStartService) buildURL() (string, url.Values, error) {
// Build URL path
path := "/_watcher/_start"
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *XPackWatcherStartService) Validate() error {
return nil
}
// Do executes the operation.
func (s *XPackWatcherStartService) Do(ctx context.Context) (*XPackWatcherStartResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "POST",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(XPackWatcherStartResponse)
if err := json.Unmarshal(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// XPackWatcherStartResponse is the response of XPackWatcherStartService.Do.
type XPackWatcherStartResponse struct {
Acknowledged bool `json:"acknowledged"`
}
================================================
FILE: xpack_watcher_start_test.go
================================================
// Copyright 2012-2018 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"testing"
)
func TestXPackWatcherStartBuildURL(t *testing.T) {
client := setupTestClient(t) // , SetURL("http://elastic:elastic@localhost:9210"))
tests := []struct {
Expected string
ExpectErr bool
}{
{
"/_watcher/_start",
false,
},
}
for i, test := range tests {
builder := client.XPackWatchStart()
err := builder.Validate()
if err != nil {
if !test.ExpectErr {
t.Errorf("case #%d: %v", i+1, err)
continue
}
} else {
// err == nil
if test.ExpectErr {
t.Errorf("case #%d: expected error", i+1)
continue
}
path, _, _ := builder.buildURL()
if path != test.Expected {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.Expected, path)
}
}
}
}
================================================
FILE: xpack_watcher_stats.go
================================================
// Copyright 2012-2018 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
)
// XPackWatcherStatsService returns the current watcher metrics.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/watcher-api-stats.html.
type XPackWatcherStatsService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
metric string
emitStacktraces *bool
}
// NewXPackWatcherStatsService creates a new XPackWatcherStatsService.
func NewXPackWatcherStatsService(client *Client) *XPackWatcherStatsService {
return &XPackWatcherStatsService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *XPackWatcherStatsService) Pretty(pretty bool) *XPackWatcherStatsService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *XPackWatcherStatsService) Human(human bool) *XPackWatcherStatsService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *XPackWatcherStatsService) ErrorTrace(errorTrace bool) *XPackWatcherStatsService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *XPackWatcherStatsService) FilterPath(filterPath ...string) *XPackWatcherStatsService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *XPackWatcherStatsService) Header(name string, value string) *XPackWatcherStatsService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *XPackWatcherStatsService) Headers(headers http.Header) *XPackWatcherStatsService {
s.headers = headers
return s
}
// Metric controls what additional stat metrics should be include in the response.
func (s *XPackWatcherStatsService) Metric(metric string) *XPackWatcherStatsService {
s.metric = metric
return s
}
// EmitStacktraces, if enabled, emits stack traces of currently running watches.
func (s *XPackWatcherStatsService) EmitStacktraces(emitStacktraces bool) *XPackWatcherStatsService {
s.emitStacktraces = &emitStacktraces
return s
}
// buildURL builds the URL for the operation.
func (s *XPackWatcherStatsService) buildURL() (string, url.Values, error) {
// Build URL
path := "/_watcher/stats"
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
if v := s.emitStacktraces; v != nil {
params.Set("emit_stacktraces", fmt.Sprint(*v))
}
if s.metric != "" {
params.Set("metric", s.metric)
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *XPackWatcherStatsService) Validate() error {
return nil
}
// Do executes the operation.
func (s *XPackWatcherStatsService) Do(ctx context.Context) (*XPackWatcherStatsResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "GET",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(XPackWatcherStatsResponse)
if err := json.Unmarshal(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// XPackWatcherStatsResponse is the response of XPackWatcherStatsService.Do.
type XPackWatcherStatsResponse struct {
Stats []XPackWatcherStats `json:"stats"`
}
// XPackWatcherStats represents the stats used in XPackWatcherStatsResponse.
type XPackWatcherStats struct {
WatcherState string `json:"watcher_state"`
WatchCount int `json:"watch_count"`
ExecutionThreadPool map[string]interface{} `json:"execution_thread_pool"`
}
================================================
FILE: xpack_watcher_stats_test.go
================================================
// Copyright 2012-2018 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"testing"
)
func TestXPackWatcherStatsBuildURL(t *testing.T) {
client := setupTestClient(t) // , SetURL("http://elastic:elastic@localhost:9210"))
tests := []struct {
Metric string
Expected string
ExpectErr bool
}{
{
"",
"/_watcher/stats",
false,
},
{
"_all",
"/_watcher/stats",
false,
},
}
for i, test := range tests {
builder := client.XPackWatchStats().Metric(test.Metric)
err := builder.Validate()
if err != nil {
if !test.ExpectErr {
t.Errorf("case #%d: %v", i+1, err)
continue
}
} else {
// err == nil
if test.ExpectErr {
t.Errorf("case #%d: expected error", i+1)
continue
}
path, _, _ := builder.buildURL()
if path != test.Expected {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.Expected, path)
}
}
}
}
================================================
FILE: xpack_watcher_stop.go
================================================
// Copyright 2012-2018 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
)
// XPackWatcherStopService stops the watcher service if it is running.
// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/watcher-api-stop.html.
type XPackWatcherStopService struct {
client *Client
pretty *bool // pretty format the returned JSON response
human *bool // return human readable values for statistics
errorTrace *bool // include the stack trace of returned errors
filterPath []string // list of filters used to reduce the response
headers http.Header // custom request-level HTTP headers
}
// NewXPackWatcherStopService creates a new XPackWatcherStopService.
func NewXPackWatcherStopService(client *Client) *XPackWatcherStopService {
return &XPackWatcherStopService{
client: client,
}
}
// Pretty tells Elasticsearch whether to return a formatted JSON response.
func (s *XPackWatcherStopService) Pretty(pretty bool) *XPackWatcherStopService {
s.pretty = &pretty
return s
}
// Human specifies whether human readable values should be returned in
// the JSON response, e.g. "7.5mb".
func (s *XPackWatcherStopService) Human(human bool) *XPackWatcherStopService {
s.human = &human
return s
}
// ErrorTrace specifies whether to include the stack trace of returned errors.
func (s *XPackWatcherStopService) ErrorTrace(errorTrace bool) *XPackWatcherStopService {
s.errorTrace = &errorTrace
return s
}
// FilterPath specifies a list of filters used to reduce the response.
func (s *XPackWatcherStopService) FilterPath(filterPath ...string) *XPackWatcherStopService {
s.filterPath = filterPath
return s
}
// Header adds a header to the request.
func (s *XPackWatcherStopService) Header(name string, value string) *XPackWatcherStopService {
if s.headers == nil {
s.headers = http.Header{}
}
s.headers.Add(name, value)
return s
}
// Headers specifies the headers of the request.
func (s *XPackWatcherStopService) Headers(headers http.Header) *XPackWatcherStopService {
s.headers = headers
return s
}
// buildURL builds the URL for the operation.
func (s *XPackWatcherStopService) buildURL() (string, url.Values, error) {
// Build URL path
path := "/_watcher/_stop"
// Add query string parameters
params := url.Values{}
if v := s.pretty; v != nil {
params.Set("pretty", fmt.Sprint(*v))
}
if v := s.human; v != nil {
params.Set("human", fmt.Sprint(*v))
}
if v := s.errorTrace; v != nil {
params.Set("error_trace", fmt.Sprint(*v))
}
if len(s.filterPath) > 0 {
params.Set("filter_path", strings.Join(s.filterPath, ","))
}
return path, params, nil
}
// Validate checks if the operation is valid.
func (s *XPackWatcherStopService) Validate() error {
return nil
}
// Do executes the operation.
func (s *XPackWatcherStopService) Do(ctx context.Context) (*XPackWatcherStopResponse, error) {
// Check pre-conditions
if err := s.Validate(); err != nil {
return nil, err
}
// Get URL for request
path, params, err := s.buildURL()
if err != nil {
return nil, err
}
// Get HTTP response
res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
Method: "POST",
Path: path,
Params: params,
Headers: s.headers,
})
if err != nil {
return nil, err
}
// Return operation response
ret := new(XPackWatcherStopResponse)
if err := json.Unmarshal(res.Body, ret); err != nil {
return nil, err
}
return ret, nil
}
// XPackWatcherStopResponse is the response of XPackWatcherStopService.Do.
type XPackWatcherStopResponse struct {
Acknowledged bool `json:"acknowledged"`
}
================================================
FILE: xpack_watcher_stop_test.go
================================================
// Copyright 2012-2018 Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.
package elastic
import (
"testing"
)
func TestXPackWatcherStopBuildURL(t *testing.T) {
client := setupTestClient(t) // , SetURL("http://elastic:elastic@localhost:9210"))
tests := []struct {
Expected string
ExpectErr bool
}{
{
"/_watcher/_stop",
false,
},
}
for i, test := range tests {
builder := client.XPackWatchStop()
err := builder.Validate()
if err != nil {
if !test.ExpectErr {
t.Errorf("case #%d: %v", i+1, err)
continue
}
} else {
// err == nil
if test.ExpectErr {
t.Errorf("case #%d: expected error", i+1)
continue
}
path, _, _ := builder.buildURL()
if path != test.Expected {
t.Errorf("case #%d: expected %q; got: %q", i+1, test.Expected, path)
}
}
}
}