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>
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user