a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
264 lines
7.3 KiB
Go
264 lines
7.3 KiB
Go
package sql
|
|
|
|
import (
|
|
"sort"
|
|
|
|
"github.com/zzet/gortex/internal/graph"
|
|
)
|
|
|
|
// RebuildStats summarises a RebuildTablesFromStringRegistry pass. All
|
|
// counts are net (created — already-present). EmittersLinked counts the
|
|
// unique (caller, table) pairs that produced an EdgeQueries edge.
|
|
type RebuildStats struct {
|
|
StringsVisited int `json:"strings_visited"`
|
|
TablesCreated int `json:"tables_created"`
|
|
ColumnsCreated int `json:"columns_created"`
|
|
QueryEdges int `json:"query_edges_created"`
|
|
ReadColEdges int `json:"reads_col_edges_created"`
|
|
WriteColEdges int `json:"writes_col_edges_created"`
|
|
EmittersLinked int `json:"emitters_linked"`
|
|
Skipped int `json:"skipped"`
|
|
}
|
|
|
|
// RebuildTablesFromStringRegistry is the short-circuit: rederive
|
|
// the KindTable / KindColumn / EdgeQueries / EdgeReadsCol /
|
|
// EdgeWritesCol layer from the KindString context="sql" registry
|
|
// already present in g, without re-parsing any source file. Idempotent
|
|
// — nodes and edges are deduped via graph.AddNode / AddEdge semantics,
|
|
// so running it twice produces the same graph.
|
|
//
|
|
// For each KindString context="sql" node:
|
|
//
|
|
// - Re-extract tables and columns from node.Name (the verbatim
|
|
// query) using the canonical ExtractTables / ExtractColumns
|
|
// parsers — the same shape the source-time extractor uses.
|
|
// - For every emitter (EdgeEmits.From → KindString), wire
|
|
// EdgeQueries to each derived KindTable and EdgeReadsCol /
|
|
// EdgeWritesCol to each derived KindColumn.
|
|
//
|
|
// Dialect is taken from Meta["dialect"] when present (set by the Go
|
|
// SQL extractor) and falls back to "generic". Origin on rebuilt edges
|
|
// is text_matched (same tier the source-time extractor uses for
|
|
// regex-derived SQL).
|
|
//
|
|
// Returns counts for telemetry; rebuilt edges idempotently replace
|
|
// any existing edges with the same edgeKey, so a second call after
|
|
// the first reports tablesCreated=0, emittersLinked=0.
|
|
func RebuildTablesFromStringRegistry(g graph.Store) RebuildStats {
|
|
if g == nil {
|
|
return RebuildStats{}
|
|
}
|
|
var stats RebuildStats
|
|
// Snapshot the node set to avoid mutation-during-iteration; we
|
|
// append KindTable / KindColumn nodes below.
|
|
nodes := g.AllNodes()
|
|
// Track per-call counts of new node/edge insertions; we infer
|
|
// "created" by checking presence before AddNode / AddEdge.
|
|
preExistingTables := make(map[string]struct{})
|
|
preExistingCols := make(map[string]struct{})
|
|
for _, n := range nodes {
|
|
if n == nil {
|
|
continue
|
|
}
|
|
switch n.Kind {
|
|
case graph.KindTable:
|
|
preExistingTables[n.ID] = struct{}{}
|
|
case graph.KindColumn:
|
|
preExistingCols[n.ID] = struct{}{}
|
|
}
|
|
}
|
|
// EdgeQueries / EdgeReadsCol / EdgeWritesCol dedup tracker.
|
|
// Seeded from the live graph so a re-run on an already-rebuilt
|
|
// graph reports zero new edges (graph.AddEdge is idempotent by
|
|
// edgeKey, but the stats counters live in this function so they
|
|
// need to know what was already there).
|
|
type edgeKey struct {
|
|
from, to string
|
|
kind graph.EdgeKind
|
|
}
|
|
seenEdges := make(map[edgeKey]struct{})
|
|
for _, n := range nodes {
|
|
if n == nil {
|
|
continue
|
|
}
|
|
if n.Kind != graph.KindTable && n.Kind != graph.KindColumn {
|
|
continue
|
|
}
|
|
for _, e := range g.GetInEdges(n.ID) {
|
|
if e == nil {
|
|
continue
|
|
}
|
|
switch e.Kind {
|
|
case graph.EdgeQueries, graph.EdgeReadsCol, graph.EdgeWritesCol:
|
|
seenEdges[edgeKey{from: e.From, to: e.To, kind: e.Kind}] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
for _, n := range nodes {
|
|
if n == nil || n.Kind != graph.KindString {
|
|
continue
|
|
}
|
|
if ctx, _ := n.Meta["context"].(string); ctx != "sql" {
|
|
continue
|
|
}
|
|
query := n.Name
|
|
if query == "" {
|
|
if v, ok := n.Meta["value"].(string); ok {
|
|
query = v
|
|
}
|
|
}
|
|
if query == "" {
|
|
stats.Skipped++
|
|
continue
|
|
}
|
|
stats.StringsVisited++
|
|
dialect, _ := n.Meta["dialect"].(string)
|
|
if dialect == "" {
|
|
dialect = "generic"
|
|
}
|
|
tables := ExtractTables(query)
|
|
columns := ExtractColumns(query)
|
|
if len(tables) == 0 {
|
|
stats.Skipped++
|
|
continue
|
|
}
|
|
// Collect emitters from EdgeEmits to this KindString.
|
|
emitters := make([]string, 0, 4)
|
|
seenEmitter := make(map[string]struct{}, 4)
|
|
for _, e := range g.GetInEdges(n.ID) {
|
|
if e == nil || e.Kind != graph.EdgeEmits {
|
|
continue
|
|
}
|
|
if _, dup := seenEmitter[e.From]; dup {
|
|
continue
|
|
}
|
|
seenEmitter[e.From] = struct{}{}
|
|
emitters = append(emitters, e.From)
|
|
}
|
|
// Stable ordering — emitter snapshots are otherwise shard-
|
|
// order-dependent, which makes test assertions racy.
|
|
sort.Strings(emitters)
|
|
// Ensure KindTable nodes.
|
|
tableNodes := make([]struct {
|
|
id string
|
|
ref TableRef
|
|
added bool
|
|
}, 0, len(tables))
|
|
for _, ref := range tables {
|
|
id := TableNodeID(dialect, ref.Schema, ref.Table)
|
|
if _, exists := preExistingTables[id]; !exists {
|
|
meta := map[string]any{
|
|
"table": ref.Table,
|
|
"dialect": dialect,
|
|
}
|
|
if ref.Schema != "" {
|
|
meta["schema"] = ref.Schema
|
|
}
|
|
g.AddNode(&graph.Node{
|
|
ID: id,
|
|
Kind: graph.KindTable,
|
|
Name: ref.Table,
|
|
FilePath: n.FilePath,
|
|
Language: "sql",
|
|
Meta: meta,
|
|
})
|
|
preExistingTables[id] = struct{}{}
|
|
stats.TablesCreated++
|
|
tableNodes = append(tableNodes, struct {
|
|
id string
|
|
ref TableRef
|
|
added bool
|
|
}{id, ref, true})
|
|
} else {
|
|
tableNodes = append(tableNodes, struct {
|
|
id string
|
|
ref TableRef
|
|
added bool
|
|
}{id, ref, false})
|
|
}
|
|
}
|
|
// Ensure KindColumn nodes.
|
|
colNodes := make([]struct {
|
|
id string
|
|
ref ColumnRef
|
|
}, 0, len(columns))
|
|
for _, col := range columns {
|
|
id := ColumnNodeID(dialect, col.Schema, col.Table, col.Column)
|
|
if _, exists := preExistingCols[id]; !exists {
|
|
meta := map[string]any{
|
|
"table": col.Table,
|
|
"column": col.Column,
|
|
"dialect": dialect,
|
|
}
|
|
if col.Schema != "" {
|
|
meta["schema"] = col.Schema
|
|
}
|
|
g.AddNode(&graph.Node{
|
|
ID: id,
|
|
Kind: graph.KindColumn,
|
|
Name: col.Column,
|
|
FilePath: n.FilePath,
|
|
Language: "sql",
|
|
Meta: meta,
|
|
})
|
|
preExistingCols[id] = struct{}{}
|
|
stats.ColumnsCreated++
|
|
}
|
|
colNodes = append(colNodes, struct {
|
|
id string
|
|
ref ColumnRef
|
|
}{id, col})
|
|
}
|
|
// Wire emitters → tables / columns.
|
|
for _, emitter := range emitters {
|
|
for _, t := range tableNodes {
|
|
k := edgeKey{from: emitter, to: t.id, kind: graph.EdgeQueries}
|
|
if _, dup := seenEdges[k]; dup {
|
|
continue
|
|
}
|
|
seenEdges[k] = struct{}{}
|
|
g.AddEdge(&graph.Edge{
|
|
From: emitter,
|
|
To: t.id,
|
|
Kind: graph.EdgeQueries,
|
|
FilePath: n.FilePath,
|
|
Origin: graph.OriginTextMatched,
|
|
Meta: map[string]any{
|
|
"op": t.ref.Op,
|
|
"source": "string_registry",
|
|
},
|
|
})
|
|
stats.QueryEdges++
|
|
stats.EmittersLinked++
|
|
}
|
|
for _, c := range colNodes {
|
|
kind := graph.EdgeReadsCol
|
|
if c.ref.Op == "write" {
|
|
kind = graph.EdgeWritesCol
|
|
}
|
|
k := edgeKey{from: emitter, to: c.id, kind: kind}
|
|
if _, dup := seenEdges[k]; dup {
|
|
continue
|
|
}
|
|
seenEdges[k] = struct{}{}
|
|
g.AddEdge(&graph.Edge{
|
|
From: emitter,
|
|
To: c.id,
|
|
Kind: kind,
|
|
FilePath: n.FilePath,
|
|
Origin: graph.OriginTextMatched,
|
|
Meta: map[string]any{
|
|
"source": "string_registry",
|
|
},
|
|
})
|
|
if kind == graph.EdgeReadsCol {
|
|
stats.ReadColEdges++
|
|
} else {
|
|
stats.WriteColEdges++
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return stats
|
|
}
|