// Copyright (c) 2025 Couchbase, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package bleve import ( "context" "encoding/json" "fmt" "math/rand" "reflect" "testing" "github.com/blevesearch/bleve/v2/analysis/analyzer/custom" "github.com/blevesearch/bleve/v2/analysis/analyzer/simple" "github.com/blevesearch/bleve/v2/analysis/analyzer/standard" "github.com/blevesearch/bleve/v2/analysis/datetime/percent" "github.com/blevesearch/bleve/v2/analysis/datetime/sanitized" "github.com/blevesearch/bleve/v2/analysis/lang/en" "github.com/blevesearch/bleve/v2/analysis/token/lowercase" "github.com/blevesearch/bleve/v2/analysis/tokenizer/letter" "github.com/blevesearch/bleve/v2/analysis/tokenizer/whitespace" "github.com/blevesearch/bleve/v2/index/scorch" "github.com/blevesearch/bleve/v2/index/scorch/mergeplan" "github.com/blevesearch/bleve/v2/mapping" index "github.com/blevesearch/bleve_index_api" ) func TestCompareFieldMapping(t *testing.T) { tests := []struct { original *mapping.FieldMapping updated *mapping.FieldMapping indexFieldInfo *index.UpdateFieldInfo err bool }{ { // both nil => error original: nil, updated: nil, indexFieldInfo: nil, err: true, }, { // updated nil => delete all original: &mapping.FieldMapping{}, updated: nil, indexFieldInfo: &index.UpdateFieldInfo{ Deleted: true, }, err: false, }, { // type changed => not updatable original: &mapping.FieldMapping{ Type: "text", }, updated: &mapping.FieldMapping{ Type: "datetime", }, indexFieldInfo: nil, err: true, }, { // synonym source changed for text => updatable original: &mapping.FieldMapping{ Type: "text", SynonymSource: "a", }, updated: &mapping.FieldMapping{ Type: "text", SynonymSource: "b", }, indexFieldInfo: &index.UpdateFieldInfo{}, err: false, }, { // analyser changed for text => not updatable original: &mapping.FieldMapping{ Type: "text", Analyzer: "a", }, updated: &mapping.FieldMapping{ Type: "text", Analyzer: "b", }, indexFieldInfo: nil, err: true, }, { // dims changed for vector => not updatable original: &mapping.FieldMapping{ Type: "vector", Dims: 128, Similarity: "l2_norm", VectorIndexOptimizedFor: "memory-efficient", }, updated: &mapping.FieldMapping{ Type: "vector", Dims: 1024, Similarity: "l2_norm", VectorIndexOptimizedFor: "memory-efficient", }, indexFieldInfo: nil, err: true, }, { // similarity changed for vectorbase64 => not updatable original: &mapping.FieldMapping{ Type: "vector_base64", Similarity: "l2_norm", Dims: 128, VectorIndexOptimizedFor: "memory-efficient", }, updated: &mapping.FieldMapping{ Type: "vector_base64", Similarity: "dot_product", Dims: 128, VectorIndexOptimizedFor: "memory-efficient", }, indexFieldInfo: nil, err: true, }, { // vectorindexoptimizedfor chagned for vector => not updatable original: &mapping.FieldMapping{ Type: "vector", Similarity: "dot_product", Dims: 128, VectorIndexOptimizedFor: "memory-efficient", }, updated: &mapping.FieldMapping{ Type: "vector", Similarity: "dot_product", Dims: 128, VectorIndexOptimizedFor: "latency", }, indexFieldInfo: nil, err: true, }, { // gpu changed for vector => not updatable original: &mapping.FieldMapping{ Type: "vector", Similarity: "dot_product", Dims: 128, VectorIndexOptimizedFor: "memory-efficient", GPU: false, }, updated: &mapping.FieldMapping{ Type: "vector", Similarity: "dot_product", Dims: 128, VectorIndexOptimizedFor: "memory-efficient", GPU: true, }, indexFieldInfo: nil, err: true, }, { // gpu changed for vectorbase64 => not updatable original: &mapping.FieldMapping{ Type: "vector_base64", Similarity: "dot_product", Dims: 128, VectorIndexOptimizedFor: "memory-efficient", GPU: false, }, updated: &mapping.FieldMapping{ Type: "vector_base64", Similarity: "dot_product", Dims: 128, VectorIndexOptimizedFor: "memory-efficient", GPU: true, }, indexFieldInfo: nil, err: true, }, { // includeinall changed => not updatable original: &mapping.FieldMapping{ Type: "numeric", IncludeInAll: true, }, updated: &mapping.FieldMapping{ Type: "numeric", IncludeInAll: false, }, indexFieldInfo: nil, err: true, }, { //includetermvectors changed => not updatable original: &mapping.FieldMapping{ Type: "numeric", IncludeTermVectors: false, }, updated: &mapping.FieldMapping{ Type: "numeric", IncludeTermVectors: true, }, indexFieldInfo: nil, err: true, }, { // store changed after all checks => updatable with store delete original: &mapping.FieldMapping{ Type: "numeric", SkipFreqNorm: true, }, updated: &mapping.FieldMapping{ Type: "numeric", SkipFreqNorm: false, }, indexFieldInfo: nil, err: true, }, { // index changed after all checks => updatable with index and docvalues delete original: &mapping.FieldMapping{ Type: "geopoint", Index: true, }, updated: &mapping.FieldMapping{ Type: "geopoint", Index: false, }, indexFieldInfo: &index.UpdateFieldInfo{ Index: true, DocValues: true, }, err: false, }, { // docvalues changed after all checks => docvalues delete original: &mapping.FieldMapping{ Type: "numeric", DocValues: true, }, updated: &mapping.FieldMapping{ Type: "numeric", DocValues: false, }, indexFieldInfo: &index.UpdateFieldInfo{ DocValues: true, }, err: false, }, { // no relavent changes => continue but no op original: &mapping.FieldMapping{ Name: "", Type: "datetime", Analyzer: "a", Store: true, Index: false, IncludeTermVectors: true, IncludeInAll: false, DateFormat: "a", DocValues: false, SkipFreqNorm: true, Dims: 128, Similarity: "dot_product", VectorIndexOptimizedFor: "memory-efficient", SynonymSource: "a", }, updated: &mapping.FieldMapping{ Name: "", Type: "datetime", Analyzer: "b", Store: true, Index: false, IncludeTermVectors: true, IncludeInAll: false, DateFormat: "a", DocValues: false, SkipFreqNorm: true, Dims: 256, Similarity: "l2_norm", VectorIndexOptimizedFor: "latency", SynonymSource: "b", }, indexFieldInfo: &index.UpdateFieldInfo{}, err: false, }, } for i, test := range tests { rv, err := compareFieldMapping(test.original, test.updated) if err == nil && test.err || err != nil && !test.err { t.Errorf("Unexpected error value for test %d, expecting %t, got %v\n", i, test.err, err) } if rv == nil && test.indexFieldInfo != nil || rv != nil && test.indexFieldInfo == nil || !reflect.DeepEqual(rv, test.indexFieldInfo) { t.Errorf("Unexpected index field info value for test %d, expecting %+v, got %+v, err %v", i, test.indexFieldInfo, rv, err) } } } func TestCompareMappings(t *testing.T) { tests := []struct { original *mapping.IndexMappingImpl updated *mapping.IndexMappingImpl err bool }{ { // changed type field when non empty mappings are present => error original: &mapping.IndexMappingImpl{ TypeField: "a", TypeMapping: map[string]*mapping.DocumentMapping{ "a": {}, "b": {}, }, }, updated: &mapping.IndexMappingImpl{ TypeField: "b", TypeMapping: map[string]*mapping.DocumentMapping{ "a": {}, "b": {}, }, }, err: true, }, { // changed default type => error original: &mapping.IndexMappingImpl{ DefaultType: "a", }, updated: &mapping.IndexMappingImpl{ DefaultType: "b", }, err: true, }, { // changed default analyzer => analyser true original: &mapping.IndexMappingImpl{ DefaultAnalyzer: "a", }, updated: &mapping.IndexMappingImpl{ DefaultAnalyzer: "b", }, err: false, }, { // changed default datetimeparser => datetimeparser true original: &mapping.IndexMappingImpl{ DefaultDateTimeParser: "a", }, updated: &mapping.IndexMappingImpl{ DefaultDateTimeParser: "b", }, err: false, }, { // changed default synonym source => synonym source true original: &mapping.IndexMappingImpl{ DefaultSynonymSource: "a", }, updated: &mapping.IndexMappingImpl{ DefaultSynonymSource: "b", }, err: false, }, { // changed default field => false original: &mapping.IndexMappingImpl{ DefaultField: "a", }, updated: &mapping.IndexMappingImpl{ DefaultField: "b", }, err: false, }, { // changed index dynamic => error original: &mapping.IndexMappingImpl{ IndexDynamic: true, }, updated: &mapping.IndexMappingImpl{ IndexDynamic: false, }, err: true, }, { // changed store dynamic => error original: &mapping.IndexMappingImpl{ StoreDynamic: false, }, updated: &mapping.IndexMappingImpl{ StoreDynamic: true, }, err: true, }, { // changed docvalues dynamic => error original: &mapping.IndexMappingImpl{ DocValuesDynamic: true, }, updated: &mapping.IndexMappingImpl{ DocValuesDynamic: false, }, err: true, }, } for i, test := range tests { err := compareMappings(test.original, test.updated) if err == nil && test.err || err != nil && !test.err { t.Errorf("Unexpected error value for test %d, expecting %t, got %v\n", i, test.err, err) } } } func TestCompareAnalysers(t *testing.T) { ori := mapping.NewIndexMapping() ori.DefaultMapping.AddFieldMappingsAt("a", NewTextFieldMapping()) ori.DefaultMapping.AddFieldMappingsAt("b", NewTextFieldMapping()) ori.DefaultMapping.AddFieldMappingsAt("c", NewTextFieldMapping()) ori.DefaultMapping.Properties["b"].DefaultAnalyzer = "3xbla" ori.DefaultMapping.Properties["c"].DefaultAnalyzer = simple.Name upd := mapping.NewIndexMapping() upd.DefaultMapping.AddFieldMappingsAt("a", NewTextFieldMapping()) upd.DefaultMapping.AddFieldMappingsAt("b", NewTextFieldMapping()) upd.DefaultMapping.AddFieldMappingsAt("c", NewTextFieldMapping()) upd.DefaultMapping.Properties["b"].DefaultAnalyzer = "3xbla" upd.DefaultMapping.Properties["c"].DefaultAnalyzer = simple.Name if err := ori.AddCustomAnalyzer("3xbla", map[string]interface{}{ "type": custom.Name, "tokenizer": whitespace.Name, "token_filters": []interface{}{lowercase.Name, "stop_en"}, }); err != nil { t.Fatal(err) } if err := upd.AddCustomAnalyzer("3xbla", map[string]interface{}{ "type": custom.Name, "tokenizer": whitespace.Name, "token_filters": []interface{}{lowercase.Name, "stop_en"}, }); err != nil { t.Fatal(err) } oriPaths := map[string]*pathInfo{ "a": { fieldMapInfo: []*fieldMapInfo{ { fieldMapping: &mapping.FieldMapping{ Type: "text", }, }, }, dynamic: false, path: "a", parentPath: "", }, "b": { fieldMapInfo: []*fieldMapInfo{ { fieldMapping: &mapping.FieldMapping{ Type: "text", }, }, }, dynamic: false, path: "b", parentPath: "", }, "c": { fieldMapInfo: []*fieldMapInfo{ { fieldMapping: &mapping.FieldMapping{ Type: "text", }, }, }, dynamic: false, path: "c", parentPath: "", }, } updPaths := map[string]*pathInfo{ "a": { fieldMapInfo: []*fieldMapInfo{ { fieldMapping: &mapping.FieldMapping{ Type: "text", }, }, }, dynamic: false, path: "a", parentPath: "", }, "b": { fieldMapInfo: []*fieldMapInfo{ { fieldMapping: &mapping.FieldMapping{ Type: "text", }, }, }, dynamic: false, path: "b", parentPath: "", }, "c": { fieldMapInfo: []*fieldMapInfo{ { fieldMapping: &mapping.FieldMapping{ Type: "text", }, }, }, dynamic: false, path: "c", parentPath: "", }, } // Test case has identical analysers for text fields err := compareAnalysers(oriPaths, updPaths, ori, upd) if err != nil { t.Errorf("Expected error to be nil, got %v", err) } ori2 := mapping.NewIndexMapping() ori2.DefaultMapping.AddFieldMappingsAt("a", NewTextFieldMapping()) ori2.DefaultMapping.AddFieldMappingsAt("b", NewTextFieldMapping()) ori2.DefaultMapping.AddFieldMappingsAt("c", NewTextFieldMapping()) ori2.DefaultMapping.Properties["b"].DefaultAnalyzer = "3xbla" ori2.DefaultMapping.Properties["c"].DefaultAnalyzer = simple.Name upd2 := mapping.NewIndexMapping() upd2.DefaultMapping.AddFieldMappingsAt("a", NewTextFieldMapping()) upd2.DefaultMapping.AddFieldMappingsAt("b", NewTextFieldMapping()) upd2.DefaultMapping.AddFieldMappingsAt("c", NewTextFieldMapping()) upd2.DefaultMapping.Properties["b"].DefaultAnalyzer = "3xbla" upd2.DefaultMapping.Properties["c"].DefaultAnalyzer = simple.Name if err := ori2.AddCustomAnalyzer("3xbla", map[string]interface{}{ "type": custom.Name, "tokenizer": whitespace.Name, "token_filters": []interface{}{lowercase.Name, "stop_en"}, }); err != nil { t.Fatal(err) } if err := upd2.AddCustomAnalyzer("3xbla", map[string]interface{}{ "type": custom.Name, "tokenizer": letter.Name, "token_filters": []interface{}{lowercase.Name, "stop_en"}, }); err != nil { t.Fatal(err) } // Test case has different custom analyser for field "b" err = compareAnalysers(oriPaths, updPaths, ori2, upd2) if err == nil { t.Errorf("Expected error, got nil") } } func TestCompareDatetimeParsers(t *testing.T) { ori := mapping.NewIndexMapping() ori.DefaultMapping.AddFieldMappingsAt("a", NewDateTimeFieldMapping()) ori.DefaultMapping.AddFieldMappingsAt("b", NewDateTimeFieldMapping()) ori.DefaultMapping.AddFieldMappingsAt("c", NewDateTimeFieldMapping()) ori.DefaultMapping.Properties["b"].Fields[0].DateFormat = "customDT" ori.DefaultMapping.Properties["c"].Fields[0].DateFormat = percent.Name upd := mapping.NewIndexMapping() upd.DefaultMapping.AddFieldMappingsAt("a", NewDateTimeFieldMapping()) upd.DefaultMapping.AddFieldMappingsAt("b", NewDateTimeFieldMapping()) upd.DefaultMapping.AddFieldMappingsAt("c", NewDateTimeFieldMapping()) upd.DefaultMapping.Properties["b"].Fields[0].DateFormat = "customDT" upd.DefaultMapping.Properties["c"].Fields[0].DateFormat = percent.Name err := ori.AddCustomDateTimeParser("customDT", map[string]interface{}{ "type": sanitized.Name, "layouts": []interface{}{ "02/01/2006 15:04:05", "2006/01/02 3:04PM", }, }) if err != nil { t.Fatal(err) } err = upd.AddCustomDateTimeParser("customDT", map[string]interface{}{ "type": sanitized.Name, "layouts": []interface{}{ "02/01/2006 15:04:05", "2006/01/02 3:04PM", }, }) if err != nil { t.Fatal(err) } oriPaths := map[string]*pathInfo{ "a": { fieldMapInfo: []*fieldMapInfo{ { fieldMapping: &mapping.FieldMapping{ Type: "datetime", }, }, }, dynamic: false, path: "a", parentPath: "", }, "b": { fieldMapInfo: []*fieldMapInfo{ { fieldMapping: &mapping.FieldMapping{ Type: "datetime", DateFormat: "customDT", }, }, }, dynamic: false, path: "b", parentPath: "", }, "c": { fieldMapInfo: []*fieldMapInfo{ { fieldMapping: &mapping.FieldMapping{ Type: "datetime", }, }, }, dynamic: false, path: "c", parentPath: "", }, } updPaths := map[string]*pathInfo{ "a": { fieldMapInfo: []*fieldMapInfo{ { fieldMapping: &mapping.FieldMapping{ Type: "datetime", }, }, }, dynamic: false, path: "a", parentPath: "", }, "b": { fieldMapInfo: []*fieldMapInfo{ { fieldMapping: &mapping.FieldMapping{ Type: "datetime", DateFormat: "customDT", }, }, }, dynamic: false, path: "b", parentPath: "", }, "c": { fieldMapInfo: []*fieldMapInfo{ { fieldMapping: &mapping.FieldMapping{ Type: "datetime", }, }, }, dynamic: false, path: "c", parentPath: "", }, } // Test case has identical datetime parsers for all fields err = compareDateTimeParsers(oriPaths, updPaths, ori, upd) if err != nil { t.Fatalf("Expected error to be nil, got %v", err) } ori2 := mapping.NewIndexMapping() ori2.DefaultMapping.AddFieldMappingsAt("a", NewDateTimeFieldMapping()) ori2.DefaultMapping.AddFieldMappingsAt("b", NewDateTimeFieldMapping()) ori2.DefaultMapping.AddFieldMappingsAt("c", NewDateTimeFieldMapping()) ori2.DefaultMapping.Properties["b"].Fields[0].DateFormat = "customDT" ori2.DefaultMapping.Properties["c"].Fields[0].DateFormat = percent.Name upd2 := mapping.NewIndexMapping() upd2.DefaultMapping.AddFieldMappingsAt("a", NewDateTimeFieldMapping()) upd2.DefaultMapping.AddFieldMappingsAt("b", NewDateTimeFieldMapping()) upd2.DefaultMapping.AddFieldMappingsAt("c", NewDateTimeFieldMapping()) upd2.DefaultMapping.Properties["b"].Fields[0].DateFormat = "customDT" upd2.DefaultMapping.Properties["c"].Fields[0].DateFormat = percent.Name err = ori2.AddCustomDateTimeParser("customDT", map[string]interface{}{ "type": sanitized.Name, "layouts": []interface{}{ "02/01/2006 15:04:05", "2006/01/02 3:04PM", }, }) if err != nil { t.Fatal(err) } err = upd2.AddCustomDateTimeParser("customDT", map[string]interface{}{ "type": sanitized.Name, "layouts": []interface{}{ "02/01/2006 15:04:05", "2006/01/02", }, }) if err != nil { t.Fatal(err) } // test case has different custom datetime parser for field "b" err = compareDateTimeParsers(oriPaths, updPaths, ori2, upd2) if err == nil { t.Errorf("Expected error, got nil") } } func TestCompareSynonymSources(t *testing.T) { ori := mapping.NewIndexMapping() ori.DefaultMapping.AddFieldMappingsAt("a", NewTextFieldMapping()) ori.DefaultMapping.AddFieldMappingsAt("b", NewTextFieldMapping()) ori.DefaultMapping.DefaultSynonymSource = "syn1" ori.DefaultMapping.Properties["b"].Fields[0].SynonymSource = "syn2" upd := mapping.NewIndexMapping() upd.DefaultMapping.AddFieldMappingsAt("a", NewTextFieldMapping()) upd.DefaultMapping.AddFieldMappingsAt("b", NewTextFieldMapping()) upd.DefaultMapping.DefaultSynonymSource = "syn1" upd.DefaultMapping.Properties["b"].Fields[0].SynonymSource = "syn2" err := ori.AddSynonymSource("syn1", map[string]interface{}{ "collection": "col1", "analyzer": simple.Name, }) if err != nil { t.Fatal(err) } err = ori.AddSynonymSource("syn2", map[string]interface{}{ "collection": "col2", "analyzer": standard.Name, }) if err != nil { t.Fatal(err) } err = upd.AddSynonymSource("syn1", map[string]interface{}{ "collection": "col1", "analyzer": simple.Name, }) if err != nil { t.Fatal(err) } err = upd.AddSynonymSource("syn2", map[string]interface{}{ "collection": "col2", "analyzer": standard.Name, }) if err != nil { t.Fatal(err) } // Test case has identical synonym sources err = compareSynonymSources(ori, upd) if err != nil { t.Errorf("Expected error to be nil, got %v", err) } ori2 := mapping.NewIndexMapping() ori2.DefaultMapping.AddFieldMappingsAt("a", NewTextFieldMapping()) ori2.DefaultMapping.AddFieldMappingsAt("b", NewTextFieldMapping()) ori2.DefaultMapping.DefaultSynonymSource = "syn1" ori2.DefaultMapping.Properties["b"].Fields[0].SynonymSource = "syn2" upd2 := mapping.NewIndexMapping() upd2.DefaultMapping.AddFieldMappingsAt("a", NewTextFieldMapping()) upd2.DefaultMapping.AddFieldMappingsAt("b", NewTextFieldMapping()) upd2.DefaultMapping.DefaultSynonymSource = "syn1" upd2.DefaultMapping.Properties["b"].Fields[0].SynonymSource = "syn2" err = ori2.AddSynonymSource("syn1", map[string]interface{}{ "collection": "col1", "analyzer": simple.Name, }) if err != nil { t.Fatal(err) } err = ori2.AddSynonymSource("syn2", map[string]interface{}{ "collection": "col2", "analyzer": standard.Name, }) if err != nil { t.Fatal(err) } err = upd2.AddSynonymSource("syn1", map[string]interface{}{ "collection": "col1", "analyzer": simple.Name, }) if err != nil { t.Fatal(err) } err = upd2.AddSynonymSource("syn2", map[string]interface{}{ "collection": "col3", "analyzer": standard.Name, }) if err != nil { t.Fatal(err) } // test case has different synonym sources err = compareSynonymSources(ori2, upd2) if err == nil { t.Errorf("Expected error, got nil") } } func TestDeletedFields(t *testing.T) { tests := []struct { original *mapping.IndexMappingImpl updated *mapping.IndexMappingImpl fieldInfo map[string]*index.UpdateFieldInfo err bool }{ { // changed default analyzer with index dynamic // => error original: &mapping.IndexMappingImpl{ TypeMapping: map[string]*mapping.DocumentMapping{}, DefaultMapping: &mapping.DocumentMapping{}, DefaultAnalyzer: standard.Name, IndexDynamic: true, StoreDynamic: false, DocValuesDynamic: false, CustomAnalysis: NewIndexMapping().CustomAnalysis, }, updated: &mapping.IndexMappingImpl{ TypeMapping: map[string]*mapping.DocumentMapping{}, DefaultMapping: &mapping.DocumentMapping{}, DefaultAnalyzer: simple.Name, IndexDynamic: true, StoreDynamic: false, DocValuesDynamic: false, CustomAnalysis: NewIndexMapping().CustomAnalysis, }, fieldInfo: nil, err: true, }, { // changed default analyzer within a mapping with index dynamic // => error original: &mapping.IndexMappingImpl{ TypeMapping: map[string]*mapping.DocumentMapping{}, DefaultMapping: &mapping.DocumentMapping{ Enabled: true, Dynamic: true, DefaultAnalyzer: standard.Name, }, DefaultAnalyzer: "", IndexDynamic: true, StoreDynamic: false, DocValuesDynamic: false, CustomAnalysis: NewIndexMapping().CustomAnalysis, }, updated: &mapping.IndexMappingImpl{ TypeMapping: map[string]*mapping.DocumentMapping{}, DefaultMapping: &mapping.DocumentMapping{ Enabled: true, Dynamic: true, DefaultAnalyzer: simple.Name, }, IndexDynamic: true, StoreDynamic: false, DocValuesDynamic: false, CustomAnalysis: NewIndexMapping().CustomAnalysis, }, fieldInfo: nil, err: true, }, { // changed default datetime parser with index dynamic // => error original: &mapping.IndexMappingImpl{ TypeMapping: map[string]*mapping.DocumentMapping{}, DefaultMapping: &mapping.DocumentMapping{}, DefaultDateTimeParser: percent.Name, IndexDynamic: true, StoreDynamic: false, DocValuesDynamic: false, CustomAnalysis: NewIndexMapping().CustomAnalysis, }, updated: &mapping.IndexMappingImpl{ TypeMapping: map[string]*mapping.DocumentMapping{}, DefaultMapping: &mapping.DocumentMapping{}, DefaultDateTimeParser: sanitized.Name, IndexDynamic: true, StoreDynamic: false, DocValuesDynamic: false, CustomAnalysis: NewIndexMapping().CustomAnalysis, }, fieldInfo: nil, err: true, }, { // no change between original and updated having type and default mapping // => empty fieldInfo with no error original: &mapping.IndexMappingImpl{ TypeMapping: map[string]*mapping.DocumentMapping{ "map1": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "a": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, "map2": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "b": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, DefaultMapping: &mapping.DocumentMapping{ Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "c": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, IndexDynamic: false, StoreDynamic: false, DocValuesDynamic: false, CustomAnalysis: NewIndexMapping().CustomAnalysis, }, updated: &mapping.IndexMappingImpl{ TypeMapping: map[string]*mapping.DocumentMapping{ "map1": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "a": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, "map2": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "b": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, DefaultMapping: &mapping.DocumentMapping{ Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "c": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, IndexDynamic: false, StoreDynamic: false, DocValuesDynamic: false, CustomAnalysis: NewIndexMapping().CustomAnalysis, }, fieldInfo: map[string]*index.UpdateFieldInfo{}, err: false, }, { // no changes in type mappings and default mapping disabled with changes // => empty fieldInfo with no error original: &mapping.IndexMappingImpl{ TypeMapping: map[string]*mapping.DocumentMapping{ "map1": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "a": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, "map2": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "b": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, DefaultMapping: &mapping.DocumentMapping{ Enabled: false, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "c": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, IndexDynamic: false, StoreDynamic: false, DocValuesDynamic: false, CustomAnalysis: NewIndexMapping().CustomAnalysis, }, updated: &mapping.IndexMappingImpl{ TypeMapping: map[string]*mapping.DocumentMapping{ "map1": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "a": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, "map2": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "b": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, DefaultMapping: &mapping.DocumentMapping{ Enabled: false, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "d": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, IndexDynamic: false, StoreDynamic: false, DocValuesDynamic: false, CustomAnalysis: NewIndexMapping().CustomAnalysis, }, fieldInfo: map[string]*index.UpdateFieldInfo{}, err: false, }, { // new type mappings in updated => error original: &mapping.IndexMappingImpl{ TypeMapping: map[string]*mapping.DocumentMapping{ "map1": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "a": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, "map2": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "b": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, DefaultMapping: &mapping.DocumentMapping{ Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, IndexDynamic: false, StoreDynamic: false, DocValuesDynamic: false, CustomAnalysis: NewIndexMapping().CustomAnalysis, }, updated: &mapping.IndexMappingImpl{ TypeMapping: map[string]*mapping.DocumentMapping{ "map1": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "a": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, "map2": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "c": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, DefaultMapping: &mapping.DocumentMapping{ Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, IndexDynamic: false, StoreDynamic: false, DocValuesDynamic: false, CustomAnalysis: NewIndexMapping().CustomAnalysis, }, fieldInfo: nil, err: true, }, { // new mappings in default mapping => error original: &mapping.IndexMappingImpl{ TypeMapping: map[string]*mapping.DocumentMapping{}, DefaultMapping: &mapping.DocumentMapping{ Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "a": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, IndexDynamic: false, StoreDynamic: false, DocValuesDynamic: false, CustomAnalysis: NewIndexMapping().CustomAnalysis, }, updated: &mapping.IndexMappingImpl{ TypeMapping: map[string]*mapping.DocumentMapping{}, DefaultMapping: &mapping.DocumentMapping{ Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "b": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, IndexDynamic: false, StoreDynamic: false, DocValuesDynamic: false, CustomAnalysis: NewIndexMapping().CustomAnalysis, }, fieldInfo: nil, err: true, }, { // fully removed mapping in type with some dynamic => error original: &mapping.IndexMappingImpl{ TypeMapping: map[string]*mapping.DocumentMapping{ "map1": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "a": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, "map2": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "b": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, DefaultMapping: &mapping.DocumentMapping{ Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "c": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, IndexDynamic: false, StoreDynamic: false, DocValuesDynamic: false, CustomAnalysis: NewIndexMapping().CustomAnalysis, }, updated: &mapping.IndexMappingImpl{ TypeMapping: map[string]*mapping.DocumentMapping{ "map1": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "a": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, DefaultMapping: &mapping.DocumentMapping{ Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "c": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, IndexDynamic: true, StoreDynamic: false, DocValuesDynamic: false, CustomAnalysis: NewIndexMapping().CustomAnalysis, }, fieldInfo: nil, err: true, }, { // semi removed mapping in default with some dynamic // proper fieldInfo with no errors original: &mapping.IndexMappingImpl{ TypeMapping: map[string]*mapping.DocumentMapping{ "map1": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "a": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, "map2": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "b": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, DefaultMapping: &mapping.DocumentMapping{ Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "c": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, IndexDynamic: false, StoreDynamic: false, DocValuesDynamic: false, CustomAnalysis: NewIndexMapping().CustomAnalysis, }, updated: &mapping.IndexMappingImpl{ TypeMapping: map[string]*mapping.DocumentMapping{ "map1": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "a": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, "map2": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "b": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: false, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, DefaultMapping: &mapping.DocumentMapping{ Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "c": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, IndexDynamic: false, StoreDynamic: false, DocValuesDynamic: false, CustomAnalysis: NewIndexMapping().CustomAnalysis, }, fieldInfo: map[string]*index.UpdateFieldInfo{ "b": { Index: true, DocValues: true, }, }, err: false, }, { // two fields from diff paths with removed content matching // => relavent fieldInfo original: &mapping.IndexMappingImpl{ TypeMapping: map[string]*mapping.DocumentMapping{ "map1": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "a": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, "map2": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "a": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, DefaultMapping: &mapping.DocumentMapping{ Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "b": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, IndexDynamic: false, StoreDynamic: false, DocValuesDynamic: false, CustomAnalysis: NewIndexMapping().CustomAnalysis, }, updated: &mapping.IndexMappingImpl{ TypeMapping: map[string]*mapping.DocumentMapping{ "map1": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "a": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: false, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, "map2": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "a": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: false, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, DefaultMapping: &mapping.DocumentMapping{ Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "b": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, IndexDynamic: false, StoreDynamic: false, DocValuesDynamic: false, CustomAnalysis: NewIndexMapping().CustomAnalysis, }, fieldInfo: map[string]*index.UpdateFieldInfo{ "a": { Index: true, DocValues: true, }, }, err: false, }, { // two fields from diff paths with removed content not matching // => error original: &mapping.IndexMappingImpl{ TypeMapping: map[string]*mapping.DocumentMapping{ "map1": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "a": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, "map2": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "a": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, DefaultMapping: &mapping.DocumentMapping{ Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "b": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, IndexDynamic: false, StoreDynamic: false, DocValuesDynamic: false, CustomAnalysis: NewIndexMapping().CustomAnalysis, }, updated: &mapping.IndexMappingImpl{ TypeMapping: map[string]*mapping.DocumentMapping{ "map1": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "a": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, "map2": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "a": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: false, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, DefaultMapping: &mapping.DocumentMapping{ Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "b": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, IndexDynamic: false, StoreDynamic: false, DocValuesDynamic: false, CustomAnalysis: NewIndexMapping().CustomAnalysis, }, fieldInfo: nil, err: true, }, { // two fields from the same path => relavent fieldInfo original: &mapping.IndexMappingImpl{ TypeMapping: map[string]*mapping.DocumentMapping{ "map1": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "a": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Name: "a", Type: "numeric", Index: true, Store: true, }, { Name: "b", Type: "numeric", Index: true, Store: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, DefaultMapping: &mapping.DocumentMapping{ Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "c": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, IndexDynamic: false, StoreDynamic: false, DocValuesDynamic: false, CustomAnalysis: NewIndexMapping().CustomAnalysis, }, updated: &mapping.IndexMappingImpl{ TypeMapping: map[string]*mapping.DocumentMapping{ "map1": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "a": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Name: "a", Type: "numeric", Index: false, Store: true, }, { Name: "b", Type: "numeric", Index: true, Store: false, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, DefaultMapping: &mapping.DocumentMapping{ Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "c": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, IndexDynamic: false, StoreDynamic: false, DocValuesDynamic: false, CustomAnalysis: NewIndexMapping().CustomAnalysis, }, fieldInfo: map[string]*index.UpdateFieldInfo{ "a": { Index: true, DocValues: true, }, "b": { Store: true, }, }, err: false, }, { // one store, one index, one dynamic and one all removed in type and default // => relavent fieldInfo without error original: &mapping.IndexMappingImpl{ TypeMapping: map[string]*mapping.DocumentMapping{ "map1": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "a": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, "map2": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "b": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Store: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, "map3": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "c": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", DocValues: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, DefaultMapping: &mapping.DocumentMapping{ Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "d": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: true, Store: true, DocValues: true, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, IndexDynamic: false, StoreDynamic: false, DocValuesDynamic: false, CustomAnalysis: NewIndexMapping().CustomAnalysis, }, updated: &mapping.IndexMappingImpl{ TypeMapping: map[string]*mapping.DocumentMapping{ "map1": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "a": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Index: false, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, "map2": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "b": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", Store: false, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, "map3": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "c": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "numeric", DocValues: false, }, }, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, }, DefaultMapping: &mapping.DocumentMapping{ Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "", DefaultSynonymSource: "", }, IndexDynamic: false, StoreDynamic: false, DocValuesDynamic: false, CustomAnalysis: NewIndexMapping().CustomAnalysis, }, fieldInfo: map[string]*index.UpdateFieldInfo{ "a": { Index: true, DocValues: true, }, "b": { Store: true, }, "c": { DocValues: true, }, "d": { Deleted: true, }, }, err: false, }, } for i, test := range tests { info, err := DeletedFields(test.original, test.updated) if err == nil && test.err || err != nil && !test.err { t.Errorf("Unexpected error value for test %d, expecting %t, got %v\n", i, test.err, err) } if info == nil && test.fieldInfo != nil || info != nil && test.fieldInfo == nil || !reflect.DeepEqual(info, test.fieldInfo) { t.Errorf("Unexpected default info value for test %d, expecting %+v, got %+v, err %v", i, test.fieldInfo, info, err) } } } func TestIndexUpdateText(t *testing.T) { tmpIndexPath := createTmpIndexPath(t) defer cleanupTmpIndexPath(t, tmpIndexPath) indexMappingBefore := mapping.NewIndexMapping() indexMappingBefore.TypeMapping = map[string]*mapping.DocumentMapping{} indexMappingBefore.DefaultMapping = &mapping.DocumentMapping{ Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "a": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "text", Index: true, Store: true, }, }, DefaultAnalyzer: "standard", DefaultSynonymSource: "", }, "b": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "text", Index: true, Store: true, }, }, DefaultAnalyzer: "standard", DefaultSynonymSource: "", }, "c": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "text", Index: true, Store: true, }, }, DefaultAnalyzer: "standard", DefaultSynonymSource: "", }, "d": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "text", Index: true, Store: true, }, }, DefaultAnalyzer: "standard", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "standard", DefaultSynonymSource: "", } indexMappingBefore.IndexDynamic = false indexMappingBefore.StoreDynamic = false indexMappingBefore.DocValuesDynamic = false index, err := New(tmpIndexPath, indexMappingBefore) if err != nil { t.Fatal(err) } doc1 := map[string]interface{}{"a": "xyz", "b": "abc", "c": "def", "d": "ghi"} doc2 := map[string]interface{}{"a": "uvw", "b": "rst", "c": "klm", "d": "pqr"} doc3 := map[string]interface{}{"a": "xyz", "b": "def", "c": "abc", "d": "mno"} batch := index.NewBatch() err = batch.Index("001", doc1) if err != nil { t.Fatal(err) } err = batch.Index("002", doc2) if err != nil { t.Fatal(err) } err = batch.Index("003", doc3) if err != nil { t.Fatal(err) } err = index.Batch(batch) if err != nil { t.Fatal(err) } err = index.Close() if err != nil { t.Fatal(err) } indexMappingAfter := mapping.NewIndexMapping() indexMappingAfter.TypeMapping = map[string]*mapping.DocumentMapping{} indexMappingAfter.DefaultMapping = &mapping.DocumentMapping{ Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "a": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "text", Index: true, Store: true, }, }, DefaultAnalyzer: "standard", DefaultSynonymSource: "", }, "b": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "text", Index: false, Store: true, }, }, DefaultAnalyzer: "standard", DefaultSynonymSource: "", }, "c": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "text", Index: true, Store: false, }, }, DefaultAnalyzer: "standard", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "standard", DefaultSynonymSource: "", } indexMappingAfter.IndexDynamic = false indexMappingAfter.StoreDynamic = false indexMappingAfter.DocValuesDynamic = false mappingString, err := json.Marshal(indexMappingAfter) if err != nil { t.Fatal(err) } config := map[string]interface{}{ "updated_mapping": string(mappingString), } index, err = OpenUsing(tmpIndexPath, config) if err != nil { t.Fatal(err) } defer func() { err := index.Close() if err != nil { t.Fatal(err) } }() q1 := NewSearchRequest(NewQueryStringQuery("a:*")) q1.Fields = append(q1.Fields, "a") res1, err := index.Search(q1) if err != nil { t.Fatal(err) } if len(res1.Hits) != 3 { t.Errorf("Expected 3 hits, got %d\n", len(res1.Hits)) } if len(res1.Hits[0].Fields) != 1 { t.Errorf("Expected 1 field, got %d\n", len(res1.Hits[0].Fields)) } q2 := NewSearchRequest(NewQueryStringQuery("b:*")) q2.Fields = append(q2.Fields, "b") res2, err := index.Search(q2) if err != nil { t.Fatal(err) } if len(res2.Hits) != 0 { t.Errorf("Expected 0 hits, got %d\n", len(res2.Hits)) } q3 := NewSearchRequest(NewQueryStringQuery("c:*")) q3.Fields = append(q3.Fields, "c") res3, err := index.Search(q3) if err != nil { t.Fatal(err) } if len(res3.Hits) != 3 { t.Errorf("Expected 3 hits, got %d\n", len(res3.Hits)) } if len(res3.Hits[0].Fields) != 0 { t.Errorf("Expected 0 fields, got %d\n", len(res3.Hits[0].Fields)) } q4 := NewSearchRequest(NewQueryStringQuery("d:*")) q4.Fields = append(q4.Fields, "d") res4, err := index.Search(q4) if err != nil { t.Fatal(err) } if len(res4.Hits) != 0 { t.Errorf("Expected 0 hits, got %d\n", len(res4.Hits)) } } func TestIndexUpdateSynonym(t *testing.T) { tmpIndexPath := createTmpIndexPath(t) defer cleanupTmpIndexPath(t, tmpIndexPath) synonymCollection := "collection1" synonymSourceName := "english" analyzer := en.AnalyzerName synonymSourceConfig := map[string]interface{}{ "collection": synonymCollection, "analyzer": analyzer, } a := mapping.NewTextFieldMapping() a.Analyzer = analyzer a.SynonymSource = synonymSourceName a.IncludeInAll = false b := mapping.NewTextFieldMapping() b.Analyzer = analyzer b.SynonymSource = synonymSourceName b.IncludeInAll = false c := mapping.NewTextFieldMapping() c.Analyzer = analyzer c.SynonymSource = synonymSourceName c.IncludeInAll = false indexMappingBefore := mapping.NewIndexMapping() indexMappingBefore.DefaultMapping.AddFieldMappingsAt("a", a) indexMappingBefore.DefaultMapping.AddFieldMappingsAt("b", b) indexMappingBefore.DefaultMapping.AddFieldMappingsAt("c", c) err := indexMappingBefore.AddSynonymSource(synonymSourceName, synonymSourceConfig) if err != nil { t.Fatal(err) } indexMappingBefore.IndexDynamic = false indexMappingBefore.StoreDynamic = false indexMappingBefore.DocValuesDynamic = false index, err := New(tmpIndexPath, indexMappingBefore) if err != nil { t.Fatal(err) } doc1 := map[string]interface{}{ "a": `The hardworking employee consistently strives to exceed expectations. His industrious nature makes him a valuable asset to any team. His conscientious attention to detail ensures that projects are completed efficiently and accurately. He remains persistent even in the face of challenges.`, "b": `The hardworking employee consistently strives to exceed expectations. His industrious nature makes him a valuable asset to any team. His conscientious attention to detail ensures that projects are completed efficiently and accurately. He remains persistent even in the face of challenges.`, "c": `The hardworking employee consistently strives to exceed expectations. His industrious nature makes him a valuable asset to any team. His conscientious attention to detail ensures that projects are completed efficiently and accurately. He remains persistent even in the face of challenges.`, } doc2 := map[string]interface{}{ "a": `The tranquil surroundings of the retreat provide a perfect escape from the hustle and bustle of city life. Guests enjoy the peaceful atmosphere, which is perfect for relaxation and rejuvenation. The calm environment offers the ideal place to meditate and connect with nature. Even the most stressed individuals find themselves feeling relaxed and at ease.`, "b": `The tranquil surroundings of the retreat provide a perfect escape from the hustle and bustle of city life. Guests enjoy the peaceful atmosphere, which is perfect for relaxation and rejuvenation. The calm environment offers the ideal place to meditate and connect with nature. Even the most stressed individuals find themselves feeling relaxed and at ease.`, "c": `The tranquil surroundings of the retreat provide a perfect escape from the hustle and bustle of city life. Guests enjoy the peaceful atmosphere, which is perfect for relaxation and rejuvenation. The calm environment offers the ideal place to meditate and connect with nature. Even the most stressed individuals find themselves feeling relaxed and at ease.`, } synDoc1 := &SynonymDefinition{Synonyms: []string{"hardworking", "industrious", "conscientious", "persistent", "focused", "devoted"}} synDoc2 := &SynonymDefinition{Synonyms: []string{"tranquil", "peaceful", "calm", "relaxed", "unruffled"}} batch := index.NewBatch() err = batch.IndexSynonym("001", synonymCollection, synDoc1) if err != nil { t.Fatal(err) } err = batch.IndexSynonym("002", synonymCollection, synDoc2) if err != nil { t.Fatal(err) } err = batch.Index("003", doc1) if err != nil { t.Fatal(err) } err = batch.Index("004", doc2) if err != nil { t.Fatal(err) } err = index.Batch(batch) if err != nil { t.Fatal(err) } err = index.Close() if err != nil { t.Fatal(err) } indexMappingAfter := mapping.NewIndexMapping() indexMappingAfter.DefaultMapping.AddFieldMappingsAt("a", a) b.Index = false indexMappingAfter.DefaultMapping.AddFieldMappingsAt("b", b) err = indexMappingAfter.AddSynonymSource(synonymSourceName, synonymSourceConfig) if err != nil { t.Fatal(err) } indexMappingAfter.IndexDynamic = false indexMappingAfter.StoreDynamic = false indexMappingAfter.DocValuesDynamic = false mappingString, err := json.Marshal(indexMappingAfter) if err != nil { t.Fatal(err) } config := map[string]interface{}{ "updated_mapping": string(mappingString), } index, err = OpenUsing(tmpIndexPath, config) if err != nil { t.Fatal(err) } defer func() { err := index.Close() if err != nil { t.Fatal(err) } }() q1 := NewSearchRequest(NewQueryStringQuery("a:devoted")) res1, err := index.Search(q1) if err != nil { t.Fatal(err) } if len(res1.Hits) != 1 { t.Errorf("Expected 1 hit, got %d\n", len(res1.Hits)) } q2 := NewSearchRequest(NewQueryStringQuery("b:devoted")) res2, err := index.Search(q2) if err != nil { t.Fatal(err) } if len(res2.Hits) != 0 { t.Errorf("Expected 0 hits, got %d\n", len(res2.Hits)) } q3 := NewSearchRequest(NewQueryStringQuery("c:unruffled")) res3, err := index.Search(q3) if err != nil { t.Fatal(err) } if len(res3.Hits) != 0 { t.Errorf("Expected 0 hits, got %d\n", len(res3.Hits)) } } func TestIndexUpdateMerge(t *testing.T) { tmpIndexPath := createTmpIndexPath(t) defer cleanupTmpIndexPath(t, tmpIndexPath) indexMappingBefore := mapping.NewIndexMapping() indexMappingBefore.TypeMapping = map[string]*mapping.DocumentMapping{} indexMappingBefore.DefaultMapping = &mapping.DocumentMapping{ Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "a": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "text", Index: true, Store: true, }, }, DefaultAnalyzer: "standard", DefaultSynonymSource: "", }, "b": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "text", Index: true, Store: true, }, }, DefaultAnalyzer: "standard", DefaultSynonymSource: "", }, "c": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "text", Index: true, Store: true, }, }, DefaultAnalyzer: "standard", DefaultSynonymSource: "", }, "d": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "text", Index: true, Store: true, }, }, DefaultAnalyzer: "standard", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "standard", DefaultSynonymSource: "", } indexMappingBefore.IndexDynamic = false indexMappingBefore.StoreDynamic = false indexMappingBefore.DocValuesDynamic = false index, err := New(tmpIndexPath, indexMappingBefore) if err != nil { t.Fatal(err) } numDocsPerBatch := 1000 numBatches := 10 var batch *Batch doc := make(map[string]interface{}) const letters = "abcdefghijklmnopqrstuvwxyz" randStr := func() string { result := make([]byte, 3) for i := 0; i < 3; i++ { result[i] = letters[rand.Intn(len(letters))] } return string(result) } for i := 0; i < numBatches; i++ { batch = index.NewBatch() for j := 0; j < numDocsPerBatch; j++ { doc["a"] = randStr() doc["b"] = randStr() doc["c"] = randStr() doc["d"] = randStr() err = batch.Index(fmt.Sprintf("%d", i*numDocsPerBatch+j), doc) if err != nil { t.Fatal(err) } } err = index.Batch(batch) if err != nil { t.Fatal(err) } } err = index.Close() if err != nil { t.Fatal(err) } indexMappingAfter := mapping.NewIndexMapping() indexMappingAfter.TypeMapping = map[string]*mapping.DocumentMapping{} indexMappingAfter.DefaultMapping = &mapping.DocumentMapping{ Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "a": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "text", Index: true, Store: true, }, }, DefaultAnalyzer: "standard", DefaultSynonymSource: "", }, "b": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "text", Index: false, Store: true, }, }, DefaultAnalyzer: "standard", DefaultSynonymSource: "", }, "c": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "text", Index: true, Store: false, }, }, DefaultAnalyzer: "standard", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "standard", DefaultSynonymSource: "", } indexMappingAfter.IndexDynamic = false indexMappingAfter.StoreDynamic = false indexMappingAfter.DocValuesDynamic = false mappingString, err := json.Marshal(indexMappingAfter) if err != nil { t.Fatal(err) } config := map[string]interface{}{ "updated_mapping": string(mappingString), } index, err = OpenUsing(tmpIndexPath, config) if err != nil { t.Fatal(err) } defer func() { err := index.Close() if err != nil { t.Fatal(err) } }() impl, ok := index.(*indexImpl) if !ok { t.Fatalf("Typecasting index to indexImpl failed") } sindex, ok := impl.i.(*scorch.Scorch) if !ok { t.Fatalf("Typecasting index to scorch index failed") } err = sindex.ForceMerge(context.Background(), &mergeplan.SingleSegmentMergePlanOptions) if err != nil { t.Fatal(err) } q1 := NewSearchRequest(NewQueryStringQuery("a:*")) q1.Fields = append(q1.Fields, "a") res1, err := index.Search(q1) if err != nil { t.Fatal(err) } if len(res1.Hits) != 10 { t.Errorf("Expected 10 hits, got %d\n", len(res1.Hits)) } if len(res1.Hits[0].Fields) != 1 { t.Errorf("Expected 1 field, got %d\n", len(res1.Hits[0].Fields)) } q2 := NewSearchRequest(NewQueryStringQuery("b:*")) q2.Fields = append(q2.Fields, "b") res2, err := index.Search(q2) if err != nil { t.Fatal(err) } if len(res2.Hits) != 0 { t.Errorf("Expected 0 hits, got %d\n", len(res2.Hits)) } q3 := NewSearchRequest(NewQueryStringQuery("c:*")) q3.Fields = append(q3.Fields, "c") res3, err := index.Search(q3) if err != nil { t.Fatal(err) } if len(res3.Hits) != 10 { t.Errorf("Expected 10 hits, got %d\n", len(res3.Hits)) } if len(res3.Hits[0].Fields) != 0 { t.Errorf("Expected 0 fields, got %d\n", len(res3.Hits[0].Fields)) } q4 := NewSearchRequest(NewQueryStringQuery("d:*")) q4.Fields = append(q4.Fields, "d") res4, err := index.Search(q4) if err != nil { t.Fatal(err) } if len(res4.Hits) != 0 { t.Errorf("Expected 0 hits, got %d\n", len(res4.Hits)) } } func BenchmarkIndexUpdateText(b *testing.B) { tmpIndexPath := createTmpIndexPath(b) defer cleanupTmpIndexPath(b, tmpIndexPath) indexMappingBefore := mapping.NewIndexMapping() indexMappingBefore.TypeMapping = map[string]*mapping.DocumentMapping{} indexMappingBefore.DefaultMapping = &mapping.DocumentMapping{ Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "a": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "text", Index: true, Store: true, }, }, DefaultAnalyzer: "standard", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "standard", DefaultSynonymSource: "", } indexMappingBefore.IndexDynamic = false indexMappingBefore.StoreDynamic = false indexMappingBefore.DocValuesDynamic = false index, err := New(tmpIndexPath, indexMappingBefore) if err != nil { b.Fatal(err) } numDocsPerBatch := 1000 numBatches := 5 var batch *Batch doc := make(map[string]interface{}) const letters = "abcdefghijklmnopqrstuvwxyz" randStr := func() string { result := make([]byte, 3) for i := 0; i < 3; i++ { result[i] = letters[rand.Intn(len(letters))] } return string(result) } for i := 0; i < numBatches; i++ { batch = index.NewBatch() for j := 0; j < numDocsPerBatch; j++ { doc["a"] = randStr() err = batch.Index(fmt.Sprintf("%d", i*numDocsPerBatch+j), doc) if err != nil { b.Fatal(err) } } err = index.Batch(batch) if err != nil { b.Fatal(err) } } err = index.Close() if err != nil { b.Fatal(err) } indexMappingAfter := mapping.NewIndexMapping() indexMappingAfter.TypeMapping = map[string]*mapping.DocumentMapping{} indexMappingAfter.DefaultMapping = &mapping.DocumentMapping{ Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{ "a": { Enabled: true, Dynamic: false, Properties: map[string]*mapping.DocumentMapping{}, Fields: []*mapping.FieldMapping{ { Type: "text", Index: true, Store: false, }, }, DefaultAnalyzer: "standard", DefaultSynonymSource: "", }, }, Fields: []*mapping.FieldMapping{}, DefaultAnalyzer: "standard", DefaultSynonymSource: "", } indexMappingAfter.IndexDynamic = false indexMappingAfter.StoreDynamic = false indexMappingAfter.DocValuesDynamic = false mappingString, err := json.Marshal(indexMappingAfter) if err != nil { b.Fatal(err) } config := map[string]interface{}{ "updated_mapping": string(mappingString), } index, err = OpenUsing(tmpIndexPath, config) if err != nil { b.Fatal(err) } defer func() { err := index.Close() if err != nil { b.Fatal(err) } }() b.ResetTimer() for i := 0; i < b.N; i++ { q := NewQueryStringQuery("a:*") req := NewSearchRequest(q) if _, err = index.Search(req); err != nil { b.Fatal(err) } } } func TestIndexUpdateNestedMapping(t *testing.T) { // Helper: create a mapping with optional nested structure createCompanyMapping := func(nestedEmployees, nestedDepartments, nestedProjects, nestedLocations bool) *mapping.IndexMappingImpl { rv := mapping.NewIndexMapping() companyMapping := mapping.NewDocumentMapping() // Basic fields companyMapping.AddFieldMappingsAt("id", mapping.NewTextFieldMapping()) companyMapping.AddFieldMappingsAt("name", mapping.NewTextFieldMapping()) var deptMapping *mapping.DocumentMapping // Departments nested conditionally if !nestedDepartments { deptMapping = mapping.NewDocumentMapping() } else { deptMapping = mapping.NewNestedDocumentMapping() } deptMapping.AddFieldMappingsAt("name", mapping.NewTextFieldMapping()) deptMapping.AddFieldMappingsAt("budget", mapping.NewNumericFieldMapping()) // Employees nested conditionally var empMapping *mapping.DocumentMapping if !nestedEmployees { empMapping = mapping.NewNestedDocumentMapping() } else { empMapping = mapping.NewDocumentMapping() } empMapping.AddFieldMappingsAt("name", mapping.NewTextFieldMapping()) empMapping.AddFieldMappingsAt("role", mapping.NewTextFieldMapping()) deptMapping.AddSubDocumentMapping("employees", empMapping) // Projects nested conditionally var projMapping *mapping.DocumentMapping if !nestedProjects { projMapping = mapping.NewNestedDocumentMapping() } else { projMapping = mapping.NewDocumentMapping() } projMapping.AddFieldMappingsAt("title", mapping.NewTextFieldMapping()) projMapping.AddFieldMappingsAt("status", mapping.NewTextFieldMapping()) deptMapping.AddSubDocumentMapping("projects", projMapping) companyMapping.AddSubDocumentMapping("departments", deptMapping) // Locations nested conditionally var locMapping *mapping.DocumentMapping if nestedLocations { locMapping = mapping.NewNestedDocumentMapping() } else { locMapping = mapping.NewDocumentMapping() } locMapping.AddFieldMappingsAt("address", mapping.NewTextFieldMapping()) locMapping.AddFieldMappingsAt("city", mapping.NewTextFieldMapping()) companyMapping.AddSubDocumentMapping("locations", locMapping) rv.DefaultMapping.AddSubDocumentMapping("company", companyMapping) return rv } tests := []struct { name string original *mapping.IndexMappingImpl updated *mapping.IndexMappingImpl expectErr bool }{ { name: "No nested to all nested", original: createCompanyMapping(false, false, false, false), updated: createCompanyMapping(true, true, true, true), expectErr: true, }, { name: "No nested to mixed nested", original: createCompanyMapping(false, false, false, false), updated: createCompanyMapping(true, false, true, false), expectErr: true, }, { name: "No nested to mixed nested", original: createCompanyMapping(false, false, false, false), updated: createCompanyMapping(true, true, true, false), expectErr: true, }, { name: "Mixed nested to no nested", original: createCompanyMapping(false, true, false, true), updated: createCompanyMapping(false, false, true, true), expectErr: true, }, { name: "All nested to no nested", original: createCompanyMapping(true, true, true, true), updated: createCompanyMapping(false, false, false, false), expectErr: true, }, { name: "Mixed nested to all nested", original: createCompanyMapping(true, false, true, false), updated: createCompanyMapping(true, true, true, true), expectErr: true, }, { name: "All nested to mixed nested", original: createCompanyMapping(true, true, true, true), updated: createCompanyMapping(true, false, true, false), expectErr: true, }, { name: "No nested to no nested", original: createCompanyMapping(false, false, false, false), updated: createCompanyMapping(false, false, false, false), expectErr: false, }, { name: "All nested to all nested", original: createCompanyMapping(true, true, true, true), updated: createCompanyMapping(true, true, true, true), expectErr: false, }, } for _, test := range tests { _, err := DeletedFields(test.original, test.updated) if (err != nil) != test.expectErr { t.Errorf("Test '%s' unexpected error state: got %v, expectErr %t", test.name, err, test.expectErr) } } }