Skip to content

2.x Improperly caches field name when dynamic field + suffix combination used #2319

@brandondahler

Description

@brandondahler

NEST/Elasticsearch.Net version:
NEST 2.4.4
Elasticsearch.Net.2.4.4

Elasticsearch version:
2.4.0

Description of the problem including expected versus actual behavior:
When functionally generating queries that includes a dynamic field's child multi-field, the first query condition is generated properly, but subsequent conditions will continue to use the original field name instead of the correct field name. Only occurs when there both a constant and a non-constant in the Field() function's expression.

Steps to reproduce:

using System;
using System.Collections.Generic;
using Nest;
using System.Text;

namespace Test
{

    public class Program
    {
        public static void Main()
        {
            var connectionSettings = new ConnectionSettings(new Uri("http://localhost:9200"));
            connectionSettings.DisableDirectStreaming();
            connectionSettings.PrettyJson();

            var elasticClient = new ElasticClient(connectionSettings);

            var expected = elasticClient.Search<TestDocument>(sd => sd
                .Query(qcd => qcd
                    .Bool(bqd => bqd
                        .Must(
                            qcd
                                .Term(tqd => tqd
                                    .Field(td => td.fields["foo"].Suffix("suffix"))
                                    .Value("a")),
                            qcd
                                .Term(tqd => tqd
                                    .Field(td => td.fields["bar"].Suffix("suffix"))
                                    .Value("b"))
                        )
                    )
                )
            );

            var actual = elasticClient.Search<TestDocument>(sd => sd
                .Query(qcd => qcd
                    .Bool(bqd => bqd
                        .Must(
                            CreateCondition("foo", "a"),
                            CreateCondition("bar", "b")
                        )
                    )
                )
            );

            Console.WriteLine("Expected: {0}", Encoding.UTF8.GetString(expected.ApiCall.RequestBodyInBytes));
            Console.WriteLine();
            Console.WriteLine("Actual:   {0}", Encoding.UTF8.GetString(actual.ApiCall.RequestBodyInBytes));
        }

        private static Func<QueryContainerDescriptor<TestDocument>, QueryContainer> CreateCondition(string fieldName, string value)
        {
            return qcd => qcd
                .Term(tqd => tqd
                    .Field(td => td.fields[fieldName].Suffix("suffix"))
                    .Value(value));
        }

        private class TestDocument
        {
            public IDictionary<string, string> fields { get; set; }
        }
    }
}

Outputs:

Expected: {
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "fields.foo.suffix": {
              "value": "a"
            }
          }
        },
        {
          "term": {
            "fields.bar.suffix": {
              "value": "b"
            }
          }
        }
      ]
    }
  }
}

Actual:   {
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "fields.foo.suffix": {
              "value": "a"
            }
          }
        },
        {
          "term": {
            "fields.foo.suffix": {
              "value": "b"
            }
          }
        }
      ]
    }
  }
}

Root cause is src/Nest/CommonAbstractions/Infer/Field/Field.cs#L48 setting the expression to cacheable when there is a constant component to the expression.

Instead, CachableExpression should only be set when there are only constant expressions. You could check that there are no MemberAccess nodes (variable references are captured as members), but that wouldn't handle cases such as tqd.Field(td => td.fields[SomeFunction()]).

Provide ConnectionSettings (if relevant):
N/A

Provide DebugInformation (if relevant):
N/A

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions