SearchTemplate с переменными сортировки и подкачки в Elasticsearch 6.6 и NEST 6.6

Все, я пытаюсь вызвать SearchTemplate, определенный в ES 6.6. Шаблон имеет переменные подкачки (от и размер) и электронные письма, которые я передаю в массиве. Это также имеет сортировку с пользовательской логикой сценария. Когда я запускаю это в кибане, я не вижу, что подкачка и сортировка не работают. Я был бы признателен за любую помощь в получении этого на работу. Пожалуйста, смотрите детали ниже. Есть два индекса, которые я ищу, используя псевдоним индекса.

Отображения для персоны и гостевых индексов одинаковы (просто для упрощения примера)

Отображение индекса

PUT _template/person_guest_template
{
  "order": 0,
  "index_patterns": ["person*","guest*"],
  "settings": {
    "index": {
      "analysis": {
        "filter": {
          "autoComplete_filter": {
            "type": "edge_ngram",
            "min_gram": "2",
            "max_gram": "20"
          }
        },
        "analyzer": {
          "autoComplete": {
            "filter": ["lowercase", "asciifolding","autoComplete_filter"],
            "type": "custom",
            "tokenizer": "whitespace"
          },
          "default": {
            "filter": ["lowercase", "asciifolding"],
            "type": "custom",
            "tokenizer": "whitespace"
          }
        }
      },
      "number_of_shards": "3",
      "number_of_replicas": "1"
    }
  },
  "mappings": {
    "_doc": {
      "dynamic": false,
      "properties": {
        "firstName": {
          "type": "keyword",
          "fields": {
            "search": {
              "type": "text",
              "analyzer": "autoComplete",
              "search_analyzer": "default"
            }
          }
        },
        "lastName": {
          "type": "keyword",
          "fields": {
            "search": {
              "type": "text",
              "analyzer": "autoComplete",
              "search_analyzer": "default"
            }
          }
        },
        "email": {
          "type": "keyword"
        },"email": {
      "type": "keyword"
    }
      }
    }
  }
}

Определение шаблона поиска

POST _scripts/guest_person_by_email
{
  "script": {
    "from": "{{from}}{{^from}}0{{/from}}",
    "size": "{{size}}{{^size}}5{{/size}}",
    "sort": [
      {
        "_script": {
          "order": "asc",
          "type": "number",
          "script": "return (doc['type'].value == 'person')? 0 : 1;"
        }
      },
      {
        "firstName": {
          "order": "asc"
        }
      },
      {
        "lastName": {
          "order": "asc"
        }
      }
    ],
    "lang": "mustache",
    "source": """
    {
      "query":{
        "bool":{
          "filter":{
            "terms":{
              "email":
              {{#toJson}}emails{{/toJson}}
            }
          }
        }
      }
    }
"""
  }
}

Поиск с использованием SearchTemplate

GET guest-person/_search/template
{
  "id":"guest_person_by_email",
  "params": {
    "emails":["rennishj@test.com"]
  }
}

Пример данных

PUT person/_doc/1
{
  "firstName": "Rennish",
  "lastName": "Joseph",
  "email": [
    "rennishj@test.com"
  ],
  "type":"person"
}

Вызов шаблона поиска с использованием NEST 6.6

List<string> emails = new List<string>(){"rennishj@test.com"};
var searchResponse = client.SearchTemplate<object>(st => st
    .Index("guest-person")
    .Id("guest_person_by_email")
    .Params(p => p
        .Add("emails", emails.ToArray())
        .Add("from", 0)     
        .Add("size", 50)
    )
);

наблюдения

  1. Когда я удаляю логику from, size и sort из searchtemplate, она работает
  2. Похоже, я помещаю переменные sort и from/size в неправильное место?

Я нашел подобный пост здесь https://discuss.elastic.co/t/c-nest-5-search-with-template/104074/2 но похоже, что GetSearchTemplate и PutSearchTemplate более не поддерживаются в NEST 6.x

Можно ли это сделать с помощью поисковых шаблонов? Мы используем несколько очень сложных запросов NEST, отходим от NEST и используем шаблоны поиска.

2 ответа

Решение

Есть несколько вопросов

  1. шаблон индекса определяет "email" отображение поля дважды
  2. наборы шаблонов индексов "dynamic" ложно, но не содержит "type" отображение полей, поэтому сортировка сценария не удастся
  3. весь поисковый запрос должен быть определен в пределах "source" для вызова API Put Script

NEST может быть полезен при построении правильных поисковых запросов и их использовании в качестве основы для шаблона поиска, помимо целого ряда других причин использования клиента, таких как запросы с циклическим перебором, автоматическое переключение при сбое и повторные попытки и т. Д.

Вот полный пример

private static void Main()
{
    var defaultIndex = "person";
    var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));

    var settings = new ConnectionSettings(pool)
        .DefaultIndex(defaultIndex)
        .DefaultTypeName("_doc");

    var client = new ElasticClient(settings);

    // WARNING: This deletes the index to make this code repeatable.
    // You probably want to remove this if copying verbatim
    if (client.IndexExists(defaultIndex).Exists)
        client.DeleteIndex(defaultIndex);

    var indexTemplateResponse = client.LowLevel.IndicesPutTemplateForAll<PutIndexTemplateResponse>(
        "person_guest_template",
        @"{
          ""order"": 0,
          ""index_patterns"": [""person*"",""guest*""],
          ""settings"": {
            ""index"": {
              ""analysis"": {
                ""filter"": {
                  ""autoComplete_filter"": {
                    ""type"": ""edge_ngram"",
                    ""min_gram"": ""2"",
                    ""max_gram"": ""20""
                  }
                },
                ""analyzer"": {
                  ""autoComplete"": {
                    ""filter"": [""lowercase"", ""asciifolding"",""autoComplete_filter""],
                    ""type"": ""custom"",
                    ""tokenizer"": ""whitespace""
                  },
                  ""default"": {
                    ""filter"": [""lowercase"", ""asciifolding""],
                    ""type"": ""custom"",
                    ""tokenizer"": ""whitespace""
                  }
                }
              },
              ""number_of_shards"": ""3"",
              ""number_of_replicas"": ""1""
            }
          },
          ""mappings"": {
            ""_doc"": {
              ""dynamic"": false,
              ""properties"": {
                ""firstName"": {
                  ""type"": ""keyword"",
                  ""fields"": {
                    ""search"": {
                      ""type"": ""text"",
                      ""analyzer"": ""autoComplete"",
                      ""search_analyzer"": ""default""
                    }
                  }
                },
                ""lastName"": {
                  ""type"": ""keyword"",
                  ""fields"": {
                    ""search"": {
                      ""type"": ""text"",
                      ""analyzer"": ""autoComplete"",
                      ""search_analyzer"": ""default""
                    }
                  }
                },
                ""email"": {
                  ""type"": ""keyword""
                },
                ""type"": {
                  ""type"": ""keyword""
                }
              }
            }
          }
        }");

    // build a prototype search request     
    var searchRequest = new SearchRequest
    {
        From = 0,
        Size = 0,
        Sort = new List<ISort> 
        {
            new ScriptSort
            {
                Order = Nest.SortOrder.Ascending,
                Type = "number",
                Script = new InlineScript("return (doc['type'].value == 'person')? 0 : 1;")
            },
            new SortField
            {
                Field = "firstName",
                Order = Nest.SortOrder.Ascending
            },
            new SortField
            {
                Field = "lastName",
                Order = Nest.SortOrder.Ascending
            }
        },
        Query = new BoolQuery
        {
            Filter = new QueryContainer[] 
            {
                new TermsQuery
                {
                    Field = "email",
                    Terms = new[] { "emails" }
                }
            }
        }
    };

    var json = client.RequestResponseSerializer.SerializeToString(searchRequest);
    // create template from prototype search request
    var jObject = JsonConvert.DeserializeObject<JObject>(json); 
    jObject["from"] = "{{from}}{{^from}}0{{/from}}";
    jObject["size"] = "{{size}}{{^size}}5{{/size}}";    
    json = jObject.ToString(Newtonsoft.Json.Formatting.None);
    // below is invalid JSON, so can only be constructed with replacement
    json = json.Replace("[\"emails\"]", "{{#toJson}}emails{{/toJson}}");

    // add search template
    var putScriptResponse = client.PutScript("guest_person_by_email", s => s
        .Script(sc => sc
            .Lang(ScriptLang.Mustache)
            .Source(json)
        )
    );

    var person = new Person
    {
        FirstName = "Rennish",
        LastName = "Joseph",
        Email = new[] { "rennishj@test.com" }
    };

    // index document
    var indexResponse = client.Index(person, i => i.Id(1).Refresh(Refresh.WaitFor));

    // search
    var searchResponse = client.SearchTemplate<Person>(s => s
        .Id("guest_person_by_email")
        .Params(p => p
            .Add("emails", person.Email)
            .Add("from", 0)
            .Add("size", 50)
        )
    );
}

public class Person 
{
    public string FirstName {get;set;}
    public string LastName { get; set; }
    public string[] Email {get;set;}
    public string Type {get; set;} = "person";
}

Результат запроса шаблона поиска

{
  "took" : 47,
  "timed_out" : false,
  "_shards" : {
    "total" : 3,
    "successful" : 3,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : null,
    "hits" : [
      {
        "_index" : "person",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : null,
        "_source" : {
          "firstName" : "Rennish",
          "lastName" : "Joseph",
          "email" : [
            "rennishj@test.com"
          ],
          "type" : "person"
        },
        "sort" : [
          0.0,
          "Rennish",
          "Joseph"
        ]
      }
    ]
  }
}

Добавление правильного SearchTemplate(перемещение страниц и сортировка по "источнику", как указал Расс Кэм) на тот случай, если кому-то понадобится в будущем.

POST _scripts/guest_person_by_email
{
  "script": {    
    "lang": "mustache",
    "source": """
     {
       "from": "{{from}}{{^from}}0{{/from}}",
       "size": "{{size}}{{^size}}5{{/size}}",
      "sort": [
      {
        "_script": {
          "order": "asc",
          "type": "number",
          "script": "return (doc['type'].value == 'person')? 0 : 1;"
        }
      },
      {
        "firstName": {
          "order": "asc"
        }
      },
      {
        "lastName": {
          "order": "asc"
        }
      }
    ],
      "query":{
        "bool":{
          "filter":{
            "terms":{
              "email":
              {{#toJson}}emails{{/toJson}}
            }
          }
        }
      }
    }
"""
  }
}
Другие вопросы по тегам