package kg import ( "context" "database/sql" "fmt" "time" "git.wntrmute.dev/kyle/exo/core" "git.wntrmute.dev/kyle/exo/db" ) // EdgeRelation describes the type of relationship between two nodes. type EdgeRelation string const ( EdgeRelationChild EdgeRelation = "child" EdgeRelationParent EdgeRelation = "parent" EdgeRelationRelated EdgeRelation = "related" EdgeRelationArtifactLink EdgeRelation = "artifact_link" ) // Edge links a source node to a target node or artifact. type Edge struct { ID string SourceID string TargetID string Relation EdgeRelation Created time.Time } // NewEdge creates an Edge with a new UUID and current timestamp. func NewEdge(sourceID, targetID string, relation EdgeRelation) *Edge { return &Edge{ ID: core.NewUUID(), SourceID: sourceID, TargetID: targetID, Relation: relation, Created: time.Now().UTC(), } } // Store persists an Edge to the database. func (e *Edge) Store(ctx context.Context, tx *sql.Tx) error { _, err := tx.ExecContext(ctx, `INSERT INTO edges (id, source_id, target_id, relation, created) VALUES (?, ?, ?, ?, ?)`, e.ID, e.SourceID, e.TargetID, string(e.Relation), db.ToDBTime(e.Created)) if err != nil { return fmt.Errorf("kg: failed to store edge: %w", err) } return nil } // GetEdgesFrom retrieves all edges originating from a given node. func GetEdgesFrom(ctx context.Context, tx *sql.Tx, sourceID string) ([]*Edge, error) { rows, err := tx.QueryContext(ctx, `SELECT id, target_id, relation, created FROM edges WHERE source_id=?`, sourceID) if err != nil { return nil, fmt.Errorf("kg: failed to retrieve edges: %w", err) } defer func() { _ = rows.Close() }() var edges []*Edge for rows.Next() { e := &Edge{SourceID: sourceID} var relation, created string if err := rows.Scan(&e.ID, &e.TargetID, &relation, &created); err != nil { return nil, fmt.Errorf("kg: failed to scan edge: %w", err) } e.Relation = EdgeRelation(relation) e.Created, err = db.FromDBTime(created, nil) if err != nil { return nil, err } edges = append(edges, e) } return edges, rows.Err() } // GetEdgesTo retrieves all edges pointing to a given node/artifact. func GetEdgesTo(ctx context.Context, tx *sql.Tx, targetID string) ([]*Edge, error) { rows, err := tx.QueryContext(ctx, `SELECT id, source_id, relation, created FROM edges WHERE target_id=?`, targetID) if err != nil { return nil, fmt.Errorf("kg: failed to retrieve incoming edges: %w", err) } defer func() { _ = rows.Close() }() var edges []*Edge for rows.Next() { e := &Edge{TargetID: targetID} var relation, created string if err := rows.Scan(&e.ID, &e.SourceID, &relation, &created); err != nil { return nil, fmt.Errorf("kg: failed to scan edge: %w", err) } e.Relation = EdgeRelation(relation) e.Created, err = db.FromDBTime(created, nil) if err != nil { return nil, err } edges = append(edges, e) } return edges, rows.Err() }