mirror of
https://github.com/foomo/typesense.git
synced 2025-10-16 12:45:37 +00:00
Merge pull request #2 from foomo/feature/support-filter-by-multiple-values
feature: support filter by multiple values, pass uri map to documentProviders
This commit is contained in:
commit
21da1fdc84
2
go.mod
2
go.mod
@ -1,6 +1,6 @@
|
||||
module github.com/foomo/typesense
|
||||
|
||||
go 1.23.4
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/foomo/contentserver v1.11.2
|
||||
|
||||
@ -270,10 +270,10 @@ func (b *BaseAPI[indexDocument, returnType]) SimpleSearch(
|
||||
ctx context.Context,
|
||||
index pkgtypesense.IndexID,
|
||||
q string,
|
||||
filterBy map[string]string,
|
||||
filterBy map[string][]string,
|
||||
page, perPage int,
|
||||
sortBy string,
|
||||
) ([]returnType, pkgtypesense.Scores, error) {
|
||||
) ([]returnType, pkgtypesense.Scores, int, error) {
|
||||
// Call buildSearchParams but also set QueryBy explicitly
|
||||
parameters := buildSearchParams(q, filterBy, page, perPage, sortBy)
|
||||
parameters.QueryBy = pointer.String("title")
|
||||
@ -282,29 +282,41 @@ func (b *BaseAPI[indexDocument, returnType]) SimpleSearch(
|
||||
}
|
||||
|
||||
// ExpertSearch will perform a search operation on the given index
|
||||
// it will return the documents and the scores
|
||||
// it will return the documents, scores, and totalResults
|
||||
func (b *BaseAPI[indexDocument, returnType]) ExpertSearch(
|
||||
ctx context.Context,
|
||||
indexID pkgtypesense.IndexID,
|
||||
parameters *api.SearchCollectionParams,
|
||||
) ([]returnType, pkgtypesense.Scores, error) {
|
||||
) ([]returnType, pkgtypesense.Scores, int, error) {
|
||||
if parameters == nil {
|
||||
b.l.Error("Search parameters are nil")
|
||||
return nil, nil, errors.New("search parameters cannot be nil")
|
||||
return nil, nil, 0, errors.New("search parameters cannot be nil")
|
||||
}
|
||||
|
||||
collectionName := string(indexID) // digital-bks-at-de
|
||||
searchResponse, err := b.client.Collection(collectionName).Documents().Search(ctx, parameters)
|
||||
if err != nil {
|
||||
b.l.Error("Failed to perform search", zap.String("index", collectionName), zap.Error(err))
|
||||
return nil, nil, err
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
// Extract totalResults from the search response
|
||||
totalResults := *searchResponse.Found
|
||||
|
||||
// Ensure Hits is not empty before proceeding
|
||||
if searchResponse.Hits == nil || len(*searchResponse.Hits) == 0 {
|
||||
b.l.Warn("Search response contains no hits", zap.String("index", collectionName))
|
||||
return nil, nil, totalResults, nil
|
||||
}
|
||||
|
||||
// Parse search results
|
||||
var results = make([]returnType, 0, len(*searchResponse.Hits))
|
||||
results := make([]returnType, len(*searchResponse.Hits))
|
||||
scores := make(pkgtypesense.Scores)
|
||||
|
||||
for _, hit := range *searchResponse.Hits {
|
||||
for i, hit := range *searchResponse.Hits {
|
||||
if hit.Document == nil {
|
||||
b.l.Warn("Hit document is nil", zap.String("index", collectionName))
|
||||
continue
|
||||
}
|
||||
|
||||
docMap := *hit.Document
|
||||
|
||||
// Extract document ID safely
|
||||
@ -322,7 +334,7 @@ func (b *BaseAPI[indexDocument, returnType]) ExpertSearch(
|
||||
continue
|
||||
}
|
||||
|
||||
results = append(results, doc)
|
||||
results[i] = doc
|
||||
index := 0
|
||||
if hit.TextMatchInfo != nil && hit.TextMatchInfo.Score != nil {
|
||||
if score, err := strconv.Atoi(*hit.TextMatchInfo.Score); err == nil {
|
||||
@ -341,7 +353,8 @@ func (b *BaseAPI[indexDocument, returnType]) ExpertSearch(
|
||||
b.l.Info("Search completed",
|
||||
zap.String("index", collectionName),
|
||||
zap.Int("results_count", len(results)),
|
||||
zap.Int("total_results", totalResults),
|
||||
)
|
||||
|
||||
return results, scores, nil
|
||||
return results, scores, totalResults, nil
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@ import (
|
||||
// for the typesense search API without any knowledge of the typesense API
|
||||
func buildSearchParams(
|
||||
q string,
|
||||
filterBy map[string]string,
|
||||
filterBy map[string][]string,
|
||||
page, perPage int,
|
||||
sortBy string,
|
||||
) *api.SearchCollectionParams {
|
||||
@ -36,31 +36,33 @@ func buildSearchParams(
|
||||
return parameters
|
||||
}
|
||||
|
||||
func formatFilterQuery(filterBy map[string]string) string {
|
||||
func formatFilterQuery(filterBy map[string][]string) string {
|
||||
if filterBy == nil {
|
||||
return ""
|
||||
}
|
||||
filterByString := []string{}
|
||||
for key, value := range filterBy {
|
||||
filterByString = append(filterByString, key+":="+value)
|
||||
|
||||
var filterClauses []string
|
||||
for key, values := range filterBy {
|
||||
if len(values) == 1 {
|
||||
// Single value → Use `:=` operator
|
||||
filterClauses = append(filterClauses, fmt.Sprintf("%s:=\"%s\"", key, values[0]))
|
||||
} else {
|
||||
// Multiple values → Use `["val1","val2"]` array syntax
|
||||
formattedValues := []string{}
|
||||
for _, v := range values {
|
||||
formattedValues = append(formattedValues, fmt.Sprintf("\"%s\"", v))
|
||||
}
|
||||
filterClauses = append(filterClauses, fmt.Sprintf("%s:[%s]", key, strings.Join(formattedValues, ",")))
|
||||
}
|
||||
}
|
||||
return strings.Join(filterByString, "&&")
|
||||
|
||||
return strings.Join(filterClauses, " && ")
|
||||
}
|
||||
|
||||
func (b *BaseAPI[indexDocument, returnType]) generateRevisionID() pkgtypesense.RevisionID {
|
||||
return pkgtypesense.RevisionID(time.Now().Format("2006-01-02-15-04")) // "YYYY-MM-DD-HH-MM"
|
||||
}
|
||||
|
||||
func getLatestRevisionID(revisions map[pkgtypesense.IndexID]pkgtypesense.RevisionID) pkgtypesense.RevisionID {
|
||||
var latest pkgtypesense.RevisionID
|
||||
for _, rev := range revisions {
|
||||
if rev > latest {
|
||||
latest = rev
|
||||
}
|
||||
}
|
||||
return latest
|
||||
}
|
||||
|
||||
func formatCollectionName(indexID pkgtypesense.IndexID, revisionID pkgtypesense.RevisionID) string {
|
||||
return fmt.Sprintf("%s-%s", indexID, revisionID)
|
||||
}
|
||||
|
||||
@ -40,12 +40,18 @@ func (c ContentServer[indexDocument]) Provide(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
urlsByIDs, err := c.fetchURLsByDocumentIDs(ctx, indexID, documentInfos)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
documents := make([]*indexDocument, len(documentInfos))
|
||||
for index, documentInfo := range documentInfos {
|
||||
if documentProvider, ok := c.documentProviderFuncs[documentInfo.DocumentType]; !ok {
|
||||
c.l.Warn("no document provider available for document type", zap.String("documentType", string(documentInfo.DocumentType)))
|
||||
} else {
|
||||
document, err := documentProvider(ctx, indexID, documentInfo.DocumentID)
|
||||
document, err := documentProvider(ctx, indexID, documentInfo.DocumentID, urlsByIDs)
|
||||
if err != nil {
|
||||
c.l.Error(
|
||||
"index document not created",
|
||||
@ -113,3 +119,31 @@ func createFlatRepoNodeMap(node *content.RepoNode, nodeMap map[string]*content.R
|
||||
}
|
||||
return nodeMap
|
||||
}
|
||||
|
||||
func (c ContentServer[indexDocument]) fetchURLsByDocumentIDs(
|
||||
ctx context.Context,
|
||||
indexID typesense.IndexID,
|
||||
documentInfos []typesense.DocumentInfo,
|
||||
) (map[typesense.DocumentID]string, error) {
|
||||
ids := make([]string, len(documentInfos))
|
||||
|
||||
for i, documentInfo := range documentInfos {
|
||||
ids[i] = string(documentInfo.DocumentID)
|
||||
}
|
||||
|
||||
uriMap, err := c.contentserverClient.GetURIs(ctx, string(indexID), ids)
|
||||
if err != nil {
|
||||
c.l.Error("failed to get URIs", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return convertMapStringToDocumentID(uriMap), nil
|
||||
}
|
||||
|
||||
func convertMapStringToDocumentID(input map[string]string) map[typesense.DocumentID]string {
|
||||
output := make(map[typesense.DocumentID]string, len(input))
|
||||
for key, value := range input {
|
||||
output[typesense.DocumentID(key)] = value
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
@ -21,11 +21,11 @@ type API[indexDocument any, returnType any] interface {
|
||||
ctx context.Context,
|
||||
index IndexID,
|
||||
q string,
|
||||
filterBy map[string]string,
|
||||
filterBy map[string][]string,
|
||||
page, perPage int,
|
||||
sortBy string,
|
||||
) ([]returnType, Scores, error)
|
||||
ExpertSearch(ctx context.Context, index IndexID, parameters *api.SearchCollectionParams) ([]returnType, Scores, error)
|
||||
) ([]returnType, Scores, int, error)
|
||||
ExpertSearch(ctx context.Context, index IndexID, parameters *api.SearchCollectionParams) ([]returnType, Scores, int, error)
|
||||
Healthz(ctx context.Context) error
|
||||
Indices() ([]IndexID, error)
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user