PageRenderTime 41ms CodeModel.GetById 2ms app.highlight 32ms RepoModel.GetById 1ms app.codeStats 0ms

/ToMigrate/Raven.Database/Indexing/AnonymousObjectToLuceneDocumentConverter.cs

Relevant Search: With Applications for Solr and Elasticsearch

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