diff --git a/elasticsearch-core/src/main/scala/com/sumologic/elasticsearch/restlastic/dsl/MappingDsl.scala b/elasticsearch-core/src/main/scala/com/sumologic/elasticsearch/restlastic/dsl/MappingDsl.scala index baabbf4..d17bb37 100644 --- a/elasticsearch-core/src/main/scala/com/sumologic/elasticsearch/restlastic/dsl/MappingDsl.scala +++ b/elasticsearch-core/src/main/scala/com/sumologic/elasticsearch/restlastic/dsl/MappingDsl.scala @@ -142,9 +142,13 @@ trait MappingDsl extends DslCommons { val _ignoreAbove = "ignore_above" val _fieldIndexOpions = "index_options" - case class BasicFieldMapping(tpe: FieldType, index: Option[IndexType], analyzer: Option[Name], - ignoreAbove: Option[Int] = None, search_analyzer: Option[Name]= None, - indexOption: Option[IndexOption] = None) + case class BasicFieldMapping(tpe: FieldType, + index: Option[IndexType], + analyzer: Option[Name], + ignoreAbove: Option[Int] = None, + search_analyzer: Option[Name]= None, + indexOption: Option[IndexOption] = None, + fieldsOption: Option[FieldsMapping] = None) extends FieldMapping { override def toJson: Map[String, Any] = Map( @@ -152,14 +156,25 @@ trait MappingDsl extends DslCommons { index.map(_index -> _.rep) ++ analyzer.map(_analyzer -> _.name) ++ search_analyzer.map(_searchAnalyzer -> _.name) ++ - indexOption.map(_fieldIndexOpions -> _.option) - ignoreAbove.map(_ignoreAbove -> _).toList.toMap + indexOption.map(_fieldIndexOpions -> _.option) ++ + ignoreAbove.map(_ignoreAbove -> _) ++ + fieldsOption.map(_.toJson).getOrElse(Map[String, Any]()) + } + + case class FieldsMapping(fields: Map[String, FieldMapping]) extends FieldMapping { + val _fields = "fields" + override def toJson: Map[String, Any] = Map(_fields -> fields.mapValues(_.toJson)) } case class BasicObjectMapping(fields: Map[String, FieldMapping]) extends FieldMapping { override def toJson: Map[String, Any] = Map(_properties -> fields.mapValues(_.toJson)) } + case class NestedObjectMapping(fields: Map[String, FieldMapping]) extends FieldMapping { + val _nested = "nested" + override def toJson: Map[String, Any] = Map(_type -> _nested, _properties -> fields.mapValues(_.toJson)) + } + trait Completion { val _type = "type" -> "completion" val _context = "context" diff --git a/elasticsearch-core/src/test/scala/com/sumologic/elasticsearch/restlastic/RestlasticSearchClientTest.scala b/elasticsearch-core/src/test/scala/com/sumologic/elasticsearch/restlastic/RestlasticSearchClientTest.scala index efda108..c7481e1 100644 --- a/elasticsearch-core/src/test/scala/com/sumologic/elasticsearch/restlastic/RestlasticSearchClientTest.scala +++ b/elasticsearch-core/src/test/scala/com/sumologic/elasticsearch/restlastic/RestlasticSearchClientTest.scala @@ -121,7 +121,7 @@ class RestlasticSearchClientTest extends WordSpec with Matchers with ScalaFuture val foundDoc: ElasticJsonDocument = whenReady(resFut){ res => res.rawSearchResponse.hits.hits.head } - + val delFut = restClient.deleteById(index, tpe, foundDoc._id) whenReady(delFut) { res => @@ -195,7 +195,7 @@ class RestlasticSearchClientTest extends WordSpec with Matchers with ScalaFuture res.jsonStr should include("doc4") res.jsonStr should not include "doc5" } - + val delFut = restClient.bulkDelete(index, tpe, Seq(doc3, doc4, doc5)) whenReady(delFut){ resp => resp.length should be(3) @@ -203,7 +203,7 @@ class RestlasticSearchClientTest extends WordSpec with Matchers with ScalaFuture resp(1).success should be(true) resp(2).success should be(true) } - + refresh() val resFut2 = restClient.query(index, tpe, new QueryRoot(TermQuery("text", "here"))) whenReady(resFut2) { res => @@ -1109,6 +1109,38 @@ class RestlasticSearchClientTest extends WordSpec with Matchers with ScalaFuture Await.result(delFut, 10.seconds) // May not need Await? } + "Support nested mapping" in { + val basicFieldMapping = BasicFieldMapping(StringType, None, Some(analyzerName), ignoreAbove = Some(10000), Some(analyzerName)) + val metadataMapping = Mapping(tpe, + IndexMapping( + Map("name" -> basicFieldMapping, + "kv" -> NestedObjectMapping(Map("key" -> basicFieldMapping, "val" -> basicFieldMapping)) + ) + ) + ) + + val mappingFut = restClient.putMapping(index, tpe, metadataMapping) + whenReady(mappingFut) { _ => refresh() } + val mappingRes = restClient.getMapping(index, tpe) + val expected = """"kv":{"type":"nested","properties":{"key":{"type":"string","analyzer":"keyword_lowercase","ignore_above":10000},"val":{"type":"string","analyzer":"keyword_lowercase","ignore_above":10000}}}""" + mappingRes.futureValue.jsonStr.toString.contains(expected) should be(true) + } + + "Support multi-fields mapping" in { + val fields = FieldsMapping(Map("raw" -> BasicFieldMapping(StringType, None, Some(analyzerName), ignoreAbove = Some(10000), Some(analyzerName)))) + val basicFieldMapping = BasicFieldMapping(StringType, None, Some(analyzerName), ignoreAbove = Some(10000), Some(analyzerName), fieldsOption = Some(fields)) + val metadataMapping = Mapping(tpe, + IndexMapping( + Map("multi-fields" -> basicFieldMapping) + ) + ) + val mappingFut = restClient.putMapping(index, tpe, metadataMapping) + whenReady(mappingFut) { _ => refresh() } + val mappingRes = restClient.getMapping(index, tpe) + val expected = """{"multi-fields":{"type":"string","fields":{"raw":{"type":"string","analyzer":"keyword_lowercase","ignore_above":10000}},"analyzer":"keyword_lowercase","ignore_above":10000}}""" + mappingRes.futureValue.jsonStr.toString.contains(expected) should be(true) + } + def indexDocs(docs: Seq[Document]): Unit = { val bulkIndexFuture = restClient.bulkIndex(index, tpe, docs) whenReady(bulkIndexFuture) { _ => refresh() }