b5a6711c64
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>
69 lines
1.8 KiB
Go
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
|
|
}
|