Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 44 additions & 40 deletions src/operations/mapping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@
//! [Indices API](https://www.elastic.co/guide/en/elasticsearch/reference/current/indices.html)
//! so subtle (potentially breaking) changes will be made to the API when that happens

use std::collections::HashMap;

use serde_json::{Value, Map};

use hyper::status::StatusCode;
Expand All @@ -31,9 +29,6 @@ use ::{Client, EsResponse};
use ::error::EsError;
use ::operations::GenericResult;

pub type DocType<'a> = HashMap<&'a str, HashMap<&'a str, &'a str>>;
pub type Mapping<'a> = HashMap<&'a str, DocType<'a>>;

#[derive(Serialize)]
pub struct Settings {
pub number_of_shards: u32,
Expand All @@ -55,7 +50,7 @@ pub struct MappingOperation<'a, 'b> {
index: &'b str,

/// A map containing the doc types and their mapping
mapping: Option<&'b Mapping<'b>>,
mappings: Option<&'b Value>,

/// A struct reflecting the settings that enable the
/// customization of analyzers
Expand All @@ -68,14 +63,14 @@ impl<'a, 'b> MappingOperation<'a, 'b> {
MappingOperation {
client: client,
index: index,
mapping: None,
mappings: None,
settings: None
}
}

/// Set the actual mapping
pub fn with_mapping(&'b mut self, mapping: &'b Mapping) -> &'b mut Self {
self.mapping = Some(mapping);
pub fn with_mappings(&'b mut self, mappings: &'b Value) -> &'b mut Self {
self.mappings = Some(mappings);
self
}

Expand All @@ -87,31 +82,38 @@ impl<'a, 'b> MappingOperation<'a, 'b> {

/// If settings have been provided, the index will be created with them. If the index already
/// exists, an `Err(EsError)` will be returned.
/// If mapping have been set too, the properties will be applied. The index will be unavailable
/// If mappings have been set too, the properties will be applied. The index will be unavailable
/// during this process.
/// Nothing will be done if either mapping and settings are not present.
/// Nothing will be done if either mappings and settings are not present.
pub fn send(&'b mut self) -> Result<MappingResult, EsError> {
// Return earlier if there is nothing to do
if self.mapping.is_none() && self.settings.is_none() {
if self.mappings.is_none() && self.settings.is_none() {
return Ok(MappingResult);
}

if self.settings.is_some() {
let url = format!("{}", self.index);

if self.mappings.is_none() {
let body = hashmap! { "settings" => self.settings.unwrap() };
let url = format!("{}", self.index);
let _ = self.client.put_body_op(&url, &body)?;

let _ = self.client.wait_for_status("yellow", "5s");
}

if self.mapping.is_some() {
if let Some(mappings) = self.mappings {
let _ = self.client.close_index(self.index);

for (entity, properties) in self.mapping.unwrap().iter() {
let body = hashmap! { "properties" => properties };
let url = format!("{}/_mapping/{}", self.index, entity);
let _ = self.client.put_body_op(&url, &body)?;
}
let body = match self.settings {
Some(settings) => json!({
"mappings": mappings,
"settings": settings
}),
None => json!({
"mappings": mappings,
})
};

let _ = self.client.put_body_op(&url, &body)?;

let _ = self.client.open_index(self.index);
}
Expand Down Expand Up @@ -175,33 +177,35 @@ pub mod tests {
}

#[test]
fn test_mapping() {
let index_name = "tests_test_mapping";
fn test_mappings() {
let index_name = "tests_test_mappings";
let mut client = ::tests::make_client();

// TODO - this fails in many cases (specifically on TravisCI), but we ignore the
// failures anyway
let _ = client.delete_index(index_name);

let mapping = hashmap! {
"post" => hashmap! {
"created_at" => hashmap! {
"type" => "date",
"format" => "date_time"
},

"title" => hashmap! {
"type" => "string",
"index" => "not_analyzed"
let mappings = json! ({
"post": {
"properties": {
"created_at": {
"type": "date",
"format": "date_time"
},
"title": {
"type": "string",
"index": "not_analyzed"
}
}
},

"author" => hashmap! {
"name" => hashmap! {
"type" => "string",
"author": {
"properties": {
"name": {
"type": "string"
}
}
},
};
}
});

let settings = Settings {
number_of_shards: 1,
Expand All @@ -226,10 +230,10 @@ pub mod tests {

// TODO add appropriate functions to the `Client` struct
let result = MappingOperation::new(&mut client, index_name)
.with_mapping(&mapping)
.with_mappings(&mappings)
.with_settings(&settings)
.send();
assert!(result.is_ok());
let _ = result.unwrap();

{
let result_wrapped = client
Expand Down
19 changes: 17 additions & 2 deletions src/operations/search/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use hyper::status::StatusCode;

use serde::de::DeserializeOwned;
use serde::ser::{Serialize, Serializer};
use serde_json::Value;
use serde_json::{from_value, Value};

use ::{Client, EsResponse};
use ::error::EsError;
Expand Down Expand Up @@ -776,9 +776,24 @@ pub struct SearchHitsHitsResult<T> {
#[serde(rename="_routing")]
pub routing: Option<String>,
pub fields: Option<Value>,
pub highlight: Option<HighlightResult>
pub highlight: Option<HighlightResult>,
pub inner_hits: Option<HashMap<String, Value>>
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the Value needed due to the potential of more-than-one different type coming back in the results? In which case its probably unavoidable.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

impl<T> SearchHitsHitsResult<T>
where T: DeserializeOwned {

pub fn inner_hit<S>(&self, key: &str) -> Result<Option<SearchHitsResult<S>>, EsError> where S: DeserializeOwned {
if let Some(hits) = self.inner_hits.to_owned() {
if let Some(hits) = hits.get(key).and_then(|hit| hit.get("hits")) {
return from_value(hits.to_owned()).map_err(EsError::from);
}
}
Ok(None)
}
}


#[derive(Debug, Deserialize)]
pub struct SearchHitsResult<T> {
pub total: u64,
Expand Down
7 changes: 6 additions & 1 deletion src/query/joining.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
//! Joining queries

use ::json::ShouldSkip;
use ::serde_json::Value;

use super::{ScoreMode, Query};

Expand Down Expand Up @@ -50,14 +51,17 @@ impl NestedQuery {
/// Has Child query
#[derive(Debug, Default, Serialize)]
pub struct HasChildQuery {
#[serde(rename="type")]
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this :) it must have been missing for quite a while

doc_type: String,
query: Query,
#[serde(skip_serializing_if="ShouldSkip::should_skip")]
score_mode: Option<ScoreMode>,
#[serde(skip_serializing_if="ShouldSkip::should_skip")]
min_children: Option<u64>,
#[serde(skip_serializing_if="ShouldSkip::should_skip")]
max_children: Option<u64>
max_children: Option<u64>,
#[serde(skip_serializing_if="ShouldSkip::should_skip")]
inner_hits: Option<Value>
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Preferably this would be a concrete type rather than Value, but the ElasticSearch documentation is... less than comprehensive on this matter. This seems to be the best reference: https://www.elastic.co/guide/en/elasticsearch/reference/1.5/search-request-inner-hits.html unless there's a better one somewhere?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The link I sent you in the other comment is similar to this one - Although I agree that a concrete type would be way preferable, I find not using a Value quite a hard way here 😞

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because of Inner Hits potentially returning more than one query? Yes, that's true.

We could have a hybrid option as there is some predictable structure. E.g. the "total" and "_id" fields, it's only really the "_source" field that needs to be a Value?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we also add the same to the HasParentQuery as the inner_hits field should in-theory work there too?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it actually? https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-has-parent-query.html I can't find it here but in case sure, I can add inner_hits in there too. I will wait for a your response before to commit it just to double check I understood correctly what you mean :)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think I've ever needed it on a has_parent query, and it's not mentioned in the documentation for it either. But it is mentioned here: "Inner hits can be used by defining a inner_hits definition on a nested, has_child or has_parent query and filter" it might be something that's missing from the documentation elsewhere.

}

/// Has Parent query
Expand Down Expand Up @@ -95,6 +99,7 @@ impl HasChildQuery {
add_field!(with_score_mode, score_mode, ScoreMode);
add_field!(with_min_children, min_children, u64);
add_field!(with_max_children, max_children, u64);
add_field!(with_inner_hits, inner_hits, Value);

build!(HasChild);
}
Expand Down