mirror of
https://github.com/anchore/syft.git
synced 2026-11-01 23:16:30 +01:00
fix: SPDX output performance with many relationships (#3053)
This commit is contained in:
+149
-56
@@ -1,88 +1,181 @@
|
||||
package relationship
|
||||
|
||||
import (
|
||||
"github.com/scylladb/go-set/strset"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
)
|
||||
|
||||
// Index indexes relationships, preventing duplicates
|
||||
type Index struct {
|
||||
typesByFromTo map[artifact.ID]map[artifact.ID]*strset.Set
|
||||
existing []artifact.Relationship
|
||||
additional []artifact.Relationship
|
||||
all []*sortableRelationship
|
||||
fromID map[artifact.ID]*mappedRelationships
|
||||
toID map[artifact.ID]*mappedRelationships
|
||||
}
|
||||
|
||||
func NewIndex(existing ...artifact.Relationship) *Index {
|
||||
r := &Index{
|
||||
typesByFromTo: make(map[artifact.ID]map[artifact.ID]*strset.Set),
|
||||
}
|
||||
r.TrackAll(existing...)
|
||||
return r
|
||||
// NewIndex returns a new relationship Index
|
||||
func NewIndex(relationships ...artifact.Relationship) *Index {
|
||||
out := Index{}
|
||||
out.Add(relationships...)
|
||||
return &out
|
||||
}
|
||||
|
||||
func (i *Index) track(r artifact.Relationship) bool {
|
||||
fromID := r.From.ID()
|
||||
if _, ok := i.typesByFromTo[fromID]; !ok {
|
||||
i.typesByFromTo[fromID] = make(map[artifact.ID]*strset.Set)
|
||||
// Add adds all the given relationships to the index, without adding duplicates
|
||||
func (i *Index) Add(relationships ...artifact.Relationship) {
|
||||
if i.fromID == nil {
|
||||
i.fromID = map[artifact.ID]*mappedRelationships{}
|
||||
}
|
||||
if i.toID == nil {
|
||||
i.toID = map[artifact.ID]*mappedRelationships{}
|
||||
}
|
||||
|
||||
toID := r.To.ID()
|
||||
if _, ok := i.typesByFromTo[fromID][toID]; !ok {
|
||||
i.typesByFromTo[fromID][toID] = strset.New()
|
||||
// store appropriate indexes for stable ordering to minimize ID() calls
|
||||
for _, r := range relationships {
|
||||
// prevent duplicates
|
||||
if i.Contains(r) {
|
||||
continue
|
||||
}
|
||||
|
||||
fromID := r.From.ID()
|
||||
toID := r.To.ID()
|
||||
|
||||
relationship := &sortableRelationship{
|
||||
from: fromID,
|
||||
to: toID,
|
||||
relationship: r,
|
||||
}
|
||||
|
||||
// add to all relationships
|
||||
i.all = append(i.all, relationship)
|
||||
|
||||
// add from -> to mapping
|
||||
mapped := i.fromID[fromID]
|
||||
if mapped == nil {
|
||||
mapped = &mappedRelationships{}
|
||||
i.fromID[fromID] = mapped
|
||||
}
|
||||
mapped.add(toID, relationship)
|
||||
|
||||
// add to -> from mapping
|
||||
mapped = i.toID[toID]
|
||||
if mapped == nil {
|
||||
mapped = &mappedRelationships{}
|
||||
i.toID[toID] = mapped
|
||||
}
|
||||
mapped.add(fromID, relationship)
|
||||
}
|
||||
|
||||
var exists bool
|
||||
if i.typesByFromTo[fromID][toID].Has(string(r.Type)) {
|
||||
exists = true
|
||||
}
|
||||
|
||||
i.typesByFromTo[fromID][toID].Add(string(r.Type))
|
||||
|
||||
return !exists
|
||||
}
|
||||
|
||||
// Track this relationship as "exists" in the index (this is used to prevent duplicate relationships from being added).
|
||||
// returns true if the relationship is new to the index, false otherwise.
|
||||
func (i *Index) Track(r artifact.Relationship) bool {
|
||||
unique := i.track(r)
|
||||
if unique {
|
||||
i.existing = append(i.existing, r)
|
||||
}
|
||||
return unique
|
||||
// From returns all relationships from the given identifiable, with specified types
|
||||
func (i *Index) From(identifiable artifact.Identifiable, types ...artifact.RelationshipType) []artifact.Relationship {
|
||||
return toSortedSlice(fromMapped(i.fromID, identifiable), types)
|
||||
}
|
||||
|
||||
// Add a new relationship to the index, returning true if the relationship is new to the index, false otherwise (thus is a duplicate).
|
||||
func (i *Index) Add(r artifact.Relationship) bool {
|
||||
if i.track(r) {
|
||||
i.additional = append(i.additional, r)
|
||||
return true
|
||||
// To returns all relationships to the given identifiable, with specified types
|
||||
func (i *Index) To(identifiable artifact.Identifiable, types ...artifact.RelationshipType) []artifact.Relationship {
|
||||
return toSortedSlice(fromMapped(i.toID, identifiable), types)
|
||||
}
|
||||
|
||||
// References returns all relationships that reference to or from the given identifiable
|
||||
func (i *Index) References(identifiable artifact.Identifiable, types ...artifact.RelationshipType) []artifact.Relationship {
|
||||
return toSortedSlice(append(fromMapped(i.fromID, identifiable), fromMapped(i.toID, identifiable)...), types)
|
||||
}
|
||||
|
||||
// Coordinates returns all coordinates for the provided identifiable for provided relationship types
|
||||
// If no types are provided, all relationship types are considered.
|
||||
func (i *Index) Coordinates(identifiable artifact.Identifiable, types ...artifact.RelationshipType) []file.Coordinates {
|
||||
var coordinates []file.Coordinates
|
||||
for _, relationship := range i.References(identifiable, types...) {
|
||||
cords := extractCoordinates(relationship)
|
||||
coordinates = append(coordinates, cords...)
|
||||
}
|
||||
return coordinates
|
||||
}
|
||||
|
||||
// Contains indicates the relationship is present in this index
|
||||
func (i *Index) Contains(r artifact.Relationship) bool {
|
||||
if mapped := i.fromID[r.From.ID()]; mapped != nil {
|
||||
if ids := mapped.typeMap[r.Type]; ids != nil {
|
||||
return ids[r.To.ID()] != nil
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (i *Index) TrackAll(rs ...artifact.Relationship) {
|
||||
for _, r := range rs {
|
||||
i.Track(r)
|
||||
// All returns a sorted set of relationships matching all types, or all relationships if no types specified
|
||||
func (i *Index) All(types ...artifact.RelationshipType) []artifact.Relationship {
|
||||
return toSortedSlice(i.all, types)
|
||||
}
|
||||
|
||||
func fromMapped(idMap map[artifact.ID]*mappedRelationships, identifiable artifact.Identifiable) []*sortableRelationship {
|
||||
if identifiable == nil || idMap == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Index) AddAll(rs ...artifact.Relationship) {
|
||||
for _, r := range rs {
|
||||
i.Add(r)
|
||||
mapped := idMap[identifiable.ID()]
|
||||
if mapped == nil {
|
||||
return nil
|
||||
}
|
||||
return mapped.allRelated
|
||||
}
|
||||
|
||||
func (i *Index) NewRelationships() []artifact.Relationship {
|
||||
return i.additional
|
||||
func toSortedSlice(relationships []*sortableRelationship, types []artifact.RelationshipType) []artifact.Relationship {
|
||||
// always return sorted for SBOM stability
|
||||
slices.SortFunc(relationships, sortFunc)
|
||||
var out []artifact.Relationship
|
||||
for _, r := range relationships {
|
||||
if len(types) == 0 || slices.Contains(types, r.relationship.Type) {
|
||||
out = append(out, r.relationship)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (i *Index) ExistingRelationships() []artifact.Relationship {
|
||||
return i.existing
|
||||
func extractCoordinates(relationship artifact.Relationship) (results []file.Coordinates) {
|
||||
if coordinates, exists := relationship.From.(file.Coordinates); exists {
|
||||
results = append(results, coordinates)
|
||||
}
|
||||
|
||||
if coordinates, exists := relationship.To.(file.Coordinates); exists {
|
||||
results = append(results, coordinates)
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func (i *Index) AllUniqueRelationships() []artifact.Relationship {
|
||||
var all []artifact.Relationship
|
||||
all = append(all, i.existing...)
|
||||
all = append(all, i.additional...)
|
||||
return all
|
||||
type mappedRelationships struct {
|
||||
typeMap map[artifact.RelationshipType]map[artifact.ID]*sortableRelationship
|
||||
allRelated []*sortableRelationship
|
||||
}
|
||||
|
||||
func (m *mappedRelationships) add(id artifact.ID, newRelationship *sortableRelationship) {
|
||||
m.allRelated = append(m.allRelated, newRelationship)
|
||||
if m.typeMap == nil {
|
||||
m.typeMap = map[artifact.RelationshipType]map[artifact.ID]*sortableRelationship{}
|
||||
}
|
||||
typeMap := m.typeMap[newRelationship.relationship.Type]
|
||||
if typeMap == nil {
|
||||
typeMap = map[artifact.ID]*sortableRelationship{}
|
||||
m.typeMap[newRelationship.relationship.Type] = typeMap
|
||||
}
|
||||
typeMap[id] = newRelationship
|
||||
}
|
||||
|
||||
type sortableRelationship struct {
|
||||
from artifact.ID
|
||||
to artifact.ID
|
||||
relationship artifact.Relationship
|
||||
}
|
||||
|
||||
func sortFunc(a, b *sortableRelationship) int {
|
||||
cmp := strings.Compare(string(a.relationship.Type), string(b.relationship.Type))
|
||||
if cmp != 0 {
|
||||
return cmp
|
||||
}
|
||||
cmp = strings.Compare(string(a.from), string(b.from))
|
||||
if cmp != 0 {
|
||||
return cmp
|
||||
}
|
||||
return strings.Compare(string(a.to), string(b.to))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user