Repository navigation
Expand file tree
/
Copy pathSearchEngine.cs
More file actions
144 lines (134 loc) · 7.96 KB
/
Copy pathSearchEngine.cs
File metadata and controls
144 lines (134 loc) · 7.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
using KeyLoad.Core;
using KeyLoad.Query.Features.Search;
using Microsoft.Extensions.Options;
namespace KeyLoad.Query;
/// <summary>Runs exact text, vector and hybrid ranking within one authorized read cut.</summary>
public sealed partial class SearchEngine
{
private readonly DatabaseEngine database;
private readonly ITextProjection? textProjection;
private readonly QueryExecutionOptions execution;
private readonly IOptions<QueryExecutionOptions> configuration;
/// <summary>Creates an authorized search owner with one frozen native execution policy.</summary>
/// <param name="database">Node-owned canonical database.</param>
/// <param name="options">Centrally validated query and text execution budgets.</param>
/// <param name="textProjection">Optional native derived text projection.</param>
public SearchEngine(DatabaseEngine database, IOptions<QueryExecutionOptions> options, ITextProjection? textProjection = null)
{
ArgumentNullException.ThrowIfNull(database);
ArgumentNullException.ThrowIfNull(options);
configuration = options;
execution = options.Value;
execution.Validate();
this.database = database;
this.textProjection = textProjection;
}
private const string UnsafeSynchronousSearch = "Await SearchAsync when native text search runs on a scheduler or synchronization context.";
/// <summary>Returns exact fused ranks with selected documents projected by persisted policy.</summary>
/// <param name="principalId">Persisted principal identity.</param>
/// <param name="request">Search fields, query and result limit.</param>
/// <param name="cancellationToken">Caller cancellation for the complete read.</param>
/// <returns>Ranked and authorized documents.</returns>
public RankedDocument[] Search(string principalId, SearchRequest request, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(request);
if (request.Text is not null && textProjection is not null
&& (TaskScheduler.Current != TaskScheduler.Default || SynchronizationContext.Current is not null))
{
throw new InvalidOperationException(UnsafeSynchronousSearch);
}
var budget = new ReadExecutionBudget(database.OperationLimitsOptions, database.EvaluationClock, cancellationToken);
QueryResultBudgetPolicy.Constrain(budget, execution);
budget.Check();
using var reservation = database.AdmitQuery(cancellationToken);
return SearchCore(principalId, request, budget);
}
/// <summary>Awaits exact search and native projection settlement without blocking the caller's execution context.</summary>
/// <param name="principalId">Persisted principal identity.</param>
/// <param name="request">Search fields, query and result limit.</param>
/// <param name="cancellationToken">Caller cancellation for admission and the complete worker.</param>
/// <returns>Ranked and authorized documents after complete lease settlement.</returns>
public async Task<RankedDocument[]> SearchAsync(string principalId, SearchRequest request, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(request);
var budget = new ReadExecutionBudget(database.OperationLimitsOptions, database.EvaluationClock, cancellationToken);
QueryResultBudgetPolicy.Constrain(budget, execution);
budget.Check();
using var reservation = database.AdmitQuery(cancellationToken);
return await Task.Run(() => SearchCore(principalId, request, budget), cancellationToken).ConfigureAwait(false);
}
/// <summary>Executes graph scope, retrieval and expansion inside one authorized search cut.</summary>
/// <param name="principalId">Persisted caller identity.</param>
/// <param name="request">Versioned search and graph operators.</param>
/// <param name="cancellationToken">Caller cancellation for admission and the complete worker.</param>
/// <returns>Exact fused hits and optional separately projected graph context.</returns>
public async Task<GraphSearchResult> GraphSearchAsync(string principalId, GraphSearchRequest request,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(request);
var budget = new ReadExecutionBudget(database.OperationLimitsOptions, database.EvaluationClock, cancellationToken);
return await GraphSearchAsync(principalId, request, budget, cancellationToken).ConfigureAwait(false);
}
internal async Task<GraphSearchResult> GraphSearchAsync(string principalId, GraphSearchRequest request,
ReadExecutionBudget budget, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(request);
ArgumentNullException.ThrowIfNull(budget);
QueryResultBudgetPolicy.Constrain(budget, execution);
budget.Check();
using var reservation = database.AdmitQuery(cancellationToken);
return await Task.Run(() => GraphSearchExecutor.Execute(database, textProjection, principalId, request, budget, execution),
cancellationToken).ConfigureAwait(false);
}
private RankedDocument[] SearchCore(string principalId, SearchRequest request, ReadExecutionBudget budget)
{
budget.Check();
SearchRequestValidation.Validate(request, database.Limits, false, execution);
FilteredSearchEligibility.ValidateRequest(request.AllowedIds, database.Limits, budget);
var similarity = request.Vector is { } vector
? PreparedSimilarity.Create(vector.AsMemory(), request.Space!.Metric) : null;
FilteredSearchRequestSizer.EnsureBounded(request, database.Limits.MaxQueryBytes, budget);
var eligibility = FilteredSearchEligibility.Create(request.AllowedIds, budget);
return database.WithQueryView(principalId, request.Partition, request.Collection, (view, principal, resource) =>
{
if (request.Text is not null)
{
database.Authorization.RequireFieldUse(principal, resource, request.TextField!);
}
if (similarity is not null)
{
database.Authorization.Require(principal, request.Partition, request.Collection, Capability.VectorSearch);
database.Authorization.RequireFieldUse(principal, resource, request.VectorField!);
}
if (eligibility.IsEmpty)
{
return [];
}
budget.Check();
var fusion = new SearchRankFusion(request.FusionConstant, request.Limit, budget, request.Explain);
if (request.Text is not null)
{
fusion.AddBranch(FilteredSearchBranch.Apply(
SearchBranchExecution.RankText(database, textProjection, view, principal, resource, request, budget, execution),
eligibility, budget), request.TextWeight, SearchBranchKind.Text);
}
if (similarity is not null)
{
fusion.AddBranch(VectorRanker.Rank(database, view, principal, request, similarity, budget, eligibility), request.VectorWeight, SearchBranchKind.Vector);
}
return SearchBranchExecution.ProjectSelected(database, view, principal, resource, fusion.Select(), budget, fusion);
});
}
/// <summary>Computes the selected vector metric using the same SIMD and scalar grouping as search.</summary>
/// <param name="left">Finite query vector.</param>
/// <param name="right">Finite candidate vector.</param>
/// <param name="metric">Typed distance metric.</param>
/// <returns>Similarity score; Euclidean distance is negated.</returns>
public static double Similarity(float[] left, float[] right, DistanceMetric metric)
{
ArgumentNullException.ThrowIfNull(left);
var prepared = PreparedSimilarity.Create(left.AsMemory(), metric);
ArgumentNullException.ThrowIfNull(right);
return prepared.Score(right.AsMemory());
}
}