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