This commit is contained in:
Krispire Admin
2026-05-30 02:33:57 +05:30
parent 5e0a484239
commit 97422c1ef5
21 changed files with 1720 additions and 82 deletions
+44 -10
View File
@@ -147,6 +147,12 @@ func (h *SearchHandler) Search(w http.ResponseWriter, r *http.Request) {
limit = 100
}
// `exact=1` (or "true") disables typo tolerance + prefix expansion so the
// query only matches literal keywords. Stopword filtering still applies —
// "letter to mukunda" with exact=1 still drops "letter"/"to" so the search
// is effectively the literal word "mukunda" with no fuzzy variants.
exact := parseBoolParam(r.URL.Query().Get("exact"))
ctx := r.Context()
var results []searchResultItem
@@ -154,13 +160,13 @@ func (h *SearchHandler) Search(w http.ResponseWriter, r *http.Request) {
if searchType == "all" {
// Search all 9 collections
results, totalFound = h.multiSearch(ctx, nil, query, page, limit)
results, totalFound = h.multiSearch(ctx, nil, query, page, limit, exact)
} else if len(requestedTypes) == 1 {
// Single collection — use singleSearch for efficiency
results, totalFound = h.singleSearch(ctx, requestedTypes[0], query, page, limit)
results, totalFound = h.singleSearch(ctx, requestedTypes[0], query, page, limit, exact)
} else {
// Multiple collections — use multiSearch with a subset
results, totalFound = h.multiSearch(ctx, requestedTypes, query, page, limit)
results, totalFound = h.multiSearch(ctx, requestedTypes, query, page, limit, exact)
}
totalPages := int(math.Ceil(float64(totalFound) / float64(limit)))
@@ -181,8 +187,10 @@ func (h *SearchHandler) Search(w http.ResponseWriter, r *http.Request) {
// multiSearch searches across all 9 collections using Typesense multi_search,
// merges results by text_match score, and returns a single paginated list.
// multiSearch searches across multiple collections using Typesense multi_search.
// If collections is nil, searches all 9 collections.
func (h *SearchHandler) multiSearch(ctx context.Context, collections []string, query string, page, limit int) ([]searchResultItem, int) {
// If collections is nil, searches all 9 collections. When `exact` is true,
// typo tolerance and prefix expansion are turned off so only literal-keyword
// matches come back.
func (h *SearchHandler) multiSearch(ctx context.Context, collections []string, query string, page, limit int, exact bool) ([]searchResultItem, int) {
if collections == nil {
collections = []string{
"verses", "prose", "letters", "transcriptions", "glossary",
@@ -193,6 +201,7 @@ func (h *SearchHandler) multiSearch(ctx context.Context, collections []string, q
exhaustive := true
prioritizeExact := true
prioritizeTokenPos := true
prefix, numTypos := fuzzinessKnobs(exact)
searches := make([]api.MultiSearchCollectionParameters, 0, len(collections))
for _, col := range collections {
queryBy := collectionQueryByFields[col]
@@ -205,8 +214,8 @@ func (h *SearchHandler) multiSearch(ctx context.Context, collections []string, q
QueryBy: stringPtr(queryBy),
PerPage: &perPage,
Page: &pg,
Prefix: stringPtr("true"),
NumTypos: stringPtr("2"),
Prefix: stringPtr(prefix),
NumTypos: stringPtr(numTypos),
ExhaustiveSearch: &exhaustive,
Stopwords: stringPtr(searchStopwordsSet),
// Boost docs whose tokens are exact matches and appear early in the field
@@ -264,7 +273,7 @@ func (h *SearchHandler) multiSearch(ctx context.Context, collections []string, q
}
// singleSearch searches a single collection.
func (h *SearchHandler) singleSearch(ctx context.Context, collection, query string, page, limit int) ([]searchResultItem, int) {
func (h *SearchHandler) singleSearch(ctx context.Context, collection, query string, page, limit int, exact bool) ([]searchResultItem, int) {
queryBy, ok := collectionQueryByFields[collection]
if !ok {
return []searchResultItem{}, 0
@@ -273,13 +282,14 @@ func (h *SearchHandler) singleSearch(ctx context.Context, collection, query stri
exhaustive := true
prioritizeExact := true
prioritizeTokenPos := true
prefix, numTypos := fuzzinessKnobs(exact)
params := &api.SearchCollectionParams{
Q: stringPtr(query),
QueryBy: stringPtr(queryBy),
Page: &page,
PerPage: &limit,
Prefix: stringPtr("true"),
NumTypos: stringPtr("2"),
Prefix: stringPtr(prefix),
NumTypos: stringPtr(numTypos),
ExhaustiveSearch: &exhaustive,
Stopwords: stringPtr(searchStopwordsSet),
// Boost docs whose tokens are exact matches and appear early in the field
@@ -534,3 +544,27 @@ func parseIntParam(s string, defaultVal int) int {
func stringPtr(s string) *string {
return &s
}
// parseBoolParam returns true for "1", "true", "yes", "on" (case-insensitive).
// Everything else (including the empty string) is false.
func parseBoolParam(s string) bool {
switch strings.ToLower(strings.TrimSpace(s)) {
case "1", "true", "yes", "on":
return true
}
return false
}
// fuzzinessKnobs maps the high-level `exact` flag to the two Typesense params
// that actually disable fuzzy matching:
//
// exact=false → prefix=true, num_typos=2 (default forgiving search)
// exact=true → prefix=false, num_typos=0 (literal-keyword only)
//
// Stopword filtering is left unchanged in both modes.
func fuzzinessKnobs(exact bool) (prefix, numTypos string) {
if exact {
return "false", "0"
}
return "true", "2"
}