Files
fn_registry/functions/core/detect_cycle.go
T
egutierrez b5a6711c64 feat: funciones core — detect_cycle, generate_id, rewrite_rule
Tres funciones puras para el dominio core: detección de ciclos en grafos
dirigidos (DFS), generación de IDs determinísticos, y reescritura de
reglas con pattern matching.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 14:24:00 +02:00

69 lines
1.8 KiB
Go

package core
import (
"database/sql"
"fmt"
)
// DetectCycle checks if adding a directed edge (from -> to) would create a cycle
// in a directed graph stored in a SQLite table.
// It performs BFS from toNode following edges where the filter column is non-empty.
// If it reaches fromNode, a cycle exists.
//
// Parameters:
// - conn: open *sql.DB connection
// - table: table name containing the edges (e.g. "relations")
// - fromCol: column name for edge source (e.g. "from_entity")
// - toCol: column name for edge destination (e.g. "to_entity")
// - filterCol: column name that must be non-empty for causal edges (e.g. "via"); pass "" to consider all edges
// - fromNode: source node of the proposed new edge
// - toNode: destination node of the proposed new edge
func DetectCycle(conn *sql.DB, table, fromCol, toCol, filterCol, fromNode, toNode string) error {
if fromNode == "" || toNode == "" {
return nil
}
var query string
if filterCol != "" {
query = fmt.Sprintf("SELECT %s FROM %s WHERE %s = ? AND %s != ''", toCol, table, fromCol, filterCol)
} else {
query = fmt.Sprintf("SELECT %s FROM %s WHERE %s = ?", toCol, table, fromCol)
}
visited := map[string]bool{}
queue := []string{toNode}
for len(queue) > 0 {
current := queue[0]
queue = queue[1:]
if visited[current] {
continue
}
visited[current] = true
if current == fromNode {
return fmt.Errorf("cycle detected: adding edge %s -> %s would create a cycle", fromNode, toNode)
}
rows, err := conn.Query(query, current)
if err != nil {
return fmt.Errorf("querying %s for cycle detection: %w", table, err)
}
for rows.Next() {
var next string
if err := rows.Scan(&next); err != nil {
rows.Close()
return err
}
if !visited[next] {
queue = append(queue, next)
}
}
rows.Close()
}
return nil
}