Skip to content

Commit c2b5030

Browse files
authored
Merge pull request #3 from koculu/2-feature-add-facet-support
Add facet search support.
2 parents 64b506b + 11d5929 commit c2b5030

File tree

15 files changed

+890
-85
lines changed

15 files changed

+890
-85
lines changed

README.md

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,72 @@ To delete a record from the search engine:
9696
searchEngine.DeleteRecord(1);
9797
```
9898

99+
### Pagination
100+
101+
In addition to basic searching, you can easily implement pagination using the `skip` and `limit` parameters in the `Search` method. This is especially useful when dealing with large datasets where you only want to display a subset of the results at a time.
102+
103+
```csharp
104+
var resultsPage1 = searchEngine.Search(
105+
search: "quick fox",
106+
respectTokenOrder: true,
107+
skip: 0, // Skip 0 records, start from the beginning
108+
limit: 10 // Limit to 10 records in this page
109+
);
110+
111+
var resultsPage2 = searchEngine.Search(
112+
search: "quick fox",
113+
respectTokenOrder: true,
114+
skip: 10, // Skip the first 10 records
115+
limit: 10 // Limit to 10 records in this page
116+
);
117+
```
118+
119+
### Adding Facets
120+
121+
To associate a facet (e.g., category, author) with a record:
122+
123+
```csharp
124+
searchEngine.AddFacet(1, "category", "books");
125+
searchEngine.AddFacet(1, "author", "John Doe");
126+
```
127+
128+
### Deleting Facets
129+
130+
To remove a specific facet associated with a record:
131+
132+
```csharp
133+
searchEngine.DeleteFacet(1, "category", "books");
134+
searchEngine.DeleteFacet(1, "author", "John Doe");
135+
```
136+
137+
### Faceted Search
138+
139+
If you're also filtering by facets, you can still apply pagination by specifying the `skip` and `limit` parameters:
140+
141+
```csharp
142+
var facets = new Dictionary<string, string>
143+
{
144+
{ "category", "books" },
145+
{ "author", "John Doe" }
146+
};
147+
148+
var paginatedFacetedResultsPage1 = searchEngine.Search(
149+
search: "quick fox",
150+
facets: facets,
151+
respectTokenOrder: true,
152+
skip: 0, // Start from the first matching record
153+
limit: 10 // Retrieve up to 10 records
154+
);
155+
156+
var paginatedFacetedResultsPage2 = searchEngine.Search(
157+
search: "quick fox",
158+
facets: facets,
159+
respectTokenOrder: true,
160+
skip: 10, // Skip the first 10 matching records
161+
limit: 10 // Retrieve the next 10 records
162+
);
163+
```
164+
99165
### Cleanup
100166

101167
When you're done using the search engine, ensure proper cleanup:
@@ -259,7 +325,7 @@ Future Search Engines: Additional search engines, such as Phrase Search Engine a
259325

260326
## License
261327

262-
ZoneTree.FullTextSearch is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details.
328+
ZoneTree.FullTextSearch is licensed under the MIT License. See the [LICENSE](https://github.com/koculu/ZoneTree.FullTextSearch/blob/main/LICENSE) file for more details.
263329

264330
---
265331

src/ZoneTree.FullTextSaearch/Core/Index/IndexOfTokenRecordPreviousToken.cs

Lines changed: 73 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ public sealed class IndexOfTokenRecordPreviousToken<TRecord, TToken>
1818
where TRecord : unmanaged
1919
where TToken : unmanaged
2020
{
21+
/// <summary>
22+
/// Gets or sets the facet previous token.
23+
/// This property represents a token that precedes the current token in the context of facet-based searches.
24+
/// </summary>
25+
public TToken FacetPreviousToken { get; set; }
26+
2127
/// <summary>
2228
/// Gets the primary zone tree used to store and retrieve records by token and previous token.
2329
/// </summary>
@@ -197,8 +203,8 @@ public void Drop()
197203
WaitForBackgroundThreads();
198204
isDropped = true;
199205
IsReadOnly = true;
200-
ZoneTree1.Maintenance.DestroyTree();
201-
ZoneTree2?.Maintenance.DestroyTree();
206+
ZoneTree1.Maintenance.Drop();
207+
ZoneTree2?.Maintenance.Drop();
202208
ZoneTree1.Dispose();
203209
ZoneTree2?.Dispose();
204210
}
@@ -227,6 +233,32 @@ public void UpsertRecord(TToken token, TRecord record, TToken previousToken)
227233
ZoneTree2.TryAdd(key, new());
228234
}
229235

236+
/// <summary>
237+
/// Deletes a record from the primary zone tree, and optionally from the secondary zone tree if a secondary index is enabled.
238+
/// </summary>
239+
/// <param name="token">The token associated with the record to delete.</param>
240+
/// <param name="record">The record to be deleted.</param>
241+
/// <param name="previousToken">The token that precedes the current token in the record.</param>
242+
public void DeleteRecord(TToken token, TRecord record, TToken previousToken)
243+
{
244+
ThrowIfIndexIsDropped();
245+
ZoneTree1.ForceDelete(new CompositeKeyOfTokenRecordPrevious<TRecord, TToken>()
246+
{
247+
Token = token,
248+
Record = record,
249+
PreviousToken = previousToken
250+
});
251+
252+
if (!useSecondaryIndex)
253+
return;
254+
255+
ZoneTree2.ForceDelete(new CompositeKeyOfRecordToken<TRecord, TToken>()
256+
{
257+
Record = record,
258+
Token = token,
259+
});
260+
}
261+
230262
/// <summary>
231263
/// Deletes a record from the index without using the secondary index.
232264
/// </summary>
@@ -291,31 +323,61 @@ public long DeleteRecord(TRecord record)
291323
}
292324

293325
/// <summary>
294-
/// Searches for records that match the provided tokens, with optional parameters for token order,
295-
/// skipping records, and limiting the number of results.
326+
/// Searches the index for records that match the specified tokens, with optional support for facets, token order respect, and pagination.
296327
/// </summary>
297-
/// <param name="tokens">The tokens to search for.</param>
298-
/// <param name="firstLookAt">An optional token to prioritize during the search.</param>
299-
/// <param name="respectTokenOrder">Indicates whether the order of tokens should be respected during the search.</param>
300-
/// <param name="skip">The number of records to skip in the result set.</param>
301-
/// <param name="limit">The maximum number of records to return.</param>
302-
/// <returns>An array of records that match the search criteria.</returns>
328+
/// <param name="tokens">
329+
/// A read-only span of tokens that the records must contain. This parameter is mandatory unless facets are provided.
330+
/// The tokens are logically grouped using "AND", meaning all tokens must be present in the matching records.
331+
/// If both the tokens span and the facets span are empty, the result will be an empty array, as searching without tokens and facets is not supported.
332+
/// Tokens can be empty if facets are provided; in this case, the search will be based solely on the facets.
333+
/// To retrieve records without specific search tokens or facets, consider fetching them from the actual record source instead of using the search index.
334+
/// </param>
335+
/// <param name="firstLookAt">
336+
/// An optional token that the search will prioritize when searching.
337+
/// If not specified, the first token in the tokens span is used.
338+
/// </param>
339+
/// <param name="respectTokenOrder">
340+
/// A boolean indicating whether the search should respect the order of tokens in the record.
341+
/// If true, the records must contain the tokens in the specified order.
342+
/// </param>
343+
/// <param name="facets">
344+
/// An optional read-only span of tokens that can be used to filter the search results.
345+
/// If any facets are provided, records must contain at least one of these facet tokens to be included in the results.
346+
/// If the span is empty or not provided, no facet filtering is applied, and all matching records are returned regardless of facet values.
347+
/// </param>
348+
/// <param name="skip">
349+
/// The number of matching records to skip in the result set, useful for pagination.
350+
/// Defaults to 0.
351+
/// </param>
352+
/// <param name="limit">
353+
/// The maximum number of records to return, useful for limiting the result set size.
354+
/// Defaults to 0, which indicates no limit.
355+
/// </param>
356+
/// <returns>
357+
/// An array of records that match the specified tokens and facets, respecting the token order if specified.
358+
/// The array may be empty if no matching records are found.
359+
/// </returns>
303360
public TRecord[] Search(
304361
ReadOnlySpan<TToken> tokens,
305362
TToken? firstLookAt = null,
306363
bool respectTokenOrder = true,
364+
ReadOnlySpan<TToken> facets = default,
307365
int skip = 0,
308366
int limit = 0)
309367
{
310368
return searchAlgorithm
311-
.Search(tokens, firstLookAt, respectTokenOrder, skip, limit);
369+
.Search(tokens, firstLookAt, respectTokenOrder, facets, skip, limit);
312370
}
313371

372+
bool isDisposed = false;
373+
314374
/// <summary>
315375
/// Disposes the resources used by the index.
316376
/// </summary>
317377
public void Dispose()
318378
{
379+
if (isDisposed) return;
380+
isDisposed = true;
319381
Maintainer1.WaitForBackgroundThreads();
320382
Maintainer1.Dispose();
321383
ZoneTree1.Dispose();

0 commit comments

Comments
 (0)