PageRenderTime 56ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/Raven.Database/Indexing/AnonymousObjectToLuceneDocumentConverter.cs

http://github.com/ravendb/ravendb
C# | 650 lines | 552 code | 75 blank | 23 comment | 185 complexity | 1842e2fabd7d18d02062b1671f7fc3df MD5 | raw file
Possible License(s): GPL-3.0, LGPL-2.1, MPL-2.0-no-copyleft-exception, Apache-2.0, BSD-3-Clause
  1. //-----------------------------------------------------------------------
  2. // <copyright file="AnonymousObjectToLuceneDocumentConverter.cs" company="Hibernating Rhinos LTD">
  3. // Copyright (c) Hibernating Rhinos LTD. All rights reserved.
  4. // </copyright>
  5. //-----------------------------------------------------------------------
  6. using System;
  7. using System.Collections;
  8. using System.Collections.Generic;
  9. using System.ComponentModel;
  10. using System.Globalization;
  11. using System.IO;
  12. using System.Linq;
  13. using Lucene.Net.Documents;
  14. using Lucene.Net.Search;
  15. using Raven.Abstractions.Extensions;
  16. using Raven.Abstractions.Logging;
  17. using Raven.Database.Linq;
  18. using Raven.Imports.Newtonsoft.Json;
  19. using Raven.Imports.Newtonsoft.Json.Linq;
  20. using Raven.Abstractions;
  21. using Raven.Abstractions.Data;
  22. using Raven.Abstractions.Indexing;
  23. using Raven.Abstractions.Linq;
  24. using Raven.Database.Extensions;
  25. using Raven.Json.Linq;
  26. using System.Runtime.CompilerServices;
  27. using Sparrow;
  28. namespace Raven.Database.Indexing
  29. {
  30. internal class AnonymousObjectToLuceneDocumentConverter
  31. {
  32. private readonly AbstractViewGenerator viewGenerator;
  33. private readonly ILog log;
  34. private readonly DocumentDatabase database;
  35. private readonly IndexDefinition indexDefinition;
  36. private readonly List<int> multipleItemsSameFieldCount = new List<int>();
  37. private readonly Dictionary<FieldCacheKey, Field> fieldsCache = new Dictionary<FieldCacheKey, Field>(Comparer);
  38. private readonly Dictionary<FieldCacheKey, NumericField> numericFieldsCache = new Dictionary<FieldCacheKey, NumericField>(Comparer);
  39. public AnonymousObjectToLuceneDocumentConverter(DocumentDatabase database, IndexDefinition indexDefinition, AbstractViewGenerator viewGenerator, ILog log)
  40. {
  41. this.database = database;
  42. this.indexDefinition = indexDefinition;
  43. this.viewGenerator = viewGenerator;
  44. this.log = log;
  45. }
  46. public IEnumerable<AbstractField> Index(object val, PropertyAccessor accessor, Field.Store defaultStorage)
  47. {
  48. return from property in accessor.Properies
  49. where property.Key != Constants.DocumentIdFieldName
  50. from field in CreateFields(property.Key, property.Value(val), defaultStorage)
  51. select field;
  52. }
  53. public IEnumerable<AbstractField> Index(RavenJObject document, Field.Store defaultStorage)
  54. {
  55. return from property in document
  56. where property.Key != Constants.DocumentIdFieldName
  57. from field in CreateFields(property.Key, GetPropertyValue(property.Value), defaultStorage)
  58. select field;
  59. }
  60. private static object GetPropertyValue(RavenJToken property)
  61. {
  62. switch (property.Type)
  63. {
  64. case JTokenType.Array:
  65. case JTokenType.Object:
  66. return property.ToString(Formatting.None);
  67. default:
  68. return property.Value<object>();
  69. }
  70. }
  71. /// <summary>
  72. /// This method generate the fields for indexing documents in lucene from the values.
  73. /// Given a name and a value, it has the following behavior:
  74. /// * If the value is enumerable, index all the items in the enumerable under the same field name
  75. /// * If the value is null, create a single field with the supplied name with the unanalyzed value 'NULL_VALUE'
  76. /// * If the value is string or was set to not analyzed, create a single field with the supplied name
  77. /// * If the value is date, create a single field with millisecond precision with the supplied name
  78. /// * If the value is numeric (int, long, double, decimal, or float) will create two fields:
  79. /// 1. with the supplied name, containing the numeric value as an unanalyzed string - useful for direct queries
  80. /// 2. with the name: name +'_Range', containing the numeric value in a form that allows range queries
  81. /// </summary>
  82. public IEnumerable<AbstractField> CreateFields(string name, object value, Field.Store defaultStorage, bool nestedArray = false, Field.TermVector defaultTermVector = Field.TermVector.NO, Field.Index? analyzed = null)
  83. {
  84. if (string.IsNullOrWhiteSpace(name))
  85. throw new ArgumentException("Field must be not null, not empty and cannot contain whitespace", "name");
  86. if (char.IsLetter(name[0]) == false && name[0] != '_')
  87. {
  88. name = "_" + name;
  89. }
  90. if (viewGenerator.IsSpatialField(name))
  91. return viewGenerator.GetSpatialField(name).CreateIndexableFields(value);
  92. return CreateRegularFields(name, value, defaultStorage, nestedArray, defaultTermVector, analyzed);
  93. }
  94. private IEnumerable<AbstractField> CreateRegularFields(string name, object value, Field.Store defaultStorage, bool nestedArray = false, Field.TermVector defaultTermVector = Field.TermVector.NO, Field.Index? analyzed = null)
  95. {
  96. var fieldIndexingOptions = analyzed ?? indexDefinition.GetIndex(name, null);
  97. var storage = indexDefinition.GetStorage(name, defaultStorage);
  98. var termVector = indexDefinition.GetTermVector(name, defaultTermVector);
  99. if (fieldIndexingOptions == Field.Index.NO && storage == Field.Store.NO && termVector == Field.TermVector.NO)
  100. {
  101. yield break;
  102. }
  103. if (fieldIndexingOptions == Field.Index.NO && storage == Field.Store.NO)
  104. {
  105. fieldIndexingOptions = Field.Index.ANALYZED; // we have some sort of term vector, forcing index to be analyzed, then.
  106. }
  107. if (value == null)
  108. {
  109. yield return CreateFieldWithCaching(name, Constants.NullValue, storage, Field.Index.NOT_ANALYZED_NO_NORMS, Field.TermVector.NO);
  110. yield break;
  111. }
  112. CheckIfSortOptionsAndInputTypeMatch(name, value);
  113. var attachmentFoIndexing = value as AttachmentForIndexing;
  114. if (attachmentFoIndexing != null)
  115. {
  116. if (database == null)
  117. throw new InvalidOperationException(
  118. "Cannot use attachment for indexing if the database parameter is null. This is probably a RavenDB bug");
  119. var attachment = database.Attachments.GetStatic(attachmentFoIndexing.Key);
  120. if (attachment == null)
  121. {
  122. yield break;
  123. }
  124. var fieldWithCaching = CreateFieldWithCaching(name, string.Empty, Field.Store.NO, fieldIndexingOptions, termVector);
  125. if (database.TransactionalStorage.IsAlreadyInBatch)
  126. {
  127. var streamReader = new StreamReader(attachment.Data());
  128. fieldWithCaching.SetValue(streamReader);
  129. }
  130. else
  131. {
  132. // we are not in batch operation so we have to create it be able to read attachment's data
  133. database.TransactionalStorage.Batch(accessor =>
  134. {
  135. var streamReader = new StreamReader(attachment.Data());
  136. // we have to read it into memory because we after exiting the batch an attachment's data stream will be closed
  137. fieldWithCaching.SetValue(streamReader.ReadToEnd());
  138. });
  139. }
  140. yield return fieldWithCaching;
  141. yield break;
  142. }
  143. if (Equals(value, string.Empty))
  144. {
  145. yield return CreateFieldWithCaching(name, Constants.EmptyString, storage,
  146. Field.Index.NOT_ANALYZED_NO_NORMS, Field.TermVector.NO);
  147. yield break;
  148. }
  149. var dynamicNullObject = value as DynamicNullObject;
  150. if (ReferenceEquals(dynamicNullObject, null) == false)
  151. {
  152. if (dynamicNullObject.IsExplicitNull)
  153. {
  154. var sortOptions = indexDefinition.GetSortOption(name, query: null);
  155. if (sortOptions == null ||
  156. sortOptions.Value == SortOptions.String ||
  157. sortOptions.Value == SortOptions.None ||
  158. sortOptions.Value == SortOptions.StringVal ||
  159. sortOptions.Value == SortOptions.Custom)
  160. {
  161. yield return CreateFieldWithCaching(name, Constants.NullValue, storage,
  162. Field.Index.NOT_ANALYZED_NO_NORMS, Field.TermVector.NO);
  163. }
  164. foreach (var field in CreateNumericFieldWithCaching(name, GetNullValueForSorting(sortOptions), storage, termVector))
  165. yield return field;
  166. }
  167. yield break;
  168. }
  169. var boostedValue = value as BoostedValue;
  170. if (boostedValue != null)
  171. {
  172. foreach (var field in CreateFields(name, boostedValue.Value, storage, false, termVector))
  173. {
  174. field.Boost = boostedValue.Boost;
  175. field.OmitNorms = false;
  176. yield return field;
  177. }
  178. yield break;
  179. }
  180. var abstractField = value as AbstractField;
  181. if (abstractField != null)
  182. {
  183. yield return abstractField;
  184. yield break;
  185. }
  186. var bytes = value as byte[];
  187. if (bytes != null)
  188. {
  189. yield return CreateBinaryFieldWithCaching(name, bytes, storage, fieldIndexingOptions, termVector);
  190. yield break;
  191. }
  192. var itemsToIndex = value as IEnumerable;
  193. if (itemsToIndex != null && ShouldTreatAsEnumerable(itemsToIndex))
  194. {
  195. int count = 1;
  196. if (nestedArray == false)
  197. yield return new Field(name + "_IsArray", "true", storage, Field.Index.NOT_ANALYZED_NO_NORMS, Field.TermVector.NO);
  198. foreach (var itemToIndex in itemsToIndex)
  199. {
  200. if (!CanCreateFieldsForNestedArray(itemToIndex, fieldIndexingOptions))
  201. continue;
  202. multipleItemsSameFieldCount.Add(count++);
  203. foreach (var field in CreateFields(name, itemToIndex, storage, nestedArray: true, defaultTermVector: defaultTermVector, analyzed: analyzed))
  204. yield return field;
  205. multipleItemsSameFieldCount.RemoveAt(multipleItemsSameFieldCount.Count - 1);
  206. }
  207. yield break;
  208. }
  209. if (Equals(fieldIndexingOptions, Field.Index.NOT_ANALYZED) ||
  210. Equals(fieldIndexingOptions, Field.Index.NOT_ANALYZED_NO_NORMS))// explicitly not analyzed
  211. {
  212. // date time, time span and date time offset have the same structure fo analyzed and not analyzed.
  213. if (!(value is DateTime) && !(value is DateTimeOffset) && !(value is TimeSpan))
  214. {
  215. yield return CreateFieldWithCaching(name, value.ToString(), storage,
  216. indexDefinition.GetIndex(name, Field.Index.NOT_ANALYZED_NO_NORMS), termVector);
  217. yield break;
  218. }
  219. }
  220. if (value is string)
  221. {
  222. var index = indexDefinition.GetIndex(name, Field.Index.ANALYZED);
  223. yield return CreateFieldWithCaching(name, value.ToString(), storage, index, termVector);
  224. yield break;
  225. }
  226. if (value is TimeSpan)
  227. {
  228. var val = (TimeSpan)value;
  229. yield return CreateFieldWithCaching(name, val.ToString("c", CultureInfo.InvariantCulture), storage,
  230. indexDefinition.GetIndex(name, Field.Index.NOT_ANALYZED_NO_NORMS), termVector);
  231. }
  232. else if (value is DateTime)
  233. {
  234. var val = (DateTime)value;
  235. var dateAsString = val.GetDefaultRavenFormat();
  236. if (val.Kind == DateTimeKind.Utc)
  237. dateAsString += "Z";
  238. yield return CreateFieldWithCaching(name, dateAsString, storage,
  239. indexDefinition.GetIndex(name, Field.Index.NOT_ANALYZED_NO_NORMS), termVector);
  240. }
  241. else if (value is DateTimeOffset)
  242. {
  243. var val = (DateTimeOffset)value;
  244. string dtoStr;
  245. if (Equals(fieldIndexingOptions, Field.Index.NOT_ANALYZED) || Equals(fieldIndexingOptions, Field.Index.NOT_ANALYZED_NO_NORMS))
  246. {
  247. dtoStr = val.ToString(Default.DateTimeOffsetFormatsToWrite, CultureInfo.InvariantCulture);
  248. }
  249. else
  250. {
  251. dtoStr = val.UtcDateTime.GetDefaultRavenFormat(true);
  252. }
  253. yield return CreateFieldWithCaching(name, dtoStr, storage,
  254. indexDefinition.GetIndex(name, Field.Index.NOT_ANALYZED_NO_NORMS), termVector);
  255. }
  256. else if (value is bool)
  257. {
  258. yield return new Field(name, ((bool)value) ? "true" : "false", storage,
  259. indexDefinition.GetIndex(name, Field.Index.NOT_ANALYZED_NO_NORMS), termVector);
  260. }
  261. else if (value is double)
  262. {
  263. var d = (double)value;
  264. yield return CreateFieldWithCaching(name, d.ToString("r", CultureInfo.InvariantCulture), storage,
  265. indexDefinition.GetIndex(name, Field.Index.NOT_ANALYZED_NO_NORMS), termVector);
  266. }
  267. else if (value is decimal)
  268. {
  269. var d = (decimal)value;
  270. var s = d.ToString(CultureInfo.InvariantCulture);
  271. if (s.Contains('.'))
  272. {
  273. s = s.TrimEnd('0');
  274. if (s.EndsWith("."))
  275. s = s.Substring(0, s.Length - 1);
  276. }
  277. yield return CreateFieldWithCaching(name, s, storage,
  278. indexDefinition.GetIndex(name, Field.Index.NOT_ANALYZED_NO_NORMS), termVector);
  279. }
  280. else if (value is Enum)
  281. {
  282. yield return CreateFieldWithCaching(name, value.ToString(), storage,
  283. indexDefinition.GetIndex(name, Field.Index.ANALYZED_NO_NORMS), termVector);
  284. }
  285. else if (value is IConvertible) // we need this to store numbers in invariant format, so JSON could read them
  286. {
  287. var convert = ((IConvertible)value);
  288. yield return CreateFieldWithCaching(name, convert.ToString(CultureInfo.InvariantCulture), storage,
  289. indexDefinition.GetIndex(name, Field.Index.NOT_ANALYZED_NO_NORMS), termVector);
  290. }
  291. else if (value is IDynamicJsonObject)
  292. {
  293. var inner = ((IDynamicJsonObject)value).Inner;
  294. yield return CreateFieldWithCaching(name + "_ConvertToJson", "true", Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS, Field.TermVector.NO);
  295. yield return CreateFieldWithCaching(name, inner.ToString(Formatting.None), storage,
  296. indexDefinition.GetIndex(name, Field.Index.NOT_ANALYZED_NO_NORMS), termVector);
  297. }
  298. else
  299. {
  300. var jsonVal = RavenJToken.FromObject(value).ToString(Formatting.None);
  301. if (jsonVal.StartsWith("{") || jsonVal.StartsWith("["))
  302. yield return CreateFieldWithCaching(name + "_ConvertToJson", "true", Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS, Field.TermVector.NO);
  303. else if (jsonVal.StartsWith("\"") && jsonVal.EndsWith("\"") && jsonVal.Length > 1)
  304. jsonVal = jsonVal.Substring(1, jsonVal.Length - 2);
  305. yield return CreateFieldWithCaching(name, jsonVal, storage,
  306. indexDefinition.GetIndex(name, Field.Index.NOT_ANALYZED_NO_NORMS), termVector);
  307. }
  308. foreach (var numericField in CreateNumericFieldWithCaching(name, value, storage, termVector))
  309. yield return numericField;
  310. }
  311. private void CheckIfSortOptionsAndInputTypeMatch(string name, object value)
  312. {
  313. if (log == null)
  314. return;
  315. if (value == null)
  316. return;
  317. var sortOption = indexDefinition.GetSortOption(name, null);
  318. if (sortOption.HasValue == false)
  319. return;
  320. switch (sortOption.Value)
  321. {
  322. case SortOptions.Double:
  323. case SortOptions.Float:
  324. case SortOptions.Int:
  325. case SortOptions.Long:
  326. case SortOptions.Short:
  327. if (value is int || value is short || value is double || value is long || value is float || value is decimal)
  328. return;
  329. log.Warn(string.Format("Field '{1}' in index '{0}' has numerical sorting enabled, but input value '{2}' was a '{3}'.", indexDefinition.Name, name, value, value.GetType()));
  330. break;
  331. default:
  332. return;
  333. }
  334. }
  335. private static object GetNullValueForSorting(SortOptions? sortOptions)
  336. {
  337. switch (sortOptions)
  338. {
  339. case SortOptions.Short:
  340. case SortOptions.Int:
  341. return int.MinValue;
  342. case SortOptions.Double:
  343. return double.MinValue;
  344. case SortOptions.Float:
  345. return float.MinValue;
  346. // ReSharper disable RedundantCaseLabel
  347. case SortOptions.Long:
  348. // to be able to sort on timestamps
  349. case SortOptions.String:
  350. case SortOptions.StringVal:
  351. case SortOptions.None:
  352. case SortOptions.Custom:
  353. // ReSharper restore RedundantCaseLabel
  354. default:
  355. return long.MinValue;
  356. }
  357. }
  358. private IEnumerable<AbstractField> CreateNumericFieldWithCaching(string name, object value, Field.Store defaultStorage, Field.TermVector termVector)
  359. {
  360. var fieldName = name + "_Range";
  361. var storage = indexDefinition.GetStorage(name, defaultStorage);
  362. var cacheKey = new FieldCacheKey(name, null, storage, termVector, multipleItemsSameFieldCount.ToArray());
  363. NumericField numericField;
  364. if (numericFieldsCache.TryGetValue(cacheKey, out numericField) == false)
  365. {
  366. numericFieldsCache[cacheKey] = numericField = new NumericField(fieldName, storage, true);
  367. }
  368. if (value is TimeSpan)
  369. {
  370. yield return numericField.SetLongValue(((TimeSpan)value).Ticks);
  371. }
  372. else if (value is int)
  373. {
  374. var sortOption = indexDefinition.GetSortOption(name, query: null);
  375. if (sortOption == SortOptions.Long)
  376. yield return numericField.SetLongValue((int)value);
  377. else if (sortOption == SortOptions.Float)
  378. yield return numericField.SetFloatValue((int)value);
  379. else if (sortOption == SortOptions.Double)
  380. yield return numericField.SetDoubleValue((int)value);
  381. else
  382. yield return numericField.SetIntValue((int)value);
  383. }
  384. else if (value is long)
  385. {
  386. var sortOption = indexDefinition.GetSortOption(name, query: null);
  387. if (sortOption == SortOptions.Double)
  388. yield return numericField.SetDoubleValue((long)value);
  389. else if (sortOption == SortOptions.Float)
  390. yield return numericField.SetFloatValue((long)value);
  391. else if (sortOption == SortOptions.Int)
  392. yield return numericField.SetIntValue(Convert.ToInt32((long)value));
  393. else
  394. yield return numericField.SetLongValue((long)value);
  395. }
  396. else if (value is decimal)
  397. {
  398. var sortOption = indexDefinition.GetSortOption(name, query: null);
  399. if (sortOption == SortOptions.Float)
  400. yield return numericField.SetFloatValue(Convert.ToSingle((decimal)value));
  401. else if (sortOption == SortOptions.Int)
  402. yield return numericField.SetIntValue(Convert.ToInt32((decimal)value));
  403. else if (sortOption == SortOptions.Long)
  404. yield return numericField.SetLongValue(Convert.ToInt64((decimal)value));
  405. else
  406. yield return numericField.SetDoubleValue((double)(decimal)value);
  407. }
  408. else if (value is float)
  409. {
  410. var sortOption = indexDefinition.GetSortOption(name, query: null);
  411. if (sortOption == SortOptions.Double)
  412. yield return numericField.SetDoubleValue((float)value);
  413. else if (sortOption == SortOptions.Int)
  414. yield return numericField.SetIntValue(Convert.ToInt32((float)value));
  415. else if (sortOption == SortOptions.Long)
  416. yield return numericField.SetLongValue(Convert.ToInt64((float)value));
  417. else
  418. yield return numericField.SetFloatValue((float)value);
  419. }
  420. else if (value is double)
  421. {
  422. var sortOption = indexDefinition.GetSortOption(name, query: null);
  423. if (sortOption == SortOptions.Float)
  424. yield return numericField.SetFloatValue(Convert.ToSingle((double)value));
  425. else if (sortOption == SortOptions.Int)
  426. yield return numericField.SetIntValue(Convert.ToInt32((double)value));
  427. else if (sortOption == SortOptions.Long)
  428. yield return numericField.SetLongValue(Convert.ToInt64((double)value));
  429. else
  430. yield return numericField.SetDoubleValue((double)value);
  431. }
  432. }
  433. public static bool ShouldTreatAsEnumerable(object itemsToIndex)
  434. {
  435. if (itemsToIndex == null)
  436. return false;
  437. if (itemsToIndex is DynamicJsonObject)
  438. return false;
  439. if (itemsToIndex is string)
  440. return false;
  441. if (itemsToIndex is RavenJObject)
  442. return false;
  443. if (itemsToIndex is IDictionary)
  444. return false;
  445. return true;
  446. }
  447. private Field CreateBinaryFieldWithCaching(string name, byte[] value, Field.Store store, Field.Index index, Field.TermVector termVector)
  448. {
  449. if (value.Length > 1024)
  450. throw new ArgumentException("Binary values must be smaller than 1Kb");
  451. var cacheKey = new FieldCacheKey(name, null, store, termVector, multipleItemsSameFieldCount.ToArray());
  452. Field field;
  453. var stringWriter = new StringWriter();
  454. JsonExtensions.CreateDefaultJsonSerializer().Serialize(stringWriter, value);
  455. var sb = stringWriter.GetStringBuilder();
  456. sb.Remove(0, 1); // remove prefix "
  457. sb.Remove(sb.Length - 1, 1); // remove postfix "
  458. var val = sb.ToString();
  459. if (fieldsCache.TryGetValue(cacheKey, out field) == false)
  460. {
  461. fieldsCache[cacheKey] = field = new Field(name, val, store, index, termVector);
  462. }
  463. field.SetValue(val);
  464. field.Boost = 1;
  465. field.OmitNorms = true;
  466. return field;
  467. }
  468. private static FieldCacheKeyEqualityComparer Comparer = new FieldCacheKeyEqualityComparer();
  469. private class FieldCacheKeyEqualityComparer : IEqualityComparer<FieldCacheKey>
  470. {
  471. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  472. public bool Equals(FieldCacheKey x, FieldCacheKey y)
  473. {
  474. if (x.HashKey != y.HashKey)
  475. {
  476. return false;
  477. }
  478. else // We are thinking it is possible to have collisions. This may not be true ever!
  479. {
  480. if ( x.index == y.index &&
  481. x.store == y.store &&
  482. x.termVector == y.termVector &&
  483. string.Equals(x.name, y.name))
  484. {
  485. if (x.multipleItemsSameField.Length != y.multipleItemsSameField.Length)
  486. return false;
  487. int count = x.multipleItemsSameField.Length;
  488. for ( int i = 0; i < count; i++ )
  489. {
  490. if (x.multipleItemsSameField[i] != y.multipleItemsSameField[i])
  491. return false;
  492. }
  493. return true;
  494. }
  495. else return false;
  496. }
  497. }
  498. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  499. public int GetHashCode(FieldCacheKey obj)
  500. {
  501. return obj.HashKey;
  502. }
  503. }
  504. private class FieldCacheKey
  505. {
  506. internal readonly string name;
  507. internal readonly Field.Index? index;
  508. internal readonly Field.Store store;
  509. internal readonly Field.TermVector termVector;
  510. internal readonly int[] multipleItemsSameField;
  511. private int _hashKey;
  512. // We can precalculate the hash code because all fields involved are readonly.
  513. internal int HashKey
  514. {
  515. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  516. get
  517. {
  518. if (_hashKey == 0)
  519. {
  520. unsafe
  521. {
  522. int nameHash = (name != null ? name.GetHashCode() : 0);
  523. int fieldHash = (index != null ? (byte)index : -1) << 16 | ((byte)store << 8) | (byte)termVector;
  524. int hash = Hashing.CombineInline(nameHash, fieldHash);
  525. if (multipleItemsSameField.Length > 0)
  526. {
  527. fixed (int* buffer = multipleItemsSameField)
  528. {
  529. _hashKey = (int)Hashing.XXHash32.CalculateInline((byte*)buffer, multipleItemsSameField.Length * sizeof(int), (uint)hash);
  530. }
  531. }
  532. else _hashKey = hash;
  533. }
  534. }
  535. return _hashKey;
  536. }
  537. }
  538. public FieldCacheKey(string name, Field.Index? index, Field.Store store, Field.TermVector termVector, int[] multipleItemsSameField)
  539. {
  540. this.name = name;
  541. this.index = index;
  542. this.store = store;
  543. this.termVector = termVector;
  544. this.multipleItemsSameField = multipleItemsSameField;
  545. }
  546. public override int GetHashCode()
  547. {
  548. return HashKey;
  549. }
  550. }
  551. private Field CreateFieldWithCaching(string name, string value, Field.Store store, Field.Index index, Field.TermVector termVector)
  552. {
  553. var cacheKey = new FieldCacheKey(name, index, store, termVector, multipleItemsSameFieldCount.ToArray());
  554. Field field;
  555. if (fieldsCache.TryGetValue(cacheKey, out field) == false)
  556. fieldsCache[cacheKey] = field = new Field(name, value, store, index, termVector);
  557. field.SetValue(value);
  558. field.Boost = 1;
  559. field.OmitNorms = true;
  560. return field;
  561. }
  562. private bool CanCreateFieldsForNestedArray(object value, Field.Index fieldIndexingOptions)
  563. {
  564. if (!fieldIndexingOptions.IsAnalyzed())
  565. {
  566. return true;
  567. }
  568. if (value == null || value is DynamicNullObject)
  569. {
  570. return false;
  571. }
  572. return true;
  573. }
  574. }
  575. }