From 22a54caf454a645abbf66b411c6d1dbfae32dce7 Mon Sep 17 00:00:00 2001 From: Marty Schoch Date: Fri, 11 Mar 2016 12:18:24 -0500 Subject: [PATCH] fix handling of dynamic property in mappings of sub-documents fixes #353 --- mapping_document.go | 47 +++++++++++++++++++++++++++++++++++---------- mapping_test.go | 47 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 10 deletions(-) diff --git a/mapping_document.go b/mapping_document.go index a7f42e1b..2d565e3d 100644 --- a/mapping_document.go +++ b/mapping_document.go @@ -127,9 +127,41 @@ func (dm *DocumentMapping) fieldDescribedByPath(path string) *FieldMapping { return nil } +// documentMappingForPath only returns EXACT matches for a sub document +// or for an explicitly mapped field, if you want to find the +// closest document mapping to a field not explicitly mapped +// use closestDocMapping func (dm *DocumentMapping) documentMappingForPath(path string) *DocumentMapping { pathElements := decodePath(path) current := dm +OUTER: + for i, pathElement := range pathElements { + for name, subDocMapping := range current.Properties { + if name == pathElement { + current = subDocMapping + continue OUTER + } + } + // no subDocMapping matches this pathElement + // only if this is the last element check for field name + if i == len(pathElements)-1 { + for _, field := range current.Fields { + if field.Name == pathElement { + break + } + } + } + + return nil + } + return current +} + +// closestDocMapping findest the most specific document mapping that matches +// part of the provided path +func (dm *DocumentMapping) closestDocMapping(path string) *DocumentMapping { + pathElements := decodePath(path) + current := dm OUTER: for _, pathElement := range pathElements { for name, subDocMapping := range current.Properties { @@ -138,12 +170,6 @@ OUTER: continue OUTER } } - for _, field := range current.Fields { - if field.Name == pathElement { - continue OUTER - } - } - return nil } return current } @@ -334,6 +360,7 @@ func (dm *DocumentMapping) processProperty(property interface{}, path []string, pathString := encodePath(path) // look to see if there is a mapping for this field subDocMapping := dm.documentMappingForPath(pathString) + closestDocMapping := dm.closestDocMapping(pathString) // check to see if we even need to do further processing if subDocMapping != nil && !subDocMapping.Enabled { @@ -354,7 +381,7 @@ func (dm *DocumentMapping) processProperty(property interface{}, path []string, for _, fieldMapping := range subDocMapping.Fields { fieldMapping.processString(propertyValueString, pathString, path, indexes, context) } - } else if dm.Dynamic { + } else if closestDocMapping.Dynamic { // automatic indexing behavior // first see if it can be parsed by the default date parser @@ -385,7 +412,7 @@ func (dm *DocumentMapping) processProperty(property interface{}, path []string, for _, fieldMapping := range subDocMapping.Fields { fieldMapping.processFloat64(propertyValFloat, pathString, path, indexes, context) } - } else if dm.Dynamic { + } else if closestDocMapping.Dynamic { // automatic indexing behavior fieldMapping := newNumericFieldMappingDynamic(context.im) fieldMapping.processFloat64(propertyValFloat, pathString, path, indexes, context) @@ -397,7 +424,7 @@ func (dm *DocumentMapping) processProperty(property interface{}, path []string, for _, fieldMapping := range subDocMapping.Fields { fieldMapping.processBoolean(propertyValBool, pathString, path, indexes, context) } - } else if dm.Dynamic { + } else if closestDocMapping.Dynamic { // automatic indexing behavior fieldMapping := newBooleanFieldMappingDynamic(context.im) fieldMapping.processBoolean(propertyValBool, pathString, path, indexes, context) @@ -411,7 +438,7 @@ func (dm *DocumentMapping) processProperty(property interface{}, path []string, for _, fieldMapping := range subDocMapping.Fields { fieldMapping.processTime(property, pathString, path, indexes, context) } - } else if dm.Dynamic { + } else if closestDocMapping.Dynamic { fieldMapping := newDateTimeFieldMappingDynamic(context.im) fieldMapping.processTime(property, pathString, path, indexes, context) } diff --git a/mapping_test.go b/mapping_test.go index 77d9df5a..955ac22f 100644 --- a/mapping_test.go +++ b/mapping_test.go @@ -556,3 +556,50 @@ func TestInvalidIndexMappingStrict(t *testing.T) { t.Fatalf("expect to find index mapping default field 'all', got '%s'", im.DefaultField) } } + +func TestMappingBug353(t *testing.T) { + dataBytes := `{ + "Reviews": [ + { + "ReviewID": "RX16692001", + "Content": "Usually stay near the airport..." + } + ], + "Other": { + "Inside": "text" + }, + "Name": "The Inn at Baltimore White Marsh" +}` + + var data map[string]interface{} + err := json.Unmarshal([]byte(dataBytes), &data) + if err != nil { + t.Fatal(err) + } + + reviewContentFieldMapping := NewTextFieldMapping() + reviewContentFieldMapping.Analyzer = "crazy" + + reviewsMapping := NewDocumentMapping() + reviewsMapping.Dynamic = false + reviewsMapping.AddFieldMappingsAt("Content", reviewContentFieldMapping) + otherMapping := NewDocumentMapping() + otherMapping.Dynamic = false + mapping := NewIndexMapping() + mapping.DefaultMapping.AddSubDocumentMapping("Reviews", reviewsMapping) + mapping.DefaultMapping.AddSubDocumentMapping("Other", otherMapping) + + doc := document.NewDocument("x") + err = mapping.mapDocument(doc, data) + if err != nil { + t.Fatal(err) + } + + // expect doc has only 2 fields + if len(doc.Fields) != 2 { + t.Errorf("expected doc with 2 fields, got: %d", len(doc.Fields)) + for _, f := range doc.Fields { + t.Logf("field named: %s", f.Name()) + } + } +}