Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: add node annotations #40

Merged
merged 7 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
234 changes: 212 additions & 22 deletions backends/ent/annotations.go
lallevato-lm marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,57 @@ import (
"github.com/protobom/storage/internal/backends/ent"
"github.com/protobom/storage/internal/backends/ent/annotation"
"github.com/protobom/storage/internal/backends/ent/document"
"github.com/protobom/storage/internal/backends/ent/node"
"github.com/protobom/storage/internal/backends/ent/predicate"
)

// AddAnnotations applies multiple named annotation values to a single document.
func (backend *Backend) AddAnnotations(documentID, name string, values ...string) error {
// AddAnnotationToDocuments applies a single named annotation value to multiple documents.
func (backend *Backend) AddAnnotationToDocuments(name, value string, documentIDs ...string) error {
data := ent.Annotations{}

for _, documentID := range documentIDs {
documentUUID, err := backend.client.Document.Query().
Where(document.MetadataIDEQ(documentID)).
OnlyID(backend.ctx)
if err != nil {
return fmt.Errorf("querying documents: %w", err)
}

data = append(data, &ent.Annotation{
DocumentID: documentUUID,
Name: name,
Value: value,
})
}

return backend.withTx(backend.saveAnnotations(data...))
}

// AddAnnotationToNodes applies a single named annotation value to multiple nodes.
func (backend *Backend) AddAnnotationToNodes(name, value string, nodeIDs ...string) error {
data := ent.Annotations{}

for _, nodeID := range nodeIDs {
result, err := backend.client.Node.Query().
Where(node.IDEQ(nodeID)).
Only(backend.ctx)
if err != nil {
return fmt.Errorf("querying nodes: %w", err)
}

data = append(data, &ent.Annotation{
DocumentID: result.DocumentID,
NodeID: &result.ID,
Name: name,
Value: value,
})
}

return backend.withTx(backend.saveAnnotations(data...))
}

// AddDocumentAnnotations applies multiple named annotation values to a single document.
func (backend *Backend) AddDocumentAnnotations(documentID, name string, values ...string) error {
data := ent.Annotations{}

documentUUID, err := backend.client.Document.Query().
Expand All @@ -39,20 +85,21 @@ func (backend *Backend) AddAnnotations(documentID, name string, values ...string
return backend.withTx(backend.saveAnnotations(data...))
}

// AddAnnotationToDocuments applies a single named annotation value to multiple documents.
func (backend *Backend) AddAnnotationToDocuments(name, value string, documentIDs ...string) error {
// AddNodeAnnotations applies multiple named annotation values to a single node.
func (backend *Backend) AddNodeAnnotations(nodeID, name string, values ...string) error {
data := ent.Annotations{}

for _, documentID := range documentIDs {
documentUUID, err := backend.client.Document.Query().
Where(document.MetadataIDEQ(documentID)).
OnlyID(backend.ctx)
if err != nil {
return fmt.Errorf("querying documents: %w", err)
}
result, err := backend.client.Node.Query().
Where(node.IDEQ(nodeID)).
Only(backend.ctx)
if err != nil {
return fmt.Errorf("querying documents: %w", err)
}

for _, value := range values {
data = append(data, &ent.Annotation{
DocumentID: documentUUID,
DocumentID: result.DocumentID,
NodeID: &nodeID,
Name: name,
Value: value,
})
Expand All @@ -61,8 +108,8 @@ func (backend *Backend) AddAnnotationToDocuments(name, value string, documentIDs
return backend.withTx(backend.saveAnnotations(data...))
}

// ClearAnnotations removes all annotations from the specified documents.
func (backend *Backend) ClearAnnotations(documentIDs ...string) error {
// ClearDocumentAnnotations removes all annotations from the specified documents.
func (backend *Backend) ClearDocumentAnnotations(documentIDs ...string) error {
if len(documentIDs) == 0 {
return nil
}
Expand All @@ -78,6 +125,23 @@ func (backend *Backend) ClearAnnotations(documentIDs ...string) error {
})
}

// ClearNodeAnnotations removes all annotations from the specified nodes.
func (backend *Backend) ClearNodeAnnotations(nodeIDs ...string) error {
if len(nodeIDs) == 0 {
return nil
}

return backend.withTx(func(tx *ent.Tx) error {
if _, err := tx.Annotation.Delete().
Where(annotation.HasNodeWith(node.IDIn(nodeIDs...))).
Exec(backend.ctx); err != nil {
return fmt.Errorf("clearing annotations: %w", err)
}

return nil
})
}

// GetDocumentAnnotations gets all annotations for the specified
// document, limited to a set of annotation names if specified.
func (backend *Backend) GetDocumentAnnotations(documentID string, names ...string) (ent.Annotations, error) {
Expand Down Expand Up @@ -154,9 +218,84 @@ func (backend *Backend) GetDocumentUniqueAnnotation(documentID, name string) (st
return result.Value, nil
}

// RemoveAnnotations removes all annotations with the specified name from
// GetNodeAnnotations gets all annotations for the specified
// node, limited to a set of annotation names if specified.
func (backend *Backend) GetNodeAnnotations(nodeID string, names ...string) (ent.Annotations, error) {
if backend.client == nil {
return nil, errUninitializedClient
}

predicates := []predicate.Annotation{
annotation.HasNodeWith(node.IDEQ(nodeID)),
}

if len(names) > 0 {
predicates = append(predicates, annotation.NameIn(names...))
}

annotations, err := backend.client.Annotation.Query().Where(predicates...).All(backend.ctx)
if err != nil {
return nil, fmt.Errorf("querying annotations: %w", err)
}

return annotations, nil
}

// GetNodesByAnnotation gets all nodes having the specified named
// annotation, limited to a set of annotation values if specified.
func (backend *Backend) GetNodesByAnnotation(name string, values ...string) ([]*sbom.Node, error) {
if backend.client == nil {
return nil, errUninitializedClient
}

predicates := []predicate.Annotation{annotation.NameEQ(name)}

if len(values) > 0 {
predicates = append(predicates, annotation.ValueIn(values...))
}

ids, err := backend.client.Annotation.Query().
Where(predicates...).
QueryNode().
IDs(backend.ctx)
if err != nil {
return nil, fmt.Errorf("querying nodes table: %w", err)
}

if len(ids) == 0 {
return []*sbom.Node{}, nil
}

return backend.GetNodesByID(ids...)
}

// GetNodeUniqueAnnotation gets the value for a unique annotation.
func (backend *Backend) GetNodeUniqueAnnotation(nodeID, name string) (string, error) {
if backend.client == nil {
return "", errUninitializedClient
}

result, err := backend.client.Annotation.Query().
Where(
annotation.HasNodeWith(node.IDEQ(nodeID)),
annotation.NameEQ(name),
annotation.IsUniqueEQ(true),
).
Only(backend.ctx)
if err != nil {
if ent.IsNotFound(err) {
return "", nil
}

return "", fmt.Errorf("retrieving unique annotation for node: %w", err)
}

return result.Value, nil
}

// RemoveDocumentAnnotations removes all annotations with the specified name from
// the document, limited to a set of annotation values if specified.
func (backend *Backend) RemoveAnnotations(documentID, name string, values ...string) error {
func (backend *Backend) RemoveDocumentAnnotations(documentID, name string, values ...string) error {
return backend.withTx(
func(tx *ent.Tx) error {
predicates := []predicate.Annotation{
Expand All @@ -176,17 +315,39 @@ func (backend *Backend) RemoveAnnotations(documentID, name string, values ...str
})
}

// SetAnnotations explicitly sets the named annotations for the specified document.
func (backend *Backend) SetAnnotations(documentID, name string, values ...string) error {
if err := backend.ClearAnnotations(documentID); err != nil {
// RemoveNodeAnnotations removes all annotations with the specified name from
// the node, limited to a set of annotation values if specified.
func (backend *Backend) RemoveNodeAnnotations(nodeID, name string, values ...string) error {
return backend.withTx(
func(tx *ent.Tx) error {
predicates := []predicate.Annotation{
annotation.HasNodeWith(node.IDEQ(nodeID)),
annotation.NameEQ(name),
}

if len(values) > 0 {
predicates = append(predicates, annotation.ValueIn(values...))
}

if _, err := tx.Annotation.Delete().Where(predicates...).Exec(backend.ctx); err != nil {
return fmt.Errorf("removing annotations: %w", err)
}

return nil
})
}

// SetDocumentAnnotations explicitly sets the named annotations for the specified document.
func (backend *Backend) SetDocumentAnnotations(documentID, name string, values ...string) error {
if err := backend.ClearDocumentAnnotations(documentID); err != nil {
return err
}

return backend.AddAnnotations(documentID, name, values...)
return backend.AddDocumentAnnotations(documentID, name, values...)
}

// SetUniqueAnnotation sets a named annotation value that is unique to the specified document.
func (backend *Backend) SetUniqueAnnotation(documentID, name, value string) error {
// SetDocumentUniqueAnnotation sets a named annotation value that is unique to the specified document.
func (backend *Backend) SetDocumentUniqueAnnotation(documentID, name, value string) error {
documentUUID, err := backend.client.Document.Query().
Where(document.MetadataIDEQ(documentID)).
OnlyID(backend.ctx)
Expand All @@ -203,3 +364,32 @@ func (backend *Backend) SetUniqueAnnotation(documentID, name, value string) erro
}),
)
}

// SetNodeAnnotations explicitly sets the named annotations for the specified node.
func (backend *Backend) SetNodeAnnotations(nodeID, name string, values ...string) error {
if err := backend.ClearNodeAnnotations(nodeID); err != nil {
return err
}

return backend.AddNodeAnnotations(nodeID, name, values...)
}

// SetNodeUniqueAnnotation sets a named annotation value that is unique to the specified node.
func (backend *Backend) SetNodeUniqueAnnotation(nodeID, name, value string) error {
result, err := backend.client.Node.Query().
Where(node.IDEQ(nodeID)).
Only(backend.ctx)
if err != nil {
return fmt.Errorf("querying nodes: %w", err)
}

return backend.withTx(
backend.saveAnnotations(&ent.Annotation{
DocumentID: result.DocumentID,
NodeID: &result.ID,
Name: name,
Value: value,
IsUnique: true,
}),
)
}
Loading