Initial QSfera import
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
package bleve
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/blevesearch/bleve/v2"
|
||||
"github.com/blevesearch/bleve/v2/search/query"
|
||||
storageProvider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storagespace"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/qsfera/server/pkg/log"
|
||||
"github.com/qsfera/server/services/search/pkg/search"
|
||||
|
||||
searchMessage "github.com/qsfera/server/protogen/gen/qsfera/messages/search/v0"
|
||||
searchService "github.com/qsfera/server/protogen/gen/qsfera/services/search/v0"
|
||||
searchQuery "github.com/qsfera/server/services/search/pkg/query"
|
||||
)
|
||||
|
||||
const defaultBatchSize = 50
|
||||
|
||||
var _ search.Engine = (*Backend)(nil) // ensure Backend implements Engine
|
||||
|
||||
type Backend struct {
|
||||
index bleve.Index
|
||||
queryCreator searchQuery.Creator[query.Query]
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func NewBackend(index bleve.Index, queryCreator searchQuery.Creator[query.Query], log log.Logger) *Backend {
|
||||
return &Backend{
|
||||
index: index,
|
||||
queryCreator: queryCreator,
|
||||
log: log,
|
||||
}
|
||||
}
|
||||
|
||||
// Search executes a search request operation within the index.
|
||||
// Returns a SearchIndexResponse object or an error.
|
||||
func (b *Backend) Search(_ context.Context, sir *searchService.SearchIndexRequest) (*searchService.SearchIndexResponse, error) {
|
||||
createdQuery, err := b.queryCreator.Create(sir.Query)
|
||||
if err != nil {
|
||||
if searchQuery.IsValidationError(err) {
|
||||
return nil, errtypes.BadRequest(err.Error())
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
q := bleve.NewConjunctionQuery(
|
||||
// Skip documents that have been marked as deleted
|
||||
&query.BoolFieldQuery{
|
||||
Bool: false,
|
||||
FieldVal: "Deleted",
|
||||
},
|
||||
createdQuery,
|
||||
)
|
||||
|
||||
if sir.Ref != nil {
|
||||
q.Conjuncts = append(
|
||||
q.Conjuncts,
|
||||
&query.TermQuery{
|
||||
FieldVal: "RootID",
|
||||
Term: storagespace.FormatResourceID(
|
||||
&storageProvider.ResourceId{
|
||||
StorageId: sir.Ref.GetResourceId().GetStorageId(),
|
||||
SpaceId: sir.Ref.GetResourceId().GetSpaceId(),
|
||||
OpaqueId: sir.Ref.GetResourceId().GetOpaqueId(),
|
||||
},
|
||||
),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
bleveReq := bleve.NewSearchRequest(q)
|
||||
bleveReq.Highlight = bleve.NewHighlight()
|
||||
|
||||
switch {
|
||||
case sir.PageSize == -1:
|
||||
bleveReq.Size = math.MaxInt
|
||||
case sir.PageSize == 0:
|
||||
bleveReq.Size = 200
|
||||
default:
|
||||
bleveReq.Size = int(sir.PageSize)
|
||||
}
|
||||
|
||||
bleveReq.Fields = []string{"*"}
|
||||
res, err := b.index.Search(bleveReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
matches := make([]*searchMessage.Match, 0, len(res.Hits))
|
||||
totalMatches := res.Total
|
||||
for _, hit := range res.Hits {
|
||||
if sir.Ref != nil {
|
||||
hitPath := strings.TrimSuffix(getFieldValue[string](hit.Fields, "Path"), "/")
|
||||
requestedPath := utils.MakeRelativePath(sir.Ref.Path)
|
||||
isRoot := hitPath == requestedPath
|
||||
|
||||
if !isRoot && requestedPath != "." && !strings.HasPrefix(hitPath, requestedPath+"/") {
|
||||
totalMatches--
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
rootID, err := storagespace.ParseID(getFieldValue[string](hit.Fields, "RootID"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rID, err := storagespace.ParseID(getFieldValue[string](hit.Fields, "ID"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pID, _ := storagespace.ParseID(getFieldValue[string](hit.Fields, "ParentID"))
|
||||
match := &searchMessage.Match{
|
||||
Score: float32(hit.Score),
|
||||
Entity: &searchMessage.Entity{
|
||||
Ref: &searchMessage.Reference{
|
||||
ResourceId: resourceIDtoSearchID(rootID),
|
||||
Path: getFieldValue[string](hit.Fields, "Path"),
|
||||
},
|
||||
Id: resourceIDtoSearchID(rID),
|
||||
Name: getFieldValue[string](hit.Fields, "Name"),
|
||||
ParentId: resourceIDtoSearchID(pID),
|
||||
Size: uint64(getFieldValue[float64](hit.Fields, "Size")),
|
||||
Type: uint64(getFieldValue[float64](hit.Fields, "Type")),
|
||||
MimeType: getFieldValue[string](hit.Fields, "MimeType"),
|
||||
Deleted: getFieldValue[bool](hit.Fields, "Deleted"),
|
||||
Tags: getFieldSliceValue[string](hit.Fields, "Tags"),
|
||||
Favorites: getFieldSliceValue[string](hit.Fields, "Favorites"),
|
||||
Highlights: getFragmentValue(hit.Fragments, "Content", 0),
|
||||
Audio: getAudioValue[searchMessage.Audio](hit.Fields),
|
||||
Image: getImageValue[searchMessage.Image](hit.Fields),
|
||||
Location: getLocationValue[searchMessage.GeoCoordinates](hit.Fields),
|
||||
Photo: getPhotoValue[searchMessage.Photo](hit.Fields),
|
||||
},
|
||||
}
|
||||
|
||||
if mtime, err := time.Parse(time.RFC3339, getFieldValue[string](hit.Fields, "Mtime")); err == nil {
|
||||
match.Entity.LastModifiedTime = ×tamppb.Timestamp{Seconds: mtime.Unix(), Nanos: int32(mtime.Nanosecond())}
|
||||
}
|
||||
|
||||
matches = append(matches, match)
|
||||
}
|
||||
|
||||
return &searchService.SearchIndexResponse{
|
||||
Matches: matches,
|
||||
TotalMatches: int32(totalMatches),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *Backend) DocCount() (uint64, error) {
|
||||
return b.index.DocCount()
|
||||
}
|
||||
|
||||
func (b *Backend) Upsert(id string, r search.Resource) error {
|
||||
batch, err := b.NewBatch(defaultBatchSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := batch.Upsert(id, r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return batch.Push()
|
||||
}
|
||||
|
||||
func (b *Backend) Move(rootID, parentID, location string) error {
|
||||
batch, err := b.NewBatch(defaultBatchSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := batch.Move(rootID, parentID, location); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return batch.Push()
|
||||
}
|
||||
|
||||
func (b *Backend) Delete(id string) error {
|
||||
batch, err := b.NewBatch(defaultBatchSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := batch.Delete(id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return batch.Push()
|
||||
}
|
||||
|
||||
func (b *Backend) Restore(id string) error {
|
||||
batch, err := b.NewBatch(defaultBatchSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := batch.Restore(id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return batch.Push()
|
||||
}
|
||||
|
||||
func (b *Backend) Purge(id string, onlyDeleted bool) error {
|
||||
batch, err := b.NewBatch(defaultBatchSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := batch.Purge(id, onlyDeleted); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return batch.Push()
|
||||
}
|
||||
|
||||
func (b *Backend) NewBatch(size int) (search.BatchOperator, error) {
|
||||
return NewBatch(b.index, size)
|
||||
}
|
||||
Reference in New Issue
Block a user