Skip to content

Instantly share code, notes, and snippets.

@DrLeh

DrLeh/Readme.md Secret

Last active February 7, 2018 00:51
Show Gist options
  • Save DrLeh/4288c26a112479e400865d04f99723a1 to your computer and use it in GitHub Desktop.
Save DrLeh/4288c26a112479e400865d04f99723a1 to your computer and use it in GitHub Desktop.

Here I try several ways of mapping my fields to make query search work how I want it to. None of the mappings nor the attempts to transform my query term have resulted in positive results in all cases. Each strategy seems to cover a few cases but never all of them.

Mapping:

Output mapping

"student": {
  "mappings": {
    "student": {
      "properties": {
        "name": {
          "type": "text"
        },
        "nameWithFieldData": {
          "type": "text",
          "fielddata": true
        },
        "nameWithFieldDataAndAnalyzer": {
          "type": "text",
          "analyzer": "sort_analyzer",
          "fielddata": true
        },
        "nameWithKeyword": {
          "type": "keyword"
        }
      }
    }
  }
},
LOCAL> search
Index student deleted
index student created
Indexed data successfully
Total records in index: 3
QueryString search with text
Query bob matches with 2
Query bob smith returned 3 instead of 1
Query "bob" matches with 2
Query "bob smi" returned 0 instead of 1
Query "bob smith" matches with 1
Query bob* matches with 2
Query bob?smith returned 0 instead of 1
Query *bob?smith* returned 0 instead of 1
Query bob\ smith returned 3 instead of 1
Query *smith matches with 2
Query *smith* matches with 2
Query "bob"* returned 3 instead of 2
Query *"bob"* returned 3 instead of 2
Query *"smith"* returned 3 instead of 2
Query "bob\ smith" returned 1 instead of 2
QueryString search with text + field data
Query bob matches with 2
Query bob smith returned 3 instead of 1
Query "bob" matches with 2
Query "bob smi" returned 0 instead of 1
Query "bob smith" matches with 1
Query bob* matches with 2
Query bob?smith returned 0 instead of 1
Query *bob?smith* returned 0 instead of 1
Query bob\ smith returned 3 instead of 1
Query *smith matches with 2
Query *smith* matches with 2
Query "bob"* returned 3 instead of 2
Query *"bob"* returned 3 instead of 2
Query *"smith"* returned 3 instead of 2
Query "bob\ smith" returned 1 instead of 2
QueryString search with text + field data + analyzer
Query bob returned 0 instead of 2
Query bob smith returned 0 instead of 1
Query "bob" returned 0 instead of 2
Query "bob smi" returned 0 instead of 1
Query "bob smith" matches with 1
Query bob* matches with 2
Query bob?smith matches with 1
Query *bob?smith* matches with 1
Query bob\ smith matches with 1
Query *smith matches with 2
Query *smith* matches with 2
Query "bob"* returned 3 instead of 2
Query *"bob"* returned 3 instead of 2
Query *"smith"* returned 3 instead of 2
Query "bob\ smith" returned 1 instead of 2
QueryString search By Keyword
Query bob returned 0 instead of 2
Query bob smith returned 0 instead of 1
Query "bob" returned 0 instead of 2
Query "bob smi" returned 0 instead of 1
Query "bob smith" matches with 1
Query bob* matches with 2
Query bob?smith matches with 1
Query *bob?smith* matches with 1
Query bob\ smith matches with 1
Query *smith matches with 2
Query *smith* matches with 2
Query "bob"* returned 3 instead of 2
Query *"bob"* returned 3 instead of 2
Query *"smith"* returned 3 instead of 2
Query "bob\ smith" returned 1 instead of 2
Match search with text
Match bob matches with 2
Match bob smith returned 3 instead of 1
Match "bob" matches with 2
Match "bob smi" returned 2 instead of 1
Match "bob smith" returned 3 instead of 1
Match bob* matches with 2
Match bob?smith returned 3 instead of 1
Match *bob?smith* returned 3 instead of 1
Match bob\ smith returned 3 instead of 1
Match *smith matches with 2
Match *smith* matches with 2
Match "bob"* matches with 2
Match *"bob"* matches with 2
Match *"smith"* matches with 2
Match "bob\ smith" returned 3 instead of 2
Match search with text + field data
Match bob matches with 2
Match bob smith returned 3 instead of 1
Match "bob" matches with 2
Match "bob smi" returned 2 instead of 1
Match "bob smith" returned 3 instead of 1
Match bob* matches with 2
Match bob?smith returned 3 instead of 1
Match *bob?smith* returned 3 instead of 1
Match bob\ smith returned 3 instead of 1
Match *smith matches with 2
Match *smith* matches with 2
Match "bob"* matches with 2
Match *"bob"* matches with 2
Match *"smith"* matches with 2
Match "bob\ smith" returned 3 instead of 2
Match search with text + field data + analyzer
Match bob returned 0 instead of 2
Match bob smith matches with 1
Match "bob" returned 0 instead of 2
Match "bob smi" returned 0 instead of 1
Match "bob smith" returned 0 instead of 1
Match bob* returned 0 instead of 2
Match bob?smith returned 0 instead of 1
Match *bob?smith* returned 0 instead of 1
Match bob\ smith returned 0 instead of 1
Match *smith returned 0 instead of 2
Match *smith* returned 0 instead of 2
Match "bob"* returned 0 instead of 2
Match *"bob"* returned 0 instead of 2
Match *"smith"* returned 0 instead of 2
Match "bob\ smith" returned 0 instead of 2
Match search By Keyword
Match bob returned 0 instead of 2
Match bob smith matches with 1
Match "bob" returned 0 instead of 2
Match "bob smi" returned 0 instead of 1
Match "bob smith" returned 0 instead of 1
Match bob* returned 0 instead of 2
Match bob?smith returned 0 instead of 1
Match *bob?smith* returned 0 instead of 1
Match bob\ smith returned 0 instead of 1
Match *smith returned 0 instead of 2
Match *smith* returned 0 instead of 2
Match "bob"* returned 0 instead of 2
Match *"bob"* returned 0 instead of 2
Match *"smith"* returned 0 instead of 2
Match "bob\ smith" returned 0 instead of 2
using Nest;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ESP.Console.Commands.Search
{
//TerminalCommand and ITerminalCommand are just wrappers for our codebase to make it easier for me to run abritrary code.
// you can just drop the contents into a new project with Execute as static void main.
[TerminalCommand("search")]
public class SearchNewCommand : ITerminalCommand
{
const string index = "student";
private const string SortAnalyzerName = "sort_analyzer";
private static IndexName indexName = new IndexName() { Name = index };
public void Execute(CommandArguments args, ConnectionStrings connectionStrings)
{
var client = GetClient();
RecreateIndex(client);
Seed(client);
Thread.Sleep(1000); //give elastic a second to get ready
Sanity(client);
var testCases = new List<TestCase>
{
//no formating of the input
new TestCase("bob", 2), // match on first name
new TestCase("bob smith", 1), // exact match should return the one result that matches full string
//literal string
new TestCase("\"bob\"", 2), // attempt to match on partial input
new TestCase("\"bob smi\"", 1), // attempt to match on partial input
new TestCase("\"bob smith\"", 1), // attempt to match on full name
//querystring-like fields
new TestCase("bob*", 2), // wildcard match
new TestCase("bob?smith", 1), // replace the space with a ? to signify "some character"
new TestCase("*bob?smith*", 1), // replace space and specify "contains"
new TestCase("bob\\ smith", 1), // attempt to escape the space
new TestCase("*smith", 2), //
new TestCase("*smith*", 2), //
//combined literal with wildcards
new TestCase("\"bob\"*", 2), //
new TestCase("*\"bob\"*", 2), //
new TestCase("*\"smith\"*", 2), //
new TestCase("\"bob\\ smith\"", 2), // escape space inside of a literal
};
Search("QueryString search with text", testCases, client, x => x.Name, QueryStringSearch);
Search("QueryString search with text + field data", testCases, client, x => x.NameWithFieldData, QueryStringSearch);
Search("QueryString search with text + field data + analyzer", testCases, client, x => x.NameWithFieldDataAndAnalyzer, QueryStringSearch);
Search("QueryString search By Keyword", testCases, client, x => x.NameWithKeyword, QueryStringSearch);
Search("Match search with text", testCases, client, x => x.Name, MatchSearch);
Search("Match search with text + field data", testCases, client, x => x.NameWithFieldData, MatchSearch);
Search("Match search with text + field data + analyzer", testCases, client, x => x.NameWithFieldDataAndAnalyzer, MatchSearch);
Search("Match search By Keyword", testCases, client, x => x.NameWithKeyword, MatchSearch);
//none of these work at all. commented to remove clutter
//Search("Term search with text", testCases, client, x => x.Name, TermSearch);
//Search("Term search with text + field data", testCases, client, x => x.NameWithFieldData, TermSearch);
//Search("Term search with text + field data + analyzer", testCases, client, x => x.NameWithFieldDataAndAnalyzer, TermSearch);
//Search("Term search By Keyword", testCases, client, x => x.NameWithKeyword, TermSearch);
}
private void Search(string name, IEnumerable<TestCase> cases, ElasticClient client, Expression<Func<Student, object>> field,
Action<ElasticClient, TestCase, Expression<Func<Student, object>>> func)
{
Terminal.Magenta(name);
foreach (var c in cases)
func(client, c, field);
}
private static ElasticClient GetClient()
{
var elasticSettings = new ConnectionSettings(new Uri("http://localhost:9200/"));
elasticSettings.DefaultIndex(index);
elasticSettings.EnableDebugMode();
elasticSettings.DisableDirectStreaming();
var client = new ElasticClient(elasticSettings);
return client;
}
private void Sanity(ElasticClient client)
{
var response = client.Search<Student>(a => a
.Query(q => q.MatchAll())
);
var hits = response.Hits.Count;
Terminal.Cyan($"Total records in index: {hits}");
}
private void QueryStringSearch(ElasticClient client, TestCase testCase, Expression<Func<Student, object>> field)
{
var query = Query<Student>.QueryString(qs => qs
.Fields(f => f.Field(field))
.Query(testCase.Term)
);
var response = client.Search<Student>(a => a.Query(_ => query));
var hits = response.Hits.Count;
var good = $"Query {testCase.Term,20} matches with {hits}";
var bad = $"Query {testCase.Term,20} returned {hits} instead of {testCase.ExpectedResults}";
Terminal.GoodBad(good, bad, hits == testCase.ExpectedResults);
}
private void MatchSearch(ElasticClient client, TestCase testCase, Expression<Func<Student, object>> field)
{
var query = Query<Student>.Match(m => m.Field(field).Query(testCase.Term));
var response = client.Search<Student>(a => a.Query(_ => query));
var hits = response.Hits.Count;
var good = $"Match {testCase.Term,20} matches with {hits}";
var bad = $"Match {testCase.Term,20} returned {hits} instead of {testCase.ExpectedResults}";
Terminal.GoodBad(good, bad, hits == testCase.ExpectedResults);
}
private void TermSearch(ElasticClient client, TestCase testCase, Expression<Func<Student, object>> field)
{
var query = Query<Student>.Term(field, testCase.Term);
var response = client.Search<Student>(a => a.Query(_ => query));
var hits = response.Hits.Count;
var good = $"Match {testCase.Term,20} matches with {hits}";
var bad = $"Match {testCase.Term,20} returned {hits} instead of {testCase.ExpectedResults}";
Terminal.GoodBad(good, bad, hits == testCase.ExpectedResults);
}
private static void Seed(ElasticClient client)
{
var data = new[]
{
new Student
{
Name = "bob smith",
},
new Student
{
Name = "bob jones",
},
new Student
{
Name = "john smith",
},
};
var indexResponse = client.IndexMany(data);
if (indexResponse.IsValid)
Terminal.Green("Indexed data successfully");
}
private static void RecreateIndex(ElasticClient client)
{
var sortAnalyzer = new CustomAnalyzer
{
Filter = new List<string>
{
"lowercase"
},
Tokenizer = "keyword"
};
var delResponse = client.DeleteIndex(indexName);
if (!delResponse.Acknowledged)
Terminal.Yellow($"Didnt need to delete index");
else
Terminal.Green($"Index {index} deleted");
var resp = client.CreateIndex(indexName, x => x
.Settings(z => z
.Analysis(ad => ad
.Analyzers(fdab => fdab
.Custom(SortAnalyzerName, _ => sortAnalyzer)
))
).Mappings(m => m
.Map<Student>(s => s.Properties(p => p
.Text(t => t.Name(z => z.Name))
.Text(t => t.Name(z => z.NameWithFieldData).Fielddata())
.Text(t => t.Name(z => z.NameWithFieldDataAndAnalyzer).Analyzer(SortAnalyzerName).Fielddata())
.Keyword(t => t.Name(z => z.NameWithKeyword))
))
));
if (!resp.Acknowledged)
{
Terminal.Red($"Failed to create index");
return;
}
Terminal.Green($"index {index} created");
//var mapping = client.GetMapping<Student>();
//Terminal.Blue(mapping.DebugInformation);
}
}
public class Student
{
public string Name { get; set; }
public string NameWithFieldData => Name;
public string NameWithFieldDataAndAnalyzer => Name;
public string NameWithKeyword => Name;
public List<StudentAddress> Addresses { get; set; } = new List<StudentAddress>();
}
public class StudentAddress
{
public string Line1 { get; set; }
public string Line2 { get; set; }
public string City { get; set; }
public string State { get; set; }
}
public class TestCase
{
public TestCase() { }
public TestCase(string query, int expectedResults)
{
Term = query;
ExpectedResults = expectedResults;
}
public string Term { get; set; }
public int ExpectedResults { get; set; }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment