Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
FullTextSearchModule.cs
1using System;
3using System.Globalization;
4using System.IO;
5using System.Reflection;
6using System.Text;
7using System.Threading.Tasks;
8using Waher.Events;
22
24{
28 [ModuleDependency(typeof(DatabaseModule))]
30 {
31 private static readonly MultiReadSingleWriteObject synchObj = new MultiReadSingleWriteObject(typeof(FullTextSearchModule), false);
32 private static Cache<string, QueryRecord> queryCache;
33 private static Dictionary<string, bool> stopWords = new Dictionary<string, bool>();
34 private static IPersistentDictionary collectionInformation;
35 private static Dictionary<string, CollectionInformation> collections;
36 private static Dictionary<string, IPersistentDictionary> indices;
37 private static Dictionary<Type, TypeInformation> types;
38 private static FullTextSearchModule instance = null;
39
44 {
45 }
46
50 public async Task Start()
51 {
52 collectionInformation = await Database.GetDictionary("FullTextSearchCollections");
53 collections = new Dictionary<string, CollectionInformation>();
54 indices = new Dictionary<string, IPersistentDictionary>();
55 types = new Dictionary<Type, TypeInformation>();
56 queryCache = new Cache<string, QueryRecord>(int.MaxValue, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
57
58 instance = this;
59
60 Database.ObjectInserted += this.Database_ObjectInserted;
61 Database.ObjectUpdated += this.Database_ObjectUpdated;
62 Database.ObjectDeleted += this.Database_ObjectDeleted;
63 Database.CollectionCleared += this.Database_CollectionCleared;
64
65 Types.OnInvalidated += this.Types_OnInvalidated;
66 }
67
71 public async Task Stop()
72 {
73 Database.ObjectInserted -= this.Database_ObjectInserted;
74 Database.ObjectUpdated -= this.Database_ObjectUpdated;
75 Database.ObjectDeleted -= this.Database_ObjectDeleted;
76 Database.CollectionCleared -= this.Database_CollectionCleared;
77
78 Types.OnInvalidated -= this.Types_OnInvalidated;
79
80 // TODO: Wait for current objects to be finished.
81
82 await synchObj.BeginWrite();
83 try
84 {
85 queryCache?.Dispose();
86 queryCache = null;
87
88 if (!(indices is null))
89 {
90 foreach (IPersistentDictionary Index in indices.Values)
91 Index.Dispose();
92
93 indices.Clear();
94 indices = null;
95 }
96
97 collectionInformation?.Dispose();
98 collectionInformation = null;
99
100 collections?.Clear();
101 collections = null;
102
103 types?.Clear();
104 types = null;
105
106 }
107 finally
108 {
109 await synchObj.EndWrite();
110 instance = null;
111 }
112 }
113
114 private void Database_ObjectInserted(object Sender, ObjectEventArgs e)
115 {
116 Task.Run(() => this.ObjectInserted(e));
117 }
118
119 private async Task ObjectInserted(ObjectEventArgs e)
120 {
121 try
122 {
123 Tuple<CollectionInformation, TypeInformation, GenericObject> P = await Prepare(e.Object);
124 if (P is null)
125 return;
126
127 object ObjectId = await Database.TryGetObjectId(e.Object);
128 if (ObjectId is null)
129 return;
130
131 CollectionInformation CollectionInfo = P.Item1;
132 TypeInformation TypeInfo = P.Item2;
133 GenericObject GenObj = P.Item3;
134 TokenCount[] Tokens;
135 string IndexName;
136
137 if (GenObj is null)
138 {
139 IndexName = TypeInfo.GetIndexCollection(e.Object);
140 Tokens = await TypeInfo.Tokenize(e.Object, CollectionInfo.Properties);
141 }
142 else
143 {
144 IndexName = CollectionInfo.IndexCollectionName;
145 Tokens = await Tokenize(GenObj, CollectionInfo.Properties);
146 }
147
148 if (Tokens is null || Tokens.Length == 0)
149 return;
150
151 ObjectReference Ref;
152
153 await synchObj.BeginWrite();
154 try
155 {
156 ulong Index = await GetNextIndexNrLocked(IndexName);
157
158 Ref = new ObjectReference()
159 {
160 IndexCollection = IndexName,
161 Collection = CollectionInfo.CollectionName,
162 ObjectInstanceId = ObjectId,
163 Index = Index,
164 Tokens = Tokens,
165 Indexed = DateTime.UtcNow
166 };
167
168 await AddTokensToIndexLocked(Ref);
169 await Database.Insert(Ref);
170 }
171 finally
172 {
173 await synchObj.EndWrite();
174 }
175
176 queryCache?.Clear();
177
178 await Search.RaiseObjectAddedToIndex(this, new ObjectReferenceEventArgs(Ref));
179 }
180 catch (Exception ex)
181 {
182 Log.Exception(ex);
183 }
184 }
185
186 private static async Task<IPersistentDictionary> GetIndexLocked(string IndexCollection, bool CreateIfNotFound)
187 {
188 if (indices.TryGetValue(IndexCollection, out IPersistentDictionary Result))
189 return Result;
190
191 if (CreateIfNotFound)
192 {
193 Result = await Database.GetDictionary(IndexCollection);
194 indices[IndexCollection] = Result;
195 }
196
197 return Result;
198 }
199
200 private static async Task AddTokensToIndexLocked(ObjectReference Ref)
201 {
202 DateTime TP = DateTime.UtcNow;
203 IPersistentDictionary Index = await GetIndexLocked(Ref.IndexCollection, true);
204
205 foreach (TokenCount Token in Ref.Tokens)
206 {
207 KeyValuePair<bool, object> P = await Index.TryGetValueAsync(Token.Token);
208 int c;
209
210 if (!P.Key || !(P.Value is TokenReferences References))
211 {
212 References = new TokenReferences()
213 {
214 LastBlock = 0,
215 ObjectReferences = new ulong[] { Ref.Index },
216 Counts = new uint[] { (uint)Token.DocIndex.Length },
217 Timestamps = new DateTime[] { TP }
218 };
219
220 await Index.AddAsync(Token.Token, References, true);
221 }
222 else if ((c = References.ObjectReferences.Length) < TokenReferences.MaxReferences)
223 {
224 ulong[] NewReferences = new ulong[c + 1];
225 uint[] NewCounts = new uint[c + 1];
226 DateTime[] NewTimestamps = new DateTime[c + 1];
227
228 Array.Copy(References.ObjectReferences, 0, NewReferences, 0, c);
229 Array.Copy(References.Counts, 0, NewCounts, 0, c);
230 Array.Copy(References.Timestamps, 0, NewTimestamps, 0, c);
231
232 NewReferences[c] = Ref.Index;
233 NewCounts[c] = (uint)Token.DocIndex.Length;
234 NewTimestamps[c] = TP;
235
236 References.ObjectReferences = NewReferences;
237 References.Counts = NewCounts;
238 References.Timestamps = NewTimestamps;
239
240 await Index.AddAsync(Token.Token, References, true);
241 }
242 else
243 {
244 References.LastBlock++;
245
246 TokenReferences NewBlock = new TokenReferences()
247 {
248 LastBlock = 0,
249 Counts = References.Counts,
250 ObjectReferences = References.ObjectReferences,
251 Timestamps = References.Timestamps
252 };
253
254 await Index.AddAsync(Token.Token + " " + References.LastBlock.ToString(), NewBlock, true);
255
256 References.ObjectReferences = new ulong[] { Ref.Index };
257 References.Counts = new uint[] { (uint)Token.DocIndex.Length };
258 References.Timestamps = new DateTime[] { TP };
259
260 await Index.AddAsync(Token.Token, References, true);
261 }
262
263 Token.Block = References.LastBlock + 1;
264 }
265 }
266
267 private static async Task<ulong> GetNextIndexNrLocked(string IndexedCollection)
268 {
269 if (collectionInformation is null)
270 throw new ObjectDisposedException(nameof(FullTextSearchModule));
271
272 string Key = " C(" + IndexedCollection + ")";
273 KeyValuePair<bool, object> P = await collectionInformation.TryGetValueAsync(Key);
274
275 if (!P.Key || !(P.Value is ulong Nr))
276 Nr = 0;
277
278 Nr++;
279
280 await collectionInformation.AddAsync(Key, Nr, true);
281
282 return Nr;
283 }
284
285 private static Task<CollectionInformation> GetCollectionInfoLocked(string CollectionName, bool CreateIfNotExists)
286 {
287 return GetCollectionInfoLocked(CollectionName, CollectionName, CreateIfNotExists);
288 }
289
290 private static async Task<CollectionInformation> GetCollectionInfoLocked(
291 string IndexCollectionName, string CollectionName, bool CreateIfNotExists)
292 {
293 if (collections is null)
294 {
295 if (CreateIfNotExists)
296 throw new NotSupportedException("Service not initialized or shut down.");
297 else
298 return null;
299 }
300
301 if (collections.TryGetValue(CollectionName, out CollectionInformation Result))
302 return Result;
303
304 KeyValuePair<bool, object> P = await collectionInformation.TryGetValueAsync(CollectionName);
305 if (P.Key && P.Value is CollectionInformation Result2)
306 {
307 collections[CollectionName] = Result2;
308 return Result2;
309 }
310
311 if (!CreateIfNotExists)
312 return null;
313
314 Result = new CollectionInformation(IndexCollectionName, CollectionName, false);
315 collections[CollectionName] = Result;
316 await collectionInformation.AddAsync(CollectionName, Result, true);
317
318 return Result;
319 }
320
325 public static async Task<Dictionary<string, string[]>> GetCollectionNames()
326 {
327 Dictionary<string, ChunkedList<string>> ByIndex = new Dictionary<string, ChunkedList<string>>();
328
329 await synchObj.BeginRead();
330 try
331 {
332 object[] Values = await collectionInformation.GetValuesAsync();
333
334 foreach (object Obj in Values)
335 {
336 if (Obj is CollectionInformation Info &&
338 !string.IsNullOrEmpty(Info.IndexCollectionName))
339 {
340 if (!ByIndex.TryGetValue(Info.IndexCollectionName, out ChunkedList<string> Collections))
341 {
342 Collections = new ChunkedList<string>();
343 ByIndex[Info.IndexCollectionName] = Collections;
344 }
345
346 Collections.Add(Info.CollectionName);
347 }
348 }
349 }
350 finally
351 {
352 await synchObj.EndRead();
353 }
354
355 Dictionary<string, string[]> Result = new Dictionary<string, string[]>();
356
357 foreach (KeyValuePair<string, ChunkedList<string>> Rec in ByIndex)
358 Result[Rec.Key] = Rec.Value.ToArray();
359
360 return Result;
361 }
362
369 public static async Task<string[]> GetCollectionNames(string IndexCollectionName)
370 {
371 await synchObj.BeginRead();
372 try
373 {
374 return await GetCollectionNamesLocked(IndexCollectionName);
375 }
376 finally
377 {
378 await synchObj.EndRead();
379 }
380 }
381
388 private static async Task<string[]> GetCollectionNamesLocked(string IndexCollectionName)
389 {
391
392 foreach (object Obj in await collectionInformation.GetValuesAsync())
393 {
394 if (Obj is CollectionInformation Info && Info.IndexForFullTextSearch)
395 {
396 if (Info.IndexCollectionName == IndexCollectionName)
397 Result.Add(Info.CollectionName);
398 }
399 }
400
401 return Result.ToArray();
402 }
403
410 internal static async Task<bool> SetFullTextSearchIndexCollection(string IndexCollection, string CollectionName)
411 {
412 await synchObj.BeginWrite();
413 try
414 {
415 CollectionInformation Info = await GetCollectionInfoLocked(IndexCollection, CollectionName, false);
416 bool Created;
417
418 if (Info is null)
419 {
420 Created = true;
421 Info = await GetCollectionInfoLocked(IndexCollection, CollectionName, true);
422 }
423 else
424 Created = false;
425
426 if (Info.IndexCollectionName != IndexCollection)
427 {
428 Info.IndexCollectionName = IndexCollection;
429 await collectionInformation.AddAsync(Info.CollectionName, Info, true);
430
431 return true;
432 }
433 else
434 return Created;
435 }
436 finally
437 {
438 await synchObj.EndWrite();
439 }
440 }
441
448 internal static async Task<bool> AddFullTextSearch(string CollectionName, params PropertyDefinition[] Properties)
449 {
450 await synchObj.BeginWrite();
451 try
452 {
453 CollectionInformation Info = await GetCollectionInfoLocked(CollectionName, true);
454
455 if (Info.AddIndexableProperties(Properties))
456 {
457 await collectionInformation.AddAsync(Info.CollectionName, Info, true);
458 return true;
459 }
460 else
461 return false;
462 }
463 finally
464 {
465 await synchObj.EndWrite();
466 }
467 }
468
475 internal static async Task<bool> RemoveFullTextSearch(string CollectionName, params PropertyDefinition[] Properties)
476 {
477 await synchObj.BeginWrite();
478 try
479 {
480 CollectionInformation Info = await GetCollectionInfoLocked(CollectionName, true);
481
482 if (Info.RemoveIndexableProperties(Properties))
483 {
484 await collectionInformation.AddAsync(Info.CollectionName, Info, true);
485 return true;
486 }
487 else
488 return false;
489 }
490 finally
491 {
492 await synchObj.EndWrite();
493 }
494 }
495
500 internal static async Task<Dictionary<string, PropertyDefinition[]>> GetFullTextSearchIndexedProperties()
501 {
502 Dictionary<string, PropertyDefinition[]> Result = new Dictionary<string, PropertyDefinition[]>();
503
504 await synchObj.BeginRead();
505 try
506 {
507 foreach (object Obj in await collectionInformation.GetValuesAsync())
508 {
509 if (Obj is CollectionInformation Info && Info.IndexForFullTextSearch)
510 Result[Info.CollectionName] = Info.Properties;
511 }
512 }
513 finally
514 {
515 await synchObj.EndRead();
516 }
517
518 return Result;
519 }
520
526 internal static async Task<PropertyDefinition[]> GetFullTextSearchIndexedProperties(string CollectionName)
527 {
528 await synchObj.BeginRead();
529 try
530 {
531 CollectionInformation Info = await GetCollectionInfoLocked(CollectionName, false);
532
533 if (Info is null || !Info.IndexForFullTextSearch)
534 return Array.Empty<PropertyDefinition>();
535 else
536 return (PropertyDefinition[])Info.Properties.Clone();
537 }
538 finally
539 {
540 await synchObj.EndRead();
541 }
542 }
543
544 private static async Task<Tuple<CollectionInformation, TypeInformation, GenericObject>> Prepare(object Object)
545 {
546 Object = await ScriptNode.WaitPossibleTask(Object);
547
548 await synchObj.BeginWrite();
549 try
550 {
551 if (Object is GenericObject GenObj)
552 return await PrepareLocked(GenObj);
553 else
554 return await PrepareLocked(Object.GetType(), Object);
555 }
556 finally
557 {
558 await synchObj.EndWrite();
559 }
560 }
561
562 private static async Task<Tuple<CollectionInformation, TypeInformation, GenericObject>> PrepareLocked(GenericObject GenObj)
563 {
564 CollectionInformation CollectionInfo = await GetCollectionInfoLocked(GenObj.CollectionName, true);
565
566 if (CollectionInfo.IndexForFullTextSearch)
567 return new Tuple<CollectionInformation, TypeInformation, GenericObject>(CollectionInfo, null, GenObj);
568 else
569 return null;
570 }
571
572 private static async Task<TypeInformation> GetTypeInfoLocked(Type T, object Instance)
573 {
574 if (types is null)
575 throw new Exception("Full text search module not started, or in the process of being stopped.");
576
577 if (types.TryGetValue(T, out TypeInformation Result))
578 return Result;
579
580 TypeInfo TI = T.GetTypeInfo();
581 IEnumerable<FullTextSearchAttribute> SearchAttrs = TI.GetCustomAttributes<FullTextSearchAttribute>(true);
582 CollectionNameAttribute CollectionAttr = TI.GetCustomAttribute<CollectionNameAttribute>(true);
583 ITokenizer CustomTokenizer = Types.FindBest<ITokenizer, Type>(T);
584
585 if (CollectionAttr is null)
586 Result = new TypeInformation(T, TI, null, null, CustomTokenizer, null);
587 else
588 {
589 string CollectionName = CollectionAttr.Name;
590 bool DynamicIndex = false;
591 string IndexName;
592
593 if (!(SearchAttrs is null))
594 {
595 foreach (FullTextSearchAttribute Attribute in SearchAttrs)
596 {
597 if (Attribute.DynamicIndexCollection)
598 {
599 DynamicIndex = true;
600 break;
601 }
602 }
603 }
604
605 if (DynamicIndex)
606 IndexName = null;
607 else
608 {
609 IndexName = CollectionName;
610 foreach (FullTextSearchAttribute Attribute in SearchAttrs)
611 {
612 IndexName = Attribute.GetIndexCollection(Instance);
613 break;
614 }
615 }
616
617 CollectionInformation Info = await GetCollectionInfoLocked(IndexName, CollectionName, true);
618
619 Result = new TypeInformation(T, TI, CollectionName, Info, CustomTokenizer, SearchAttrs);
620
621 if (Result.HasPropertyDefinitions && Info.AddIndexableProperties(Result.Properties))
622 await collectionInformation.AddAsync(CollectionName, Info, true);
623 else if (!(CustomTokenizer is null) && !Info.IndexForFullTextSearch)
624 {
625 Info.IndexForFullTextSearch = true;
626 await collectionInformation.AddAsync(CollectionName, Info, true);
627 }
628 }
629
630 types[T] = Result;
631
632 return Result;
633 }
634
635 private static async Task<Tuple<CollectionInformation, TypeInformation, GenericObject>> PrepareLocked(Type T, object Instance)
636 {
637 TypeInformation TypeInfo = await GetTypeInfoLocked(T, Instance);
638 if (!TypeInfo.HasCollection)
639 return null;
640
641 if (!TypeInfo.CollectionInformation?.IndexForFullTextSearch ?? false)
642 return null;
643
644 return new Tuple<CollectionInformation, TypeInformation, GenericObject>(TypeInfo.CollectionInformation, TypeInfo, null);
645 }
646
655 internal static Keyword[] ParseKeywords(string Search, bool TreatKeywordsAsPrefixes)
656 {
657 return ParseKeywords(Search, TreatKeywordsAsPrefixes, true);
658 }
659
669 private static Keyword[] ParseKeywords(string Search, bool TreatKeywordsAsPrefixes,
670 bool ParseQuotes)
671 {
673 StringBuilder sb = new StringBuilder();
674 bool First = true;
675 bool Required = false;
676 bool Prohibited = false;
677 string Wildcard = null;
678 int Type = 0;
680 string Token;
681
682 foreach (char ch in Search.ToLower().Normalize(NormalizationForm.FormD))
683 {
684 UnicodeCategory Category = CharUnicodeInfo.GetUnicodeCategory(ch);
685 if (Category == UnicodeCategory.NonSpacingMark)
686 continue;
687
688 if (char.IsLetterOrDigit(ch))
689 {
690 sb.Append(ch);
691 First = false;
692 }
693 else if (Type == 2)
694 {
695 if (ch == '/')
696 {
697 Token = sb.ToString();
698 sb.Clear();
699 First = true;
700 Type = 0;
701
702 Add(new RegexKeyword(Token), Result, ref Required, ref Prohibited);
703 }
704 else
705 {
706 sb.Append(ch);
707 First = false;
708 }
709 }
710 else if (Type == 3)
711 {
712 if (ch == '"')
713 {
714 Token = sb.ToString();
715 sb.Clear();
716 First = true;
717 Type = 0;
718
719 Add(new SequenceOfKeywords(ParseKeywords(Token, false)),
720 Result, ref Required, ref Prohibited);
721 }
722 else
723 sb.Append(ch);
724 }
725 else if (Type == 4)
726 {
727 if (ch == '\'')
728 {
729 Token = sb.ToString();
730 sb.Clear();
731 First = true;
732 Type = 0;
733
734 Add(new SequenceOfKeywords(ParseKeywords(Token, false)),
735 Result, ref Required, ref Prohibited);
736 }
737 else
738 sb.Append(ch);
739 }
740 else if (Type == 0 && (ch == '*' || ch == '%' || ch == '¤' || ch == '#'))
741 {
742 sb.Append(ch);
743 Type = 1;
744 Wildcard = new string(ch, 1);
745 }
746 else
747 {
748 if (!First)
749 {
750 Token = sb.ToString();
751 sb.Clear();
752 First = true;
753
754 if (Type == 1)
755 {
756 Keyword = new WildcardKeyword(Token, Wildcard);
757 Wildcard = null;
758 }
759 else if (TreatKeywordsAsPrefixes)
760 Keyword = new WildcardKeyword(Token);
761 else
762 Keyword = new PlainKeyword(Token);
763
764 Add(Keyword, Result, ref Required, ref Prohibited);
765 Type = 0;
766 }
767
768 if (ch == '+')
769 {
770 Required = true;
771 Prohibited = false;
772 }
773 else if (ch == '-')
774 {
775 Prohibited = true;
776 Required = false;
777 }
778 else if (ch == '/')
779 Type = 2;
780 else if (ch == '"' && ParseQuotes)
781 Type = 3;
782 else if (ch == '\'' && ParseQuotes)
783 Type = 4;
784 }
785 }
786
787 if (!First)
788 {
789 Token = sb.ToString();
790 sb.Clear();
791
792 switch (Type)
793 {
794 case 0:
795 default:
796 if (TreatKeywordsAsPrefixes)
797 Keyword = new WildcardKeyword(Token);
798 else
799 Keyword = new PlainKeyword(Token);
800 break;
801
802 case 1:
803 Keyword = new WildcardKeyword(Token, Wildcard);
804 break;
805
806 case 2:
807 Keyword = new RegexKeyword(Token);
808 break;
809 }
810
811 Add(Keyword, Result, ref Required, ref Prohibited);
812 }
813
814 return Result.ToArray();
815 }
816
817 private static void Add(Keyword Keyword, ChunkedList<Keyword> Result, ref bool Required, ref bool Prohibited)
818 {
819 if (Required)
820 {
822 Required = false;
823 }
824
825 if (Prohibited)
826 {
828 Prohibited = false;
829 }
830
831 Result.Add(Keyword);
832 }
833
846 internal static async Task<T[]> FullTextSearch<T>(string IndexCollection,
847 int Offset, int MaxCount, FullTextSearchOrder Order,
849 where T : class
850 {
851 if (MaxCount <= 0 || Keywords is null)
852 return Array.Empty<T>();
853
854 int NrKeywords = Keywords.Length;
855 if (NrKeywords == 0)
856 return Array.Empty<T>();
857
858 Keywords = (Keyword[])Keywords.Clone();
859 Array.Sort(Keywords, orderOfProcessing);
860
861 StringBuilder sb = new StringBuilder();
862
863 sb.Append(IndexCollection);
864 sb.Append(' ');
865 sb.Append(Order.ToString());
866
867 foreach (Keyword Keyword in Keywords)
868 {
869 if (!Keyword.Ignore)
870 {
871 sb.Append(' ');
872 sb.Append(Keyword.ToString());
873 }
874 }
875
876 string Key = sb.ToString();
877 MatchInformation[] FoundReferences;
878 SearchProcess Process = null;
879
880 if (queryCache.TryGetValue(Key, out QueryRecord QueryRecord))
881 {
882 FoundReferences = QueryRecord.FoundReferences;
883 Process = QueryRecord.Process;
884 }
885 else
886 {
888
889 await synchObj.BeginRead();
890 try
891 {
892 Index = await GetIndexLocked(IndexCollection, false);
893
894 if (!(Index is null))
895 {
896 Process = new SearchProcess(Index, IndexCollection);
897
898 foreach (Keyword Keyword in Keywords)
899 {
900 if (Keyword.Ignore)
901 continue;
902
903 if (!await Keyword.Process(Process))
904 return Array.Empty<T>();
905 }
906 }
907 }
908 finally
909 {
910 await synchObj.EndRead();
911 }
912
913 if (Index is null)
914 {
915 await synchObj.BeginWrite();
916 try
917 {
918 Index = await GetIndexLocked(IndexCollection, true);
919
920 Process = new SearchProcess(Index, IndexCollection);
921
922 foreach (Keyword Keyword in Keywords)
923 {
924 if (Keyword.Ignore)
925 continue;
926
927 if (!await Keyword.Process(Process))
928 return Array.Empty<T>();
929 }
930 }
931 finally
932 {
933 await synchObj.EndWrite();
934 }
935 }
936
937 int c = Process.ReferencesByObject.Count;
938
939 FoundReferences = new MatchInformation[c];
940 Process.ReferencesByObject.Values.CopyTo(FoundReferences, 0);
941
942 switch (Order)
943 {
944 case FullTextSearchOrder.Relevance:
945 default:
946 Array.Sort(FoundReferences, relevanceOrder);
947 break;
948
949 case FullTextSearchOrder.Occurrences:
950 Array.Sort(FoundReferences, occurrencesOrder);
951 break;
952
953 case FullTextSearchOrder.Newest:
954 Array.Sort(FoundReferences, newestOrder);
955 break;
956
957 case FullTextSearchOrder.Oldest:
958 Array.Sort(FoundReferences, oldestOrder);
959 break;
960 }
961
962 queryCache[Key] = new QueryRecord()
963 {
964 FoundReferences = FoundReferences,
965 Process = Process
966 };
967 }
968
969 ChunkedList<T> Result = new ChunkedList<T>();
970
971 switch (PaginationStrategy)
972 {
973 case PaginationStrategy.PaginateOverObjectsNullIfIncompatible:
974 default:
975 foreach (MatchInformation ObjectReference in FoundReferences)
976 {
977 if (Offset > 0)
978 {
979 Offset--;
980 continue;
981 }
982
983 ulong RefIndex = ObjectReference.ObjectReference;
984 ObjectReference Ref = await Process.TryGetObjectReference(RefIndex, true);
985 if (Ref is null)
986 Result.Add(null);
987 else
988 {
989 T Object = await Database.TryLoadObject<T>(Ref.Collection, Ref.ObjectInstanceId);
990 if (Object is null)
991 Result.Add(null);
992 else
993 Result.Add(Object);
994 }
995
996 MaxCount--;
997
998 if (MaxCount <= 0)
999 break;
1000 }
1001 break;
1002
1003 case PaginationStrategy.PaginateOverObjectsOnlyCompatible:
1004 foreach (MatchInformation ObjectReference in FoundReferences)
1005 {
1006 if (Offset > 0)
1007 {
1008 Offset--;
1009 continue;
1010 }
1011
1012 ulong RefIndex = ObjectReference.ObjectReference;
1013 ObjectReference Ref = await Process.TryGetObjectReference(RefIndex, true);
1014 if (Ref is null)
1015 continue;
1016
1017 T Object = await Database.TryLoadObject<T>(Ref.Collection, Ref.ObjectInstanceId);
1018 if (Object is null)
1019 continue;
1020
1021 Result.Add(Object);
1022 MaxCount--;
1023
1024 if (MaxCount <= 0)
1025 break;
1026 }
1027 break;
1028
1029 case PaginationStrategy.PaginationOverCompatibleOnly:
1030 foreach (MatchInformation ObjectReference in FoundReferences)
1031 {
1032 ulong RefIndex = ObjectReference.ObjectReference;
1033 ObjectReference Ref = await Process.TryGetObjectReference(RefIndex, true);
1034
1035 if (Ref is null)
1036 continue;
1037
1038 T Object = await Database.TryLoadObject<T>(Ref.Collection, Ref.ObjectInstanceId);
1039 if (Object is null)
1040 continue;
1041
1042 if (Offset > 0)
1043 {
1044 Offset--;
1045 continue;
1046 }
1047
1048 Result.Add(Object);
1049 MaxCount--;
1050
1051 if (MaxCount <= 0)
1052 break;
1053 }
1054 break;
1055 }
1056
1057 return Result.ToArray();
1058 }
1059
1060 private static readonly OrderOfProcessing orderOfProcessing = new OrderOfProcessing();
1061 private static readonly RelevanceOrder relevanceOrder = new RelevanceOrder();
1062 private static readonly OccurrencesOrder occurrencesOrder = new OccurrencesOrder();
1063 private static readonly NewestOrder newestOrder = new NewestOrder();
1064 private static readonly OldestOrder oldestOrder = new OldestOrder();
1065
1066 private class QueryRecord
1067 {
1068 public MatchInformation[] FoundReferences;
1069 public SearchProcess Process;
1070 }
1071
1072 private void Database_ObjectDeleted(object Sender, ObjectEventArgs e)
1073 {
1074 Task.Run(() => this.ObjectDeleted(e));
1075 }
1076
1077 private async Task ObjectDeleted(ObjectEventArgs e)
1078 {
1079 try
1080 {
1081 Tuple<CollectionInformation, TypeInformation, GenericObject> P = await Prepare(e.Object);
1082 if (P is null)
1083 return;
1084
1085 object ObjectId = await Database.TryGetObjectId(e.Object);
1086 if (ObjectId is null)
1087 return;
1088
1089 ObjectReference Ref = await Database.FindFirstIgnoreRest<ObjectReference>(new FilterAnd(
1090 new FilterFieldEqualTo("Collection", P.Item1.CollectionName),
1091 new FilterFieldEqualTo("ObjectInstanceId", ObjectId)));
1092
1093 if (Ref is null)
1094 return;
1095
1096 await synchObj.BeginWrite();
1097 try
1098 {
1099 await RemoveTokensFromIndexLocked(Ref);
1100 }
1101 finally
1102 {
1103 await synchObj.EndWrite();
1104 }
1105
1106 queryCache?.Clear();
1107
1108 await Search.RaiseObjectRemovedFromIndex(this, new ObjectReferenceEventArgs(Ref));
1109 }
1110 catch (Exception ex)
1111 {
1112 Log.Exception(ex);
1113 }
1114 }
1115
1116 private static async Task RemoveTokensFromIndexLocked(ObjectReference Ref)
1117 {
1118 IPersistentDictionary Index = await GetIndexLocked(Ref.IndexCollection, true);
1119
1120 foreach (TokenCount Token in Ref.Tokens)
1121 {
1122 string Suffix = " " + Token.Block.ToString();
1123 KeyValuePair<bool, object> P = await Index.TryGetValueAsync(Token.Token + Suffix);
1124
1125 if (!P.Key)
1126 P = await Index.TryGetValueAsync(Token.Token);
1127
1128 if (!P.Key || !(P.Value is TokenReferences References))
1129 continue;
1130
1131 int i = Array.IndexOf(References.ObjectReferences, Ref.Index);
1132 if (i < 0)
1133 continue;
1134
1135 int c = References.ObjectReferences.Length;
1136 ulong[] NewReferences = new ulong[c - 1];
1137 uint[] NewCounts = new uint[c - 1];
1138 DateTime[] NewTimestamps = new DateTime[c - 1];
1139
1140 if (i > 0)
1141 {
1142 Array.Copy(References.ObjectReferences, 0, NewReferences, 0, i);
1143 Array.Copy(References.Counts, 0, NewCounts, 0, i);
1144 Array.Copy(References.Timestamps, 0, NewTimestamps, 0, i);
1145 }
1146
1147 if (i < c - 1)
1148 {
1149 Array.Copy(References.ObjectReferences, i + 1, NewReferences, i, c - i - 1);
1150 Array.Copy(References.Counts, i + 1, NewCounts, i, c - i - 1);
1151 Array.Copy(References.Timestamps, i + 1, NewTimestamps, i, c - i - 1);
1152 }
1153
1154 References.ObjectReferences = NewReferences;
1155 References.Counts = NewCounts;
1156 References.Timestamps = NewTimestamps;
1157
1158 await Index.AddAsync(Token.Token, References, true);
1159 }
1160 }
1161
1162 private void Database_ObjectUpdated(object Sender, ObjectEventArgs e)
1163 {
1164 Task.Run(() => ProcessObjectUpdate(e.Object));
1165 }
1166
1171 public static async Task ProcessObjectUpdate(object Object)
1172 {
1173 try
1174 {
1175 Tuple<CollectionInformation, TypeInformation, GenericObject> P = await Prepare(Object);
1176 if (P is null)
1177 return;
1178
1179 object ObjectId = await Database.TryGetObjectId(Object);
1180 if (ObjectId is null)
1181 return;
1182
1183 CollectionInformation CollectionInfo = P.Item1;
1184 TypeInformation TypeInfo = P.Item2;
1185 GenericObject GenObj = P.Item3;
1186 TokenCount[] Tokens;
1187
1188 if (GenObj is null)
1189 Tokens = await TypeInfo.Tokenize(Object, CollectionInfo.Properties);
1190 else
1191 Tokens = await Tokenize(GenObj, CollectionInfo.Properties);
1192
1193 ObjectReference Ref = await Database.FindFirstIgnoreRest<ObjectReference>(new FilterAnd(
1194 new FilterFieldEqualTo("Collection", P.Item1.CollectionName),
1195 new FilterFieldEqualTo("ObjectInstanceId", ObjectId)));
1196
1197 if (AreSame(Tokens, Ref?.Tokens))
1198 return;
1199
1200 bool Added = false;
1201
1202 await synchObj.BeginWrite();
1203 try
1204 {
1205 if (Ref is null)
1206 {
1207 if (Tokens.Length == 0)
1208 return;
1209
1210 string IndexName;
1211
1212 if (GenObj is null)
1213 IndexName = TypeInfo.GetIndexCollection(Object);
1214 else
1215 IndexName = CollectionInfo.IndexCollectionName;
1216
1217 ulong Index = await GetNextIndexNrLocked(IndexName);
1218
1219 Ref = new ObjectReference()
1220 {
1221 IndexCollection = IndexName,
1222 Collection = CollectionInfo.CollectionName,
1223 ObjectInstanceId = ObjectId,
1224 Index = Index,
1225 Tokens = Tokens,
1226 Indexed = DateTime.UtcNow
1227 };
1228
1229 await AddTokensToIndexLocked(Ref);
1230 await Database.Insert(Ref);
1231
1232 Added = true;
1233 }
1234 else
1235 {
1236 await RemoveTokensFromIndexLocked(Ref);
1237
1238 Ref.Tokens = Tokens;
1239 await AddTokensToIndexLocked(Ref);
1240
1241 await Database.Update(Ref);
1242 }
1243 }
1244 finally
1245 {
1246 await synchObj.EndWrite();
1247 }
1248
1249 queryCache.Clear();
1250
1251 if (Added)
1252 await Search.RaiseObjectAddedToIndex(instance, new ObjectReferenceEventArgs(Ref));
1253 else
1254 await Search.RaiseObjectUpdatedInIndex(instance, new ObjectReferenceEventArgs(Ref));
1255 }
1256 catch (Exception ex)
1257 {
1258 Log.Exception(ex);
1259 }
1260 }
1261
1262 private static bool AreSame(TokenCount[] Tokens1, TokenCount[] Tokens2)
1263 {
1264 int c = Tokens1?.Length ?? 0;
1265 int d = Tokens2?.Length ?? 0;
1266
1267 if (c != d)
1268 return false;
1269
1270 int i;
1271
1272 for (i = 0; i < c; i++)
1273 {
1274 if (!Tokens1[i].Equals(Tokens2[i]))
1275 return false;
1276 }
1277
1278 return true;
1279 }
1280
1281 private async Task Database_CollectionCleared(object Sender, CollectionEventArgs e)
1282 {
1283 try
1284 {
1285 IEnumerable<ObjectReference> ObjectsDeleted;
1286
1287 do
1288 {
1289 ObjectsDeleted = await Database.FindDelete<ObjectReference>(0, 1000,
1290 new FilterFieldEqualTo("Collection", e.Collection));
1291
1292 foreach (ObjectReference Ref in ObjectsDeleted)
1293 {
1294 await synchObj.BeginWrite();
1295 try
1296 {
1297 await RemoveTokensFromIndexLocked(Ref);
1298 }
1299 finally
1300 {
1301 await synchObj.EndWrite();
1302 }
1303
1304 queryCache.Clear();
1305
1306 await Search.RaiseObjectRemovedFromIndex(this, new ObjectReferenceEventArgs(Ref));
1307 }
1308 }
1309 while (!IsEmpty(ObjectsDeleted));
1310 }
1311 catch (Exception ex)
1312 {
1313 Log.Exception(ex);
1314 }
1315 }
1316
1322 public static async Task<long> ReindexCollection(string IndexCollectionName)
1323 {
1325 string[] Collections;
1326
1327 await synchObj.BeginWrite();
1328 try
1329 {
1330 Index = await GetIndexLocked(IndexCollectionName, true);
1331 await Index.ClearAsync();
1332
1333 Collections = await GetCollectionNamesLocked(IndexCollectionName);
1334 }
1335 finally
1336 {
1337 await synchObj.EndWrite();
1338 }
1339
1340 IEnumerable<ObjectReference> ObjectsDeleted;
1341
1342 do
1343 {
1344 ObjectsDeleted = await Database.FindDelete<ObjectReference>(0, 1000,
1345 new FilterFieldEqualTo("IndexCollection", IndexCollectionName));
1346
1347 foreach (ObjectReference Ref in ObjectsDeleted)
1348 await Search.RaiseObjectRemovedFromIndex(instance, new ObjectReferenceEventArgs(Ref));
1349 }
1350 while (!IsEmpty(ObjectsDeleted));
1351
1352 ReindexCollectionIteration Iteration = new ReindexCollectionIteration();
1353
1354 await Database.Iterate(Iteration, Collections);
1355
1356 return Iteration.NrObjectsProcessed;
1357 }
1358
1359 private static bool IsEmpty(IEnumerable<ObjectReference> Objects)
1360 {
1361 foreach (ObjectReference _ in Objects)
1362 return false;
1363
1364 return true;
1365 }
1366
1367 private class ReindexCollectionIteration : IDatabaseIteration<object>
1368 {
1369 public Task StartDatabase() => Task.CompletedTask;
1370 public Task EndDatabase() => Task.CompletedTask;
1371 public Task EndCollection() => Task.CompletedTask;
1372 public Task IncompatibleObject(object ObjectId) => Task.CompletedTask;
1373
1374 public long NrObjectsProcessed = 0;
1375 public int NrCollectionsProcessed = 0;
1376
1377 public Task StartCollection(string CollectionName)
1378 {
1379 this.NrCollectionsProcessed++;
1380 return Task.CompletedTask;
1381 }
1382
1383 public async Task ProcessObject(object Object)
1384 {
1385 this.NrObjectsProcessed++;
1386 await instance.ObjectInserted(new ObjectEventArgs(Object));
1387 }
1388
1389 public Task ReportException(Exception Exception)
1390 {
1391 Log.Exception(Exception);
1392 return Task.CompletedTask;
1393 }
1394 }
1395
1401 internal static void RegisterStopWords(params string[] StopWords)
1402 {
1403 Dictionary<string, bool> NewList = new Dictionary<string, bool>();
1404
1405 foreach (KeyValuePair<string, bool> P in stopWords)
1406 NewList[P.Key] = P.Value;
1407
1408 foreach (string StopWord in StopWords)
1409 NewList[StopWord] = true;
1410
1411 stopWords = NewList;
1412 }
1413
1419 internal static bool IsStopWord(string StopWord)
1420 {
1421 return stopWords.TryGetValue(StopWord, out bool b) && b;
1422 }
1423
1431 public static async Task<TokenCount[]> Tokenize(IEnumerable<object> Objects)
1432 {
1434 await Tokenize(Objects, Process);
1435
1436 return Process.ToArray();
1437 }
1438
1446 public static async Task Tokenize(IEnumerable<object> Objects,
1447 TokenizationProcess Process)
1448 {
1449 foreach (object Object in Objects)
1450 {
1451 if (Object is null)
1452 continue;
1453
1454 object Object2 = await ScriptNode.WaitPossibleTask(Object);
1455
1456 Type T = Object2.GetType();
1457 ITokenizer Tokenizer;
1458 bool Found;
1459
1460 lock (tokenizers)
1461 {
1462 Found = tokenizers.TryGetValue(T, out Tokenizer);
1463 }
1464
1465 if (!Found)
1466 {
1467 Tokenizer = Types.FindBest<ITokenizer, Type>(T);
1468
1469 lock (tokenizers)
1470 {
1471 tokenizers[T] = Tokenizer;
1472 }
1473 }
1474
1475 if (Tokenizer is null)
1476 {
1477 Tuple<CollectionInformation, TypeInformation, GenericObject> P = await Prepare(Object2);
1478 if (P is null)
1479 continue;
1480
1481 object ObjectId = await Database.TryGetObjectId(Object2);
1482 if (ObjectId is null)
1483 return;
1484
1485 CollectionInformation CollectionInfo = P.Item1;
1486 TypeInformation TypeInfo = P.Item2;
1487 GenericObject GenObj = P.Item3;
1488
1489 if (GenObj is null)
1490 await TypeInfo.Tokenize(Object2, Process, CollectionInfo.Properties);
1491 else
1492 await Tokenize(GenObj, Process, CollectionInfo.Properties);
1493 }
1494 else
1495 await Tokenizer.Tokenize(Object2, Process);
1496
1497 Process.DocumentIndexOffset++;
1498 }
1499 }
1500
1501 private static readonly Dictionary<Type, ITokenizer> tokenizers = new Dictionary<Type, ITokenizer>();
1502
1503 private void Types_OnInvalidated(object Sender, EventArgs e)
1504 {
1505 lock (tokenizers)
1506 {
1507 tokenizers.Clear();
1508 }
1509 }
1510
1517 internal static async Task<TokenCount[]> Tokenize(GenericObject Obj, params PropertyDefinition[] Properties)
1518 {
1519 ChunkedList<object> Values = await GetValues(Obj, Properties);
1520
1521 if (!Values.HasFirstItem)
1522 return null;
1523
1524 return await Tokenize(Values);
1525 }
1526
1534 internal static async Task Tokenize(GenericObject Obj, TokenizationProcess Process, params PropertyDefinition[] Properties)
1535 {
1536 ChunkedList<object> Values = await GetValues(Obj, Properties);
1537
1538 if (Values.HasFirstItem)
1539 await Tokenize(Values, Process);
1540 }
1541
1548 internal static async Task<ChunkedList<object>> GetValues(GenericObject Obj, params PropertyDefinition[] Properties)
1549 {
1551 object Value;
1552
1553 if (!(Obj is null))
1554 {
1555 foreach (PropertyDefinition Property in Properties)
1556 {
1557 Value = await Property.GetValue(Obj);
1558 if (!(Value is null))
1559 Values.Add(Value);
1560 }
1561 }
1562
1563 return Values;
1564 }
1565
1574 internal static async Task<FolderIndexationStatistics> IndexFolder(string IndexCollection, string Folder, bool Recursive,
1575 params string[] ExcludeSubfolders)
1576 {
1577 if (string.IsNullOrEmpty(IndexCollection))
1578 throw new ArgumentException("Empty index.", nameof(IndexCollection));
1579
1580 if (string.IsNullOrEmpty(Folder))
1581 throw new ArgumentException("Empty folder.", nameof(Folder));
1582
1583 Folder = Path.GetFullPath(Folder);
1584 if (!Directory.Exists(Folder))
1585 throw new ArgumentException("Folder does not exist.", nameof(Folder));
1586
1587 if (Folder[Folder.Length - 1] != Path.DirectorySeparatorChar)
1588 Folder += Path.DirectorySeparatorChar;
1589
1590 string[] FileNames = Directory.GetFiles(Folder, "*.*", Recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
1591 Dictionary<CaseInsensitiveString, FileReference> References = new Dictionary<CaseInsensitiveString, FileReference>();
1593 int i, c, d;
1594
1595 if (!(ExcludeSubfolders is null))
1596 {
1597 ExcludeSubfolders = (string[])ExcludeSubfolders.Clone();
1598 c = ExcludeSubfolders.Length;
1599
1600 for (i = 0; i < c; i++)
1601 {
1602 ExcludeSubfolders[i] = Path.GetFullPath(ExcludeSubfolders[i]);
1603 d = ExcludeSubfolders[i].Length;
1604
1605 if (d == 0 || ExcludeSubfolders[i][d - 1] != Path.DirectorySeparatorChar)
1606 ExcludeSubfolders[i] += Path.DirectorySeparatorChar;
1607 }
1608 }
1609
1610 IEnumerable<FileReference> ReferencesInDB = await Database.Find<FileReference>(
1611 new FilterAnd(
1612 new FilterFieldEqualTo("IndexCollection", IndexCollection),
1613 new FilterFieldLikeRegEx("FileName", Database.WildcardToRegex(Folder + "*", "*"))));
1614
1615 foreach (FileReference Reference in ReferencesInDB)
1616 References[Reference.FileName] = Reference;
1617
1618 foreach (string FileName in FileNames)
1619 {
1620 if (!(ExcludeSubfolders is null))
1621 {
1622 bool Exclude = false;
1623
1624 foreach (string s in ExcludeSubfolders)
1625 {
1626 if (FileName.StartsWith(s))
1627 {
1628 Exclude = true;
1629 break;
1630 }
1631 }
1632
1633 if (Exclude)
1634 continue;
1635 }
1636
1637 if (!FileReferenceTokenizer.HasTokenizer(FileName))
1638 continue;
1639
1640 Result.NrFiles++;
1641
1642 DateTime TP = File.GetLastWriteTimeUtc(FileName);
1643
1644 if (References.TryGetValue(FileName, out FileReference Ref))
1645 {
1646 References.Remove(FileName);
1647
1648 if (Ref.Timestamp == TP)
1649 continue;
1650
1651 Ref.Timestamp = TP;
1652 await Database.Update(Ref); // Will trigger retokenization of file.
1653
1654 Result.NrUpdated++;
1655 Result.TotalChanges++;
1656 }
1657 else
1658 {
1659 Ref = new FileReference()
1660 {
1661 FileName = FileName,
1662 IndexCollection = IndexCollection,
1663 Timestamp = TP
1664 };
1665
1666 await Database.Insert(Ref); // Will trigger tokenization of file.
1667
1668 Result.NrAdded++;
1669 Result.TotalChanges++;
1670 }
1671 }
1672
1673 foreach (FileReference Reference in References.Values)
1674 {
1675 await Database.Delete(Reference); // Will trigger removal of tokens.
1676 Result.NrDeleted++;
1677 Result.TotalChanges++;
1678 }
1679
1680 return Result;
1681 }
1682
1689 internal static async Task<bool> IndexFile(string IndexCollection, string FileName)
1690 {
1691 FileName = Path.GetFullPath(FileName);
1692
1693 FileReference ReferenceInDB = await Database.FindFirstIgnoreRest<FileReference>(
1694 new FilterAnd(
1695 new FilterFieldEqualTo("IndexCollection", IndexCollection),
1696 new FilterFieldEqualTo("FileName", FileName)));
1697
1698 if (!FileReferenceTokenizer.HasTokenizer(FileName))
1699 return false;
1700
1701 if (File.Exists(FileName))
1702 {
1703 DateTime TP = File.GetLastWriteTimeUtc(FileName);
1704
1705 if (ReferenceInDB is null)
1706 {
1707 ReferenceInDB = new FileReference()
1708 {
1709 FileName = FileName,
1710 IndexCollection = IndexCollection,
1711 Timestamp = TP
1712 };
1713
1714 await Database.Insert(ReferenceInDB); // Will trigger tokenization of file.
1715
1716 return true;
1717 }
1718 else
1719 {
1720 if (ReferenceInDB.Timestamp == TP)
1721 return false;
1722
1723 ReferenceInDB.Timestamp = TP;
1724 await Database.Update(ReferenceInDB); // Will trigger retokenization of file.
1725
1726 return true;
1727 }
1728 }
1729 else if (ReferenceInDB is null)
1730 return false;
1731 else
1732 {
1733 await Database.Delete(ReferenceInDB); // Will trigger removal of tokens.
1734 return true;
1735 }
1736 }
1737
1738 }
1739}
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:14
static void Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
Definition: Log.cs:1657
This attribute defines the name of the collection that will house objects of this type.
Event arguments for collection events.
Static interface for database persistence. In order to work, a database provider has to be assigned t...
Definition: Database.cs:21
static Task< IEnumerable< object > > FindDelete(string Collection, params string[] SortOrder)
Finds objects in a given collection and deletes them in the same atomic operation.
Definition: Database.cs:1442
static Task< IPersistentDictionary > GetDictionary(string Collection)
Gets a persistent dictionary containing objects in a collection.
Definition: Database.cs:2307
static Task< object > TryGetObjectId(object Object)
Tries to get the Object ID of an object, if it exists.
Definition: Database.cs:2406
static string WildcardToRegex(string s, string Wildcard)
Converts a wildcard string to a regular expression string.
Definition: Database.cs:2426
static async Task Update(object Object)
Updates an object in the database.
Definition: Database.cs:1211
static async Task Delete(object Object)
Deletes an object in the database.
Definition: Database.cs:1291
static Task< IEnumerable< object > > Find(string Collection, params string[] SortOrder)
Finds objects in a given collection.
Definition: Database.cs:238
static async Task Insert(object Object)
Inserts an object into the default collection of the database.
Definition: Database.cs:97
static Task< object > TryLoadObject(string CollectionName, object ObjectId)
Tries to load an object given its Object ID ObjectId and its collection name CollectionName .
Definition: Database.cs:1838
This filter selects objects that conform to all child-filters provided.
Definition: FilterAnd.cs:10
This filter selects objects that have a named field equal to a given value.
This filter selects objects that have a named field matching a given regular expression.
Contains information about a collection, in relation to full-text-search.
bool IndexForFullTextSearch
If collection should be indexed.
bool AddIndexableProperties(params PropertyDefinition[] Properties)
Adds properties for full-text-search indexation.
PropertyDefinition[] Properties
Properties to index
bool RemoveIndexableProperties(params PropertyDefinition[] Properties)
Removes properties from full-text-search indexation.
Contains a reference to an indexed file.
DateTime Timestamp
When object was indexed.
CaseInsensitiveString FileName
Name of collection hosting object.
Contains statistics about a files folder (re)indexation procedure.
This attribute defines that objects of this type should be indexed in the full-text-search index.
bool DynamicIndexCollection
If the index collection is dynamic (i.e. depends on object instance).
string GetIndexCollection(object Reference)
Name of full-text-search index collection.
Full-text search module, controlling the life-cycle of the full-text-search engine.
static async Task ProcessObjectUpdate(object Object)
Processes an object that has been updated.
static async Task< TokenCount[]> Tokenize(IEnumerable< object > Objects)
Tokenizes a set of objects using available tokenizers. Tokenizers are classes with a default contruct...
static async Task< long > ReindexCollection(string IndexCollectionName)
Reindexes the full-text-search index for a database collection.
static async Task< string[]> GetCollectionNames(string IndexCollectionName)
Gets the database collections that get indexed into a given index colltion.
FullTextSearchModule()
Full-text search module, controlling the life-cycle of the full-text-search engine.
static async Task Tokenize(IEnumerable< object > Objects, TokenizationProcess Process)
Tokenizes a set of objects using available tokenizers. Tokenizers are classes with a default contruct...
static async Task< Dictionary< string, string[]> > GetCollectionNames()
Gets the database collections that get indexed into a given index colltion.
Abstract base class for keywords.
Definition: Keyword.cs:11
virtual async Task< bool > Process(SearchProcess Process)
Processes the keyword in a search process.
Definition: Keyword.cs:67
virtual bool Ignore
If keyword should be ignored.
Definition: Keyword.cs:47
Contains information about a search process.
async Task< ObjectReference > TryGetObjectReference(ulong ObjectIndex, bool CanLoadFromDatabase)
Tries to get an object reference.
Dictionary< ulong, MatchInformation > ReferencesByObject
References found.
Contains a reference to an indexed object.
object ObjectInstanceId
Object ID of object instance.
TokenCount[] Tokens
Token count in document.
string Collection
Name of collection hosting object.
ObjectReference()
Contains a reference to an indexed object.
string IndexCollection
Collection of full-text-search index.
ulong Index
Reference number to use in full-text-index.
Contains matching information about a document in a search.
Orders entries from newest to oldest.
Definition: NewestOrder.cs:9
Orders entries based on occurrences of keywords.
Orders entries from oldest to newest.
Definition: OldestOrder.cs:9
async Task< object > GetValue(object Instance)
Gets the object to index.
Static class for access to Full-Text-Search
Definition: Search.cs:67
Represents a token and a corresponding occurrence count.
Definition: TokenCount.cs:12
uint Block
Reference is stored in this block in the full-text-search index.
Definition: TokenCount.cs:45
override string ToString()
Object.ToString()
Definition: TokenCount.cs:50
uint[] DocIndex
Index inside document of each occurrence.
Definition: TokenCount.cs:40
Contains a sequence of object references that include the token in its indexed text properties.
uint[] Counts
Token counts for respective object reference.
const int MaxReferences
Maximum amount of references in a block (100).
Tokenizes files via FileReference object references.
static bool HasTokenizer(string FileName)
Checks if a file has a file tokenizer associated with it.
Contains information about a tokenization process.
TokenCount[] ToArray()
Generates an array of token counts.
Event arguments for database object events.
Generic object. Contains a sequence of properties.
Implements an in-memory cache.
Definition: Cache.cs:17
void Dispose()
IDisposable.Dispose
Definition: Cache.cs:99
bool TryGetValue(KeyType Key, out ValueType Value)
Tries to get a value from the cache.
Definition: Cache.cs:311
void Clear()
Clears the cache.
Definition: Cache.cs:679
A chunked list is a linked list of chunks of objects of type T .
Definition: ChunkedList.cs:54
bool HasFirstItem
If there is a first item in the collection
Definition: ChunkedList.cs:778
void Add(T Item)
Adds an item to the collection.
Definition: ChunkedList.cs:272
T[] ToArray()
Returns an array containing all elements of the collection.
Static class that dynamically manages types and interfaces available in the runtime environment.
Definition: Types.cs:15
Represents an object that allows single concurrent writers but multiple concurrent readers....
Base class for all nodes in a parsed script tree.
Definition: ScriptNode.cs:69
static async Task< object > WaitPossibleTask(object Result)
Waits for any asynchronous process to terminate.
Definition: ScriptNode.cs:441
Interface for full-text-search tokenizers
Definition: ITokenizer.cs:12
Task Tokenize(object Value, TokenizationProcess Process)
Tokenizes an object.
Persistent dictionary that can contain more entries than possible in the internal memory.
Task< KeyValuePair< bool, object > > TryGetValueAsync(string key)
Gets the value associated with the specified key.
Task< object[]> GetValuesAsync()
Gets all values.
Task AddAsync(string key, object value)
Adds an element with the provided key and value to the System.Collections.Generic....
Task ClearAsync()
Clears the dictionary.
Interface for iterations of database contents.
Interface for late-bound modules loaded at runtime.
Definition: IModule.cs:9
Definition: ImplTypes.g.cs:58
FullTextSearchOrder
Order in which results are returned.
Definition: Search.cs:13
PaginationStrategy
How pagination in full-text-searches should be handled.
Definition: Search.cs:39