diff --git a/backend/alignment.go b/backend/alignment.go index 7a4b9c9..aed2a9c 100644 --- a/backend/alignment.go +++ b/backend/alignment.go @@ -6,6 +6,7 @@ import ( "bytes" "compress/gzip" "encoding/json" + "fmt" "io" "os" "path/filepath" @@ -212,7 +213,7 @@ func ReadAlignment[T any](reader io.Reader) ([]T, error) { return results, nil } -func ReadQuery(id Id, entries []int64, jobsbase string) ([]FastaEntry, error) { +func ReadQueryByIds(id Id, ids []int64, jobsbase string) ([]FastaEntry, error) { base := filepath.Join(jobsbase, string(id)) query := filepath.Join(base, "query") seqReader := Reader[uint32]{} @@ -228,11 +229,43 @@ func ReadQuery(id Id, entries []int64, jobsbase string) ([]FastaEntry, error) { return fasta, err } - for _, entry := range entries { - sequence := strings.TrimSpace(seqReader.Data(entry)) + for _, id := range ids { + sequence := strings.TrimSpace(seqReader.Data(id)) + header := strings.TrimSpace(hdrReader.Data(id)) + fasta = append(fasta, FastaEntry{header, sequence}) + } + seqReader.Delete() + hdrReader.Delete() + return fasta, nil +} + +func ReadQueryByKeys(id Id, keys []uint32, jobsbase string) ([]FastaEntry, error) { + base := filepath.Join(jobsbase, string(id)) + query := filepath.Join(base, "query") + seqReader := Reader[uint32]{} + err := seqReader.Make(dbpaths(query)) + fasta := make([]FastaEntry, 0) + if err != nil { + return fasta, err + } + hdrReader := Reader[uint32]{} + err = hdrReader.Make(dbpaths(query + "_h")) + if err != nil { seqReader.Delete() - header := strings.TrimSpace(hdrReader.Data(entry)) - hdrReader.Delete() + return fasta, err + } + + for _, key := range keys { + id, found := seqReader.Id(key) + sequence := "" + if found { + sequence = strings.TrimSpace(seqReader.Data(id)) + } + id, found = hdrReader.Id(key) + header := "" + if found { + header = strings.TrimSpace(hdrReader.Data(id)) + } fasta = append(fasta, FastaEntry{header, sequence}) } seqReader.Delete() @@ -240,10 +273,21 @@ func ReadQuery(id Id, entries []int64, jobsbase string) ([]FastaEntry, error) { return fasta, nil } -func ReadAlignments[T any](id Id, entries []int64, databases []string, jobsbase string) ([]SearchResult, error) { +func ReadAlignments[T any, U interface{ ~uint32 | ~int64 }](id Id, entries []U, databases []string, jobsbase string) ([]SearchResult, error) { base := filepath.Join(jobsbase, string(id)) reader := Reader[uint32]{} res := make([]SearchResult, 0) + + var lookupByKey bool + switch any(entries[0]).(type) { + case uint32: + lookupByKey = true + case int64: + lookupByKey = false + default: + return nil, fmt.Errorf("unsupported type: %T", entries[0]) + } + for _, db := range databases { name := filepath.Join(filepath.Clean(base), "alis_"+db) err := reader.Make(dbpaths(name)) @@ -252,7 +296,19 @@ func ReadAlignments[T any](id Id, entries []int64, databases []string, jobsbase } all := make([][]T, 0) for _, entry := range entries { - data := strings.NewReader(reader.Data(entry)) + var body string + if lookupByKey { + alnKey := any(entry).(uint32) + alnId, found := reader.Id(alnKey) + if !found { + reader.Delete() + return nil, fmt.Errorf("missing key: %T", alnKey) + } + body = reader.Data(alnId) + } else { + body = reader.Data(any(entry).(int64)) + } + data := strings.NewReader(body) results, err := ReadAlignment[T](data) if err != nil { reader.Delete() @@ -272,15 +328,15 @@ func ReadAlignments[T any](id Id, entries []int64, databases []string, jobsbase } func Alignments(id Id, entry []int64, databases []string, jobsbase string) ([]SearchResult, error) { - return ReadAlignments[AlignmentEntry](id, entry, databases, jobsbase) + return ReadAlignments[AlignmentEntry, int64](id, entry, databases, jobsbase) } func FSAlignments(id Id, entry []int64, databases []string, jobsbase string) ([]SearchResult, error) { - return ReadAlignments[FoldseekAlignmentEntry](id, entry, databases, jobsbase) + return ReadAlignments[FoldseekAlignmentEntry, int64](id, entry, databases, jobsbase) } -func ComplexAlignments(id Id, entry []int64, databases []string, jobsbase string) ([]SearchResult, error) { - return ReadAlignments[ComplexAlignmentEntry](id, entry, databases, jobsbase) +func ComplexAlignments(id Id, entry []uint32, databases []string, jobsbase string) ([]SearchResult, error) { + return ReadAlignments[ComplexAlignmentEntry, uint32](id, entry, databases, jobsbase) } func addFile(tw *tar.Writer, path string) error { diff --git a/backend/server.go b/backend/server.go index 978f43b..2bc5f0c 100644 --- a/backend/server.go +++ b/backend/server.go @@ -770,12 +770,11 @@ func server(jobsystem JobSystem, config ConfigRoot) { var fasta []FastaEntry var results []SearchResult var mode string - var ids []int64 isFoldseek := false switch job := request.Job.(type) { case SearchJob: mode = job.Mode - ids = []int64{id} + ids := []int64{id} databases := job.Database if database != "" { if isIn(database, job.Database) == -1 { @@ -789,9 +788,14 @@ func server(jobsystem JobSystem, config ConfigRoot) { http.Error(w, err.Error(), http.StatusBadRequest) return } + fasta, err = ReadQueryByIds(ticket.Id, ids, config.Paths.Results) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } case StructureSearchJob: mode = job.Mode - ids = []int64{id} + ids := []int64{id} isFoldseek = true databases := job.Database if database != "" { @@ -806,6 +810,11 @@ func server(jobsystem JobSystem, config ConfigRoot) { http.Error(w, err.Error(), http.StatusBadRequest) return } + fasta, err = ReadQueryByIds(ticket.Id, ids, config.Paths.Results) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } case ComplexSearchJob: mode = job.Mode result, err := Lookup(ticket.Id, 0, math.MaxInt32, config.Paths.Results, false) @@ -813,10 +822,10 @@ func server(jobsystem JobSystem, config ConfigRoot) { http.Error(w, err.Error(), http.StatusBadRequest) return } - ids = make([]int64, 0) + keys := make([]uint32, 0) for _, lookup := range result.Lookup { if lookup.Set == uint32(id) { - ids = append(ids, int64(lookup.Id)) + keys = append(keys, uint32(lookup.Id)) } } isFoldseek = true @@ -828,7 +837,12 @@ func server(jobsystem JobSystem, config ConfigRoot) { } databases = []string{database} } - results, err = ComplexAlignments(ticket.Id, ids, databases, config.Paths.Results) + results, err = ComplexAlignments(ticket.Id, keys, databases, config.Paths.Results) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + fasta, err = ReadQueryByKeys(ticket.Id, keys, config.Paths.Results) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return @@ -918,11 +932,6 @@ func server(jobsystem JobSystem, config ConfigRoot) { } } - fasta, err = ReadQuery(ticket.Id, ids, config.Paths.Results) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } type AlignmentModeResponse struct { Queries []FastaEntry `json:"queries"` Mode string `json:"mode"`