Skip to content

Instantly share code, notes, and snippets.

@asmagin
Last active August 29, 2015 14:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save asmagin/85834218e66f0e9dc40c to your computer and use it in GitHub Desktop.
Save asmagin/85834218e66f0e9dc40c to your computer and use it in GitHub Desktop.
Sitecore Solr Provider: Spell Checking
public class CustomLinqToSolrIndex<TItem> : LinqToSolrIndex<TItem>
{
private readonly SolrSearchContext context;
private readonly string cultureCode;
/// <summary>
/// Initializes a new instance of the <see cref="CustomLinqToSolrIndex{TItem}" /> class.
/// </summary>
/// <param name="context">The context.</param>
/// <param name="executionContext">The execution context.</param>
public CustomLinqToSolrIndex(SolrSearchContext context, IExecutionContext executionContext)
: base(context, executionContext)
{
Assert.ArgumentNotNull(context, "context");
this.context = context;
var executionContext1 = this.Parameters.ExecutionContext as CultureExecutionContext;
var culture = executionContext1 == null ? CultureInfo.GetCultureInfo(Settings.DefaultLanguage) : executionContext1.Culture;
this.cultureCode = culture.TwoLetterISOLanguageName;
((SolrFieldNameTranslator)this.Parameters.FieldNameTranslator).AddCultureContext(culture);
}
/// <summary>
/// Executes the specified composite query.
/// </summary>
/// <typeparam name="TResult">The type of the result.</typeparam>
/// <param name="compositeQuery">The composite query.</param>
/// <returns></returns>
public TResult Execute<TResult>(ExtendedCompositeQuery compositeQuery)
{
if (!typeof(TResult).IsGenericType || typeof(TResult).GetGenericTypeDefinition() != typeof(ExtendedSearchResults<>))
{
return base.Execute<TResult>(compositeQuery);
}
var resultType = typeof(TResult).GetGenericArguments()[0];
var solrQueryResults = this.Execute(compositeQuery, resultType);
var type = typeof(SolrSearchResults<>).MakeGenericType(
new[]
{
resultType
});
var methodInfo = this.GetType().GetMethod("GetExtendedResults", BindingFlags.Instance | BindingFlags.NonPublic).MakeGenericMethod(typeof(TResult), resultType);
var selectMethod = this.GetSelectMethod(compositeQuery);
var instance = Activator.CreateInstance(
type,
new object[]
{
this.context,
solrQueryResults,
selectMethod,
compositeQuery.VirtualFieldProcessors
});
return (TResult)methodInfo.Invoke(this, new[] { compositeQuery, instance, solrQueryResults });
}
/// <summary>
/// Executes the specified composite query.
/// </summary>
/// <param name="compositeQuery">The composite query.</param>
/// <param name="resultType">Type of the result.</param>
/// <returns></returns>
internal SolrQueryResults<Dictionary<string, object>> Execute(ExtendedCompositeQuery compositeQuery, Type resultType)
{
var options = compositeQuery.QueryOptions;
if (compositeQuery.Methods != null)
{
var list1 = (compositeQuery.Methods).Where(m => m.MethodType == QueryMethodType.Select).Select(m => (SelectMethod)m).ToList();
if ((list1).Any())
{
foreach (var str in list1.SelectMany(selectMethod => (IEnumerable<string>)selectMethod.FieldNames))
{
options.Fields.Add(str.ToLowerInvariant());
}
if (!this.context.SecurityOptions.HasFlag(SearchSecurityOptions.DisableSecurityCheck))
{
options.Fields.Add("_uniqueid");
options.Fields.Add("_datasource");
}
}
var list2 = compositeQuery.Methods.Where(m => m.MethodType == QueryMethodType.GetResults).Select(m => (GetResultsMethod)m).ToList();
if (list2.Any())
{
if (options.Fields.Count > 0)
{
options.Fields.Add("score");
}
else
{
options.Fields.Add("*");
options.Fields.Add("score");
}
}
var list3 = compositeQuery.Methods.Where(m => m.MethodType == QueryMethodType.OrderBy).Select(m => (OrderByMethod)m).ToList();
if (list3.Any())
{
foreach (var orderByMethod in list3)
{
var field = orderByMethod.Field;
options.AddOrder(
new[]
{
new SortOrder(field, orderByMethod.SortDirection == SortDirection.Ascending ? Order.ASC : Order.DESC)
});
}
}
var list4 =
compositeQuery.Methods.Where(m => m.MethodType == QueryMethodType.Skip).Select(m => (SkipMethod)m).ToList();
if (list4.Any())
{
var num = list4.Sum(skipMethod => skipMethod.Count);
options.Start = num;
}
var list5 =
compositeQuery.Methods.Where(m => m.MethodType == QueryMethodType.Take).Select(m => (TakeMethod)m).ToList();
if (list5.Any())
{
var num = list5.Sum(takeMethod => takeMethod.Count);
options.Rows = num;
}
var list6 =
compositeQuery.Methods.Where(m => m.MethodType == QueryMethodType.Count).Select(m => (CountMethod)m).ToList();
if (compositeQuery.Methods.Count == 1 && list6.Any())
{
options.Rows = 0;
}
var list7 =
compositeQuery.Methods.Where(m => m.MethodType == QueryMethodType.Any).Select(m => (AnyMethod)m).ToList();
if (compositeQuery.Methods.Count == 1 && list7.Any())
{
options.Rows = 0;
}
var list8 =
compositeQuery.Methods.Where(m => m.MethodType == QueryMethodType.GetFacets).Select(m => (GetFacetsMethod)m).ToList();
if (compositeQuery.FacetQueries.Count > 0 && (list8.Any() || list2.Any()))
{
foreach (
var facetQuery in
GetFacetsPipeline.Run(
new GetFacetsArgs(
null,
compositeQuery.FacetQueries,
this.context.Index.Configuration.VirtualFieldProcessors,
this.context.Index.FieldNameTranslator)).FacetQueries.ToHashSet())
{
if (facetQuery.FieldNames.Any())
{
var minimumResultCount = facetQuery.MinimumResultCount;
if (facetQuery.FieldNames.Count() == 1)
{
var fieldNameTranslator = this.FieldNameTranslator as SolrFieldNameTranslator;
var str = facetQuery.FieldNames.First();
if (fieldNameTranslator != null && str == fieldNameTranslator.StripKnownExtensions(str) && this.context.Index.Configuration.FieldMap.GetFieldConfiguration(str) == null)
{
str = fieldNameTranslator.GetIndexFieldName(str.Replace("__", "!").Replace("_", " ").Replace("!", "__"), true);
}
var queryOptions = options;
var solrFacetQueryArray1 = new ISolrFacetQuery[1];
solrFacetQueryArray1[0] = new SolrFacetFieldQuery(str)
{
MinCount = minimumResultCount
};
var solrFacetQueryArray2 = solrFacetQueryArray1;
queryOptions.AddFacets(solrFacetQueryArray2);
}
if (facetQuery.FieldNames.Count() > 1)
{
var queryOptions = options;
var solrFacetQueryArray1 = new ISolrFacetQuery[1];
solrFacetQueryArray1[0] = new SolrFacetPivotQuery
{
Fields = new[]
{
string.Join(",", facetQuery.FieldNames)
},
MinCount = minimumResultCount
};
var solrFacetQueryArray2 = solrFacetQueryArray1;
queryOptions.AddFacets(solrFacetQueryArray2);
}
}
}
if (!list2.Any())
{
options.Rows = 0;
}
//var list9 =
// compositeQuery.Methods.Where(m => m.MethodType == QueryMethodType.Cast).Select(m => (GetSpellCheck)m).ToList();
//if (list9.Any())
//{
// options.Rows = 0;
// options.SpellCheck = new SpellCheckingParameters { Collate = true };
//}
}
}
if (compositeQuery.Filter != null)
{
options.AddFilterQueries(
new ISolrQuery[]
{
compositeQuery.Filter
});
}
options.AddFilterQueries(
new ISolrQuery[]
{
new SolrQueryByField("_indexname", this.context.Index.Name)
});
if (!Settings.DefaultLanguage.StartsWith(this.cultureCode))
{
var queryOptions = options;
var solrQueryArray1 = new ISolrQuery[1];
solrQueryArray1[0] = new SolrQueryByField("_language", this.cultureCode + "*")
{
Quoted = false
};
var solrQueryArray2 = solrQueryArray1;
queryOptions.AddFilterQueries(solrQueryArray2);
}
var loggingSerializer = new SolrLoggingSerializer();
var q = loggingSerializer.SerializeQuery(compositeQuery.Query);
try
{
if (!options.Rows.HasValue)
{
options.Rows = ContentSearchConfigurationSettings.SearchMaxResults;
}
SearchLog.Log.Info("Query - " + q);
SearchLog.Log.Info("Serialized Query - ?q=" + q + "&" + string.Join("&", loggingSerializer.GetAllParameters(options).Select(p => string.Format("{0}={1}", p.Key, p.Value)).ToArray()));
return this.SolrOperations.Query(q, options);
}
catch (Exception ex)
{
if (!(ex is SolrConnectionException) && !(ex is SolrNetException))
{
throw;
}
var message = ex.Message;
if (ex.Message.StartsWith("<?xml"))
{
var xmlDocument = new XmlDocument();
xmlDocument.LoadXml(ex.Message);
var xmlNode1 = xmlDocument.SelectSingleNode("/response/lst[@name='error'][1]/str[@name='msg'][1]");
var xmlNode2 = xmlDocument.SelectSingleNode("/response/lst[@name='responseHeader'][1]/lst[@name='params'][1]/str[@name='q'][1]");
if (xmlNode1 != null && xmlNode2 != null)
{
SearchLog.Log.Error(string.Format("Solr Error : [\"{0}\"] - Query attempted: [{1}]", xmlNode1.InnerText, xmlNode2.InnerText));
return new SolrQueryResults<Dictionary<string, object>>();
}
}
Log.Error(message, this);
return new SolrQueryResults<Dictionary<string, object>>();
}
}
/// <summary>
/// Gets the extended results.
/// </summary>
/// <typeparam name="TResult">The type of the result.</typeparam>
/// <typeparam name="TDocument">The type of the document.</typeparam>
/// <param name="compositeQuery">The composite query.</param>
/// <param name="processedResults">The processed results.</param>
/// <param name="results">The results.</param>
/// <returns></returns>
internal TResult GetExtendedResults<TResult, TDocument>(ExtendedCompositeQuery compositeQuery, SolrSearchResults<TDocument> processedResults, SolrQueryResults<Dictionary<string, object>> results)
{
var type = typeof(TResult);
var hits = processedResults.GetSearchHits();
var facetResults = this.FormatFacetResults(processedResults.GetFacets(), compositeQuery.FacetQueries);
var obj = Activator.CreateInstance(type, hits, processedResults.NumberFound, facetResults);
if (type.HasProperty("SpellCheckedResponse"))
{
var spellCheckPropetry = type.GetProperty("SpellCheckedResponse");
if (spellCheckPropetry != null && spellCheckPropetry.CanWrite)
{
spellCheckPropetry.SetValue(obj, results.SpellChecking.Collation);
}
}
if (type.HasProperty("SimilarResults"))
{
var similarResultsPropetry = type.GetProperty("SimilarResults");
if (similarResultsPropetry != null && similarResultsPropetry.CanWrite)
{
similarResultsPropetry.SetValue(obj, results.SimilarResults);
}
}
return (TResult)Convert.ChangeType(obj, typeof(TResult));
}
private SelectMethod GetSelectMethod(SolrCompositeQuery compositeQuery)
{
var type = this.GetType().BaseType;
var method = type.GetMethod("GetSelectMethod", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
try
{
return (SelectMethod)method.Invoke(this, new object[] { compositeQuery });
}
catch (Exception ex)
{
Log.Error("Signture of internal LinqToSolrIndex<TItem>.GetSelectMethod has changed or method not found", ex, this);
return null;
}
}
private FacetResults FormatFacetResults(Dictionary<string, ICollection<KeyValuePair<string, int>>> facetResults, List<FacetQuery> facetQueries)
{
var type = this.GetType().BaseType;
var method = type.GetMethod("FormatFacetResults", BindingFlags.NonPublic | BindingFlags.Instance);
try
{
return (FacetResults)method.Invoke(this, new object[] { facetResults, facetQueries });
}
catch (Exception ex)
{
Log.Error("Signture of internal LinqToSolrIndex<TItem>.FormatFacetResults has changed or method not found", ex, this);
return new FacetResults();
}
}
private ISolrOperations<Dictionary<string, object>> SolrOperations
{
get
{
var solrSearchIndex = this.context.Index as SolrSearchIndex;
if (solrSearchIndex != null)
{
return typeof(SolrSearchIndex)
.GetProperty("SolrOperations", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(solrSearchIndex) as ISolrOperations<Dictionary<string, object>>;
}
return null;
}
}
}
public class ExtendedCompositeQuery : SolrCompositeQuery
{
public QueryOptions QueryOptions { get; set; }
public ExtendedCompositeQuery(AbstractSolrQuery query, AbstractSolrQuery filterQuery, IEnumerable<Sitecore.ContentSearch.Linq.Methods.QueryMethod> methods, IEnumerable<IFieldQueryTranslator> virtualFieldProcessors, IEnumerable<FacetQuery> facetQueries, QueryOptions options)
: base(query, filterQuery, methods, virtualFieldProcessors, facetQueries)
{
this.QueryOptions = options;
}
}
public static string CheckSpelling<TSource>(this IQueryable<TSource> query, IProviderSearchContext context, string text = null)
{
var extendedQuery = (SolrCompositeQuery)((IHasNativeQuery)query).Query;
extendedQuery.Methods.Add(new GetResultsMethod(GetResultsOptions.Default));
var parameters = new SpellCheckingParameters { Collate = true };
if (!string.IsNullOrEmpty(text))
{
parameters.Query = text;
}
var newQuery = new ExtendedCompositeQuery(
extendedQuery.Query,
extendedQuery.Filter,
extendedQuery.Methods,
extendedQuery.VirtualFieldProcessors,
extendedQuery.FacetQueries,
new QueryOptions
{
SpellCheck = parameters,
Rows = 0
}
);
var linqToSolr = new CustomLinqToSolrIndex<TSource>((SolrSearchContext)context, null);
var response = linqToSolr.Execute<ExtendedSearchResults<TSource>>(newQuery);
return GetSpellCheckedString(response.SpellCheckedResponse);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment