PageRenderTime 28ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

/Raven.Database/Indexing/AnonymousObjectToLuceneDocumentConverter.cs

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