Skip to content

Commit b77a170

Browse files
committed
feat: adding dawgrun
1 parent f87dddb commit b77a170

26 files changed

Lines changed: 3303 additions & 0 deletions

go.mod

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,7 @@ require (
3636
google.golang.org/protobuf v1.36.6 // indirect
3737
gopkg.in/yaml.v3 v3.0.1 // indirect
3838
)
39+
40+
replace github.com/specterops/dawgs/tools/dawgrun/cmd/dawgrun => ./tools/dawgrun/cmd/dawgrun
41+
42+
tool github.com/specterops/dawgs/tools/dawgrun/cmd/dawgrun

go.work

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
go 1.25.4
2+
3+
use (
4+
.
5+
./tools/dawgrun
6+
)

go.work.sum

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
2+
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
3+
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
4+
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
5+
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
6+
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
7+
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
8+
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
9+
github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
10+
golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
11+
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8=

tools/dawgrun/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
dawgrun
2+
trace.out
3+
dawgrun.history.txt

tools/dawgrun/go.mod

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
module github.com/specterops/dawgs/tools/dawgrun
2+
3+
go 1.25.4
4+
5+
require (
6+
github.com/alecthomas/chroma/v2 v2.13.0
7+
github.com/charmbracelet/lipgloss v1.1.0
8+
github.com/davecgh/go-spew v1.1.1
9+
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
10+
github.com/kanmu/go-sqlfmt v0.0.2-0.20200215095417-d1e63e2ee5eb
11+
github.com/mitchellh/go-wordwrap v1.0.1
12+
github.com/openengineer/go-repl v0.0.0-00010101000000-000000000000
13+
github.com/specterops/dawgs v0.3.2
14+
)
15+
16+
require (
17+
github.com/RoaringBitmap/roaring/v2 v2.14.4 // indirect
18+
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
19+
github.com/axiomhq/hyperloglog v0.2.6 // indirect
20+
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
21+
github.com/bits-and-blooms/bitset v1.24.4 // indirect
22+
github.com/cespare/xxhash/v2 v2.3.0 // indirect
23+
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
24+
github.com/charmbracelet/x/ansi v0.8.0 // indirect
25+
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
26+
github.com/charmbracelet/x/term v0.2.1 // indirect
27+
github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 // indirect
28+
github.com/dlclark/regexp2 v1.11.0 // indirect
29+
github.com/gammazero/deque v1.2.0 // indirect
30+
github.com/jackc/pgio v1.0.0 // indirect
31+
github.com/jackc/pgpassfile v1.0.0 // indirect
32+
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
33+
github.com/jackc/pgtype v1.14.4 // indirect
34+
github.com/jackc/pgx/v5 v5.8.0 // indirect
35+
github.com/jackc/puddle/v2 v2.2.2 // indirect
36+
github.com/kamstrup/intmap v0.5.2 // indirect
37+
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
38+
github.com/mattn/go-isatty v0.0.20 // indirect
39+
github.com/mattn/go-runewidth v0.0.16 // indirect
40+
github.com/mschoch/smat v0.2.0 // indirect
41+
github.com/muesli/termenv v0.16.0 // indirect
42+
github.com/neo4j/neo4j-go-driver/v5 v5.28.4 // indirect
43+
github.com/pkg/errors v0.8.1 // indirect
44+
github.com/rivo/uniseg v0.4.7 // indirect
45+
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
46+
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect
47+
golang.org/x/sync v0.19.0 // indirect
48+
golang.org/x/sys v0.40.0 // indirect
49+
golang.org/x/term v0.39.0 // indirect
50+
golang.org/x/text v0.33.0 // indirect
51+
)
52+
53+
replace github.com/openengineer/go-repl => ./pkg/go-repl

tools/dawgrun/go.sum

Lines changed: 293 additions & 0 deletions
Large diffs are not rendered by default.

tools/dawgrun/justfile

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
default: build
2+
3+
vet:
4+
go mod tidy
5+
go fmt ./...
6+
go vet ./...
7+
8+
build *BUILDARGS: vet
9+
go build -o dawgrun {{BUILDARGS}} ./cmd/...
10+
11+
build-with-dawgs path_to_dawgs *BUILDARGS:
12+
go work edit -replace=github.com/specterops/dawgs={{path_to_dawgs}}
13+
just build {{BUILDARGS}}
14+
15+
build-with-upstream *BUILDARGS:
16+
go work edit -dropreplace=github.com/specterops/dawgs
17+
just build {{BUILDARGS}}
18+
19+
clean:
20+
@rm -f dawgrun
21+
22+
[arg("port", long)]
23+
debug port="38697": build
24+
dlv exec --continue --accept-multiclient --headless --listen 127.0.0.1:{{port}} ./dawgrun
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
package commands
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
7+
"github.com/davecgh/go-spew/spew"
8+
"github.com/kanmu/go-sqlfmt/sqlfmt"
9+
"github.com/specterops/dawgs/cypher/models/pgsql/format"
10+
"github.com/specterops/dawgs/cypher/models/pgsql/translate"
11+
"github.com/specterops/dawgs/graph"
12+
13+
"github.com/specterops/dawgs/tools/dawgrun/pkg/stubs"
14+
)
15+
16+
func parseCmd() CommandDesc {
17+
return CommandDesc{
18+
args: []string{"<...query>"},
19+
help: "Parses and dumps a Cypher query to AST form.",
20+
21+
Fn: func(ctx *CommandContext, fields []string) error {
22+
query, err := parseQueryArray(fields)
23+
if err != nil {
24+
return fmt.Errorf("error trying to parse query '%s': %w", fields, err)
25+
}
26+
27+
ctx.output.WriteHighlighted(spew.Sdump(query), "golang", "monokai")
28+
return nil
29+
},
30+
}
31+
}
32+
33+
func translateToPsqlCmd() CommandDesc {
34+
flagSet := flag.NewFlagSet("translate-psql", flag.ContinueOnError)
35+
36+
var (
37+
kindMapperConnRef = ""
38+
dumpTranslatedAst = false
39+
)
40+
flagSet.StringVar(&kindMapperConnRef, "conn", "", "Connection reference for choosing a kind mapper")
41+
flagSet.BoolVar(&dumpTranslatedAst, "dump-pg-ast", false, "Whether to dump the translator's constructed AST")
42+
43+
return CommandDesc{
44+
args: []string{"[flags]", "<...query>"},
45+
help: "Parses a query and converts it to the underlying PostgreSQL query",
46+
desc: "Does a bunch of magic to fully translate a Cypher query into a PostgreSQL query",
47+
flags: flagSet,
48+
49+
Fn: func(ctx *CommandContext, fields []string) error {
50+
if err := flagSet.Parse(fields); err != nil {
51+
return fmt.Errorf("could not parse flags: %w", err)
52+
}
53+
54+
fields = flagSet.Args()
55+
query, err := parseQueryArray(fields)
56+
if err != nil {
57+
return fmt.Errorf("error trying to parse query '%s': %w", fields, err)
58+
}
59+
60+
kindMapper := stubs.EmptyMapper()
61+
if kindMapperConnRef != "" {
62+
// Fetch kinds regardless of if it's already loaded.
63+
kindMap, err := loadKindMap(ctx, kindMapperConnRef)
64+
if err != nil {
65+
return fmt.Errorf("could not load kind map for explain: %w", err)
66+
}
67+
kindMapper = stubs.MapperFromKindMap(kindMap)
68+
}
69+
70+
result, err := translate.Translate(ctx, query, kindMapper, nil)
71+
if err != nil {
72+
return fmt.Errorf("could not translate cypher query to pgsql: %w", err)
73+
}
74+
if dumpTranslatedAst {
75+
fmt.Fprintf(ctx.output, "TRANSLATOR AST\n\n")
76+
ctx.output.WriteHighlighted(spew.Sdump(result.Statement), "golang", "monokai")
77+
fmt.Fprintf(ctx.output, "\n")
78+
}
79+
80+
sqlQuery, err := format.SyntaxNode(result.Statement)
81+
if err != nil {
82+
return fmt.Errorf("could not format translated statement into a string query: %w", err)
83+
}
84+
85+
formattedQuery, err := sqlfmt.Format(sqlQuery, &sqlfmt.Options{
86+
Distance: 0,
87+
})
88+
if err != nil {
89+
ctx.output.Warnf("could not format query: %s", err.Error())
90+
formattedQuery = sqlQuery
91+
}
92+
93+
ctx.output.WriteHighlighted(formattedQuery, "postgres", "monokai")
94+
return nil
95+
},
96+
}
97+
}
98+
99+
func explainAsPsqlCmd() CommandDesc {
100+
return CommandDesc{
101+
args: []string{"<conn>", "<...query>"},
102+
help: "Explains a translated query over an active PG connection",
103+
desc: "Asks the PG query planner to explain the (translated) Cypher query in PG terms",
104+
105+
Fn: func(ctx *CommandContext, fields []string) error {
106+
if len(fields) < 2 {
107+
return fmt.Errorf("invalid usage, requires: <connection name> <query>")
108+
}
109+
110+
connName := fields[0]
111+
conn, ok := ctx.scope.connections[connName]
112+
if !ok {
113+
return fmt.Errorf("connection %s not found; did you `open` it?", connName)
114+
}
115+
116+
// Fetch kinds regardless of if it's already loaded.
117+
kindMap, err := loadKindMap(ctx, connName)
118+
if err != nil {
119+
return fmt.Errorf("could not load kind map for explain: %w", err)
120+
}
121+
122+
query, err := parseQueryArray(fields[1:])
123+
if err != nil {
124+
return fmt.Errorf("could not parse query: %w", err)
125+
}
126+
127+
// Populate a DumbKindMapper from the database's kinds table
128+
kindMapper := stubs.MapperFromKindMap(kindMap)
129+
result, err := translate.Translate(ctx, query, kindMapper, nil)
130+
if err != nil {
131+
return fmt.Errorf("could not translate cypher query to pgsql: %w", err)
132+
}
133+
134+
sqlQuery, err := format.SyntaxNode(result.Statement)
135+
if err != nil {
136+
return fmt.Errorf("could not format translated statement into a string query: %w", err)
137+
}
138+
139+
formattedQuery, err := sqlfmt.Format(sqlQuery, &sqlfmt.Options{
140+
Distance: 2,
141+
})
142+
if err != nil {
143+
ctx.output.Warnf("could not format query: %s", err.Error())
144+
formattedQuery = sqlQuery
145+
}
146+
explainSQLQuery := fmt.Sprintf("EXPLAIN %s", formattedQuery)
147+
ctx.output.WriteHighlighted(explainSQLQuery, "postgres", "monokai")
148+
fmt.Fprint(ctx.output, "\n\n")
149+
150+
err = conn.ReadTransaction(ctx, func(tx graph.Transaction) error {
151+
result := tx.Raw(explainSQLQuery, nil)
152+
if err := result.Error(); err != nil {
153+
return fmt.Errorf("error running raw query: '%s': %w", explainSQLQuery, err)
154+
}
155+
defer result.Close()
156+
157+
var value string
158+
for result.Next() {
159+
graph.ScanNextResult(result, &value)
160+
fmt.Fprintf(ctx.output, " %s\n", value)
161+
}
162+
163+
return nil
164+
})
165+
if err != nil {
166+
return fmt.Errorf("could not run EXPLAIN query: %w", err)
167+
}
168+
169+
return nil
170+
},
171+
}
172+
}

0 commit comments

Comments
 (0)