tercul-backend/pkg/search/bleve/bleveclient.go
Damir Mukimov 0f25c8645c
Add Bleve search integration with hybrid search capabilities
- Add Bleve client for keyword search functionality
- Integrate Bleve service into application builder
- Add BleveIndexPath configuration
- Update domain mappings for proper indexing
- Add comprehensive documentation and tests
2025-11-27 03:40:48 +01:00

166 lines
4.5 KiB
Go

package bleve
import (
"fmt"
"log"
"os"
"tercul/internal/domain"
blevelib "github.com/blevesearch/bleve/v2"
"gorm.io/gorm"
)
type BleveClient struct {
index blevelib.Index
}
// NewBleveClient initializes or opens a Bleve index at the given path.
func NewBleveClient(indexPath string) (*BleveClient, error) {
var index blevelib.Index
var err error
if _, err = os.Stat(indexPath); os.IsNotExist(err) {
// Create a new index if it doesn't exist
indexMapping := blevelib.NewIndexMapping()
index, err = blevelib.New(indexPath, indexMapping)
if err != nil {
return nil, err
}
log.Println("Created a new Bleve index at:", indexPath)
} else {
// Open an existing index
index, err = blevelib.Open(indexPath)
if err != nil {
return nil, err
}
log.Println("Opened an existing Bleve index at:", indexPath)
}
return &BleveClient{index: index}, nil
}
// AddTranslation indexes a single translation into the Bleve index.
func (bc *BleveClient) AddTranslation(translation domain.Translation) error {
// Create a structured document containing all relevant translation fields
document := map[string]interface{}{
"id": translation.ID,
"title": translation.Title,
"content": translation.Content,
"description": translation.Description,
"language": translation.Language,
"status": translation.Status,
"translatable_id": translation.TranslatableID,
"translatable_type": translation.TranslatableType,
"translator_id": func() uint {
if translation.TranslatorID != nil {
return *translation.TranslatorID
}
return 0
}(),
}
// Use the translation's ID as the unique document ID
docID := fmt.Sprintf("%d", translation.ID)
return bc.index.Index(docID, document)
}
// AddTranslations indexes all translations from the database into the Bleve index.
func (bc *BleveClient) AddTranslations(db *gorm.DB) error {
const batchSize = 50000
offset := 0
for {
// Fetch translations in batches
var translations []domain.Translation
err := db.Offset(offset).Limit(batchSize).Find(&translations).Error
if err != nil {
log.Printf("Error fetching translations from the database: %v", err)
return err
}
// Break if no more translations to process
if len(translations) == 0 {
break
}
// Create a Bleve batch for better indexing performance
batch := bc.index.NewBatch()
for _, translation := range translations {
// Create a structured document for each translation
document := map[string]interface{}{
"id": translation.ID,
"title": translation.Title,
"content": translation.Content,
"description": translation.Description,
"language": translation.Language,
"status": translation.Status,
"translatable_id": translation.TranslatableID,
"translatable_type": translation.TranslatableType,
"translator_id": func() uint {
if translation.TranslatorID != nil {
return *translation.TranslatorID
}
return 0
}(),
}
docID := fmt.Sprintf("%d", translation.ID)
err = batch.Index(docID, document)
if err != nil {
log.Printf("Error indexing translation ID %s: %v", docID, err)
}
}
// Commit the batch to the index
err = bc.index.Batch(batch)
if err != nil {
log.Printf("Error committing batch to the Bleve index: %v", err)
return err
}
log.Printf("Indexed %d translations into Bleve.", len(translations))
offset += batchSize
}
log.Println("All translations have been indexed into Bleve.")
return nil
}
// Search performs a search with multiple filters and a full-text query.
func (bc *BleveClient) Search(queryString string, filters map[string]string, size int) (*blevelib.SearchResult, error) {
// Create the main query for full-text search
mainQuery := blevelib.NewMatchQuery(queryString)
mainQuery.SetFuzziness(2)
// Create a boolean query
booleanQuery := blevelib.NewBooleanQuery()
// Add the main query to the "must" clause
booleanQuery.AddMust(mainQuery)
// Add filter queries to the "must" clause
for field, value := range filters {
termQuery := blevelib.NewTermQuery(value)
termQuery.SetField(field)
booleanQuery.AddMust(termQuery)
}
// Build the search request
searchRequest := blevelib.NewSearchRequest(booleanQuery)
searchRequest.Size = size
// Execute the search
results, err := bc.index.Search(searchRequest)
if err != nil {
log.Printf("Search failed: %v", err)
return nil, err
}
return results, nil
}
// Close closes the Bleve index.
func (bc *BleveClient) Close() error {
return bc.index.Close()
}