Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions cmd/benchmark/explain.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package main
import (
"context"
"fmt"
"maps"

"github.com/specterops/dawgs/cypher/frontend"
"github.com/specterops/dawgs/cypher/models/pgsql"
Expand Down Expand Up @@ -50,7 +51,9 @@ func newPostgresExplainer(kindMapper pgsql.KindMapper, graphID int32) ExplainFun
return nil, err
}

result := tx.Raw("EXPLAIN (ANALYZE, BUFFERS) "+sqlQuery, translation.Parameters)
maps.Copy(translation.Parameters, sqlQuery.Parameters)

result := tx.Raw("EXPLAIN (ANALYZE, BUFFERS) "+sqlQuery.Statement, translation.Parameters)
defer result.Close()

var plan []string
Expand All @@ -67,8 +70,9 @@ func newPostgresExplainer(kindMapper pgsql.KindMapper, graphID int32) ExplainFun
return nil, err
}

// TODO: should this get the parameters as well?
return &ExplainResult{
SQL: sqlQuery,
SQL: sqlQuery.Statement,
Plan: plan,
Optimization: translation.Optimization,
}, nil
Expand Down
8 changes: 6 additions & 2 deletions cmd/graphbench/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package main
import (
"context"
"fmt"
"maps"
"regexp"
"strconv"
"strings"
Expand Down Expand Up @@ -187,9 +188,11 @@ func (s *postgresSQLRunner) explain(ctx context.Context, cypherQuery string, par
return postgresExplain{}, err
}

maps.Copy(translation.Parameters, sqlQuery.Parameters)

var plan []string
if err := s.db.ReadTransaction(ctx, func(tx graph.Transaction) error {
result := tx.Raw("EXPLAIN (ANALYZE, BUFFERS, TIMING OFF) "+sqlQuery, translation.Parameters)
result := tx.Raw("EXPLAIN (ANALYZE, BUFFERS, TIMING OFF) "+sqlQuery.Statement, translation.Parameters)
defer result.Close()

for result.Next() {
Expand All @@ -206,8 +209,9 @@ func (s *postgresSQLRunner) explain(ctx context.Context, cypherQuery string, par
return postgresExplain{}, err
}

// TODO: should this get the parameters as well?
return postgresExplain{
SQL: sqlQuery,
SQL: sqlQuery.Statement,
Plan: plan,
Metrics: parsePostgresPlanMetrics(plan),
Optimization: translation.Optimization,
Expand Down
8 changes: 6 additions & 2 deletions cmd/plancorpus/capture.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"context"
"fmt"
"maps"
"net/url"
"os"
"path/filepath"
Expand Down Expand Up @@ -280,9 +281,11 @@ func (s *backendCapture) capturePostgres(ctx context.Context, cypherQuery string
return
}

maps.Copy(translation.Parameters, sqlQuery.Parameters)

var plan []string
if err := s.db.ReadTransaction(ctx, func(tx graph.Transaction) error {
result := tx.Raw("EXPLAIN "+sqlQuery, translation.Parameters)
result := tx.Raw("EXPLAIN "+sqlQuery.Statement, translation.Parameters)
defer result.Close()

for result.Next() {
Expand All @@ -298,7 +301,8 @@ func (s *backendCapture) capturePostgres(ctx context.Context, cypherQuery string
record.Error = err.Error()
}

record.SQL = sqlQuery
// TODO: should this get the parameters as well?
record.SQL = sqlQuery.Statement
record.PGPlan = plan
record.PGOperators = postgresOperators(plan)
record.PlannedLowerings = loweringNames(translation.Optimization.PlannedLowerings)
Expand Down
43 changes: 23 additions & 20 deletions cypher/models/pgsql/format/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import (
)

type OutputBuilder struct {
MaterializeParameters bool
StripLiterals bool
parameters map[string]any
params map[string]any
materializeParameters bool
materializedParams map[string]any
builder *strings.Builder
}

Expand All @@ -22,8 +22,8 @@ func NewOutputBuilder() *OutputBuilder {
}

func (s *OutputBuilder) WithMaterializedParameters(parameters map[string]any) *OutputBuilder {
s.MaterializeParameters = true
s.parameters = parameters
s.materializeParameters = true
s.materializedParams = parameters

return s
}
Expand All @@ -47,8 +47,11 @@ func (s *OutputBuilder) Write(values ...any) {
}
}

func (s *OutputBuilder) Build() string {
return s.builder.String()
func (s *OutputBuilder) Build() Formatted {
return Formatted{
Statement: s.builder.String(),
Parameters: s.params,
}
}

func formatSlice[T any, TS []T](builder *OutputBuilder, slice TS, dataType pgsql.DataType) error {
Expand Down Expand Up @@ -546,8 +549,8 @@ func formatNode(builder *OutputBuilder, rootExpr pgsql.SyntaxNode) error {
)

case pgsql.Parameter:
if builder.MaterializeParameters {
if parameterValue, hasParameter := builder.parameters[typedNextExpr.Identifier.String()]; !hasParameter {
if builder.materializeParameters {
if parameterValue, hasParameter := builder.materializedParams[typedNextExpr.Identifier.String()]; !hasParameter {
return fmt.Errorf("invalid parameter %s", typedNextExpr.Identifier.String())
} else if parameterLiteral, err := pgsql.AsLiteral(parameterValue); err != nil {
return fmt.Errorf("invalid parameter value for %s: %v", typedNextExpr.Identifier.String(), err)
Expand Down Expand Up @@ -611,9 +614,9 @@ func formatNode(builder *OutputBuilder, rootExpr pgsql.SyntaxNode) error {
return nil
}

func Expression(expression pgsql.SyntaxNode, builder *OutputBuilder) (string, error) {
func Expression(expression pgsql.SyntaxNode, builder *OutputBuilder) (Formatted, error) {
if err := formatNode(builder, expression); err != nil {
return "", err
return Formatted{}, err
}

return builder.Build(), nil
Expand Down Expand Up @@ -1159,42 +1162,42 @@ func formatDeleteStatement(builder *OutputBuilder, sqlDelete pgsql.Delete) error
return nil
}

func Statement(statement pgsql.Statement, builder *OutputBuilder) (string, error) {
func Statement(statement pgsql.Statement, builder *OutputBuilder) (Formatted, error) {
switch typedStatement := statement.(type) {
case pgsql.Merge:
if err := formatMergeStatement(builder, typedStatement); err != nil {
return "", err
return Formatted{}, err
}

case pgsql.Query:
if err := formatSetExpression(builder, typedStatement); err != nil {
return "", err
return Formatted{}, err
}

case pgsql.Insert:
if err := formatInsertStatement(builder, typedStatement); err != nil {
return "", err
return Formatted{}, err
}

case pgsql.Update:
if err := formatUpdateStatement(builder, typedStatement); err != nil {
return "", err
return Formatted{}, err
}

case pgsql.Delete:
if err := formatDeleteStatement(builder, typedStatement); err != nil {
return "", err
return Formatted{}, err
}

default:
return "", fmt.Errorf("unsupported PgSQL statement type: %T", statement)
return Formatted{}, fmt.Errorf("unsupported PgSQL statement type: %T", statement)
}

builder.Write(";")
return builder.Build(), nil
}

func SyntaxNode(node pgsql.SyntaxNode) (string, error) {
func SyntaxNode(node pgsql.SyntaxNode) (Formatted, error) {
builder := NewOutputBuilder()

switch typedNode := node.(type) {
Expand All @@ -1205,7 +1208,7 @@ func SyntaxNode(node pgsql.SyntaxNode) (string, error) {
return Expression(typedNode, builder)

default:
return "", fmt.Errorf("unknown SQL AST type: %T", node)
return Formatted{}, fmt.Errorf("unknown SQL AST type: %T", node)
}
}

Expand Down
4 changes: 2 additions & 2 deletions cypher/models/pgsql/translate/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ func TestConsecutiveCreateClausesAreBuiltOnce(t *testing.T) {
formatted, err := Translated(translation)
require.NoError(t, err)

require.Equal(t, 2, strings.Count(formatted, "insert into node"))
require.Equal(t, 2, strings.Count(formatted, "nextval(pg_get_serial_sequence('node', 'id'))"))
require.Equal(t, 2, strings.Count(formatted.Statement, "insert into node"))
require.Equal(t, 2, strings.Count(formatted.Statement, "nextval(pg_get_serial_sequence('node', 'id'))"))
}
12 changes: 6 additions & 6 deletions cypher/models/pgsql/translate/expansion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,10 +245,10 @@ func TestZeroDepthExpansionBuildKeepsPrimerBranch(t *testing.T) {
primerBranch := "select 1, 2, 1, true, e0.start_id = e0.end_id, array [7]"
recursiveBranch := "select 1, 3, 2, true, false, array [8]"

require.Contains(t, formattedQuery, zeroDepthBranch)
require.Contains(t, formattedQuery, primerBranch)
require.Contains(t, formattedQuery, recursiveBranch)
require.Contains(t, formattedQuery, "where s1.depth > 0")
require.Less(t, strings.Index(formattedQuery, zeroDepthBranch), strings.Index(formattedQuery, primerBranch))
require.Less(t, strings.Index(formattedQuery, primerBranch), strings.Index(formattedQuery, recursiveBranch))
require.Contains(t, formattedQuery.Statement, zeroDepthBranch)
require.Contains(t, formattedQuery.Statement, primerBranch)
require.Contains(t, formattedQuery.Statement, recursiveBranch)
require.Contains(t, formattedQuery.Statement, "where s1.depth > 0")
require.Less(t, strings.Index(formattedQuery.Statement, zeroDepthBranch), strings.Index(formattedQuery.Statement, primerBranch))
require.Less(t, strings.Index(formattedQuery.Statement, primerBranch), strings.Index(formattedQuery.Statement, recursiveBranch))
}
5 changes: 3 additions & 2 deletions cypher/models/pgsql/translate/expression_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ func TestInferExpressionType(t *testing.T) {
if testName, err := format.Expression(nextCase.Expression, format.NewOutputBuilder()); err != nil {
t.Fatalf("unable to format test case expression: %v", err)
} else {
t.Run(testName, func(t *testing.T) {
t.Run(testName.Statement, func(t *testing.T) {
inferredType, err := translate.InferExpressionType(nextCase.Expression)

require.Nil(t, err)
Expand Down Expand Up @@ -315,7 +315,8 @@ func TestPropertyLookupEqualityScalarRewrites(t *testing.T) {
formatted, err := format.Expression(treeTranslator.PeekOperand(), format.NewOutputBuilder())
require.NoError(t, err)

return formatted
// TODO: does this need to handle Properties?
return formatted.Statement
}
testCases = []struct {
Name string
Expand Down
6 changes: 4 additions & 2 deletions cypher/models/pgsql/translate/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package translate
import (
"bytes"
"context"
"maps"
"strings"

"github.com/specterops/dawgs/cypher/models/cypher"
Expand All @@ -11,7 +12,7 @@ import (
"github.com/specterops/dawgs/cypher/models/pgsql/format"
)

func Translated(translation Result) (string, error) {
func Translated(translation Result) (format.Formatted, error) {
return format.Statement(translation.Statement, format.NewOutputBuilder())
}

Expand Down Expand Up @@ -56,8 +57,9 @@ func FromCypher(ctx context.Context, regularQuery *cypher.RegularQuery, kindMapp
} else if sqlQuery, err := format.Statement(translation.Statement, format.NewOutputBuilder()); err != nil {
return format.Formatted{}, err
} else {
output.WriteString(sqlQuery)
output.WriteString(sqlQuery.Statement)

maps.Copy(translation.Parameters, sqlQuery.Parameters)
return format.Formatted{
Statement: output.String(),
Parameters: translation.Parameters,
Expand Down
18 changes: 9 additions & 9 deletions cypher/models/pgsql/translate/function_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func TestTailFunctionDoesNotDuplicatePathComponentExpression(t *testing.T) {

formatted, err := Translated(translation)
require.NoError(t, err)
require.Equal(t, 1, strings.Count(formatted, "ordered_edges_to_path"), formatted)
require.Equal(t, 1, strings.Count(formatted.Statement, "ordered_edges_to_path"), formatted)
require.NotContains(t, formatted, "cardinality(((case when")
}

Expand All @@ -83,7 +83,7 @@ func TestTailPredicateStagesPathComponentExpression(t *testing.T) {

formatted, err := Translated(translation)
require.NoError(t, err)
require.Equal(t, 1, strings.Count(formatted, "ordered_edges_to_path"))
require.Equal(t, 1, strings.Count(formatted.Statement, "ordered_edges_to_path"))
require.Contains(t, formatted, "lateral (select")
require.Contains(t, formatted, ".nodes")
}
Expand All @@ -100,7 +100,7 @@ func TestProjectionStagesPathBeforeReadingComponents(t *testing.T) {
formatted, err := Translated(translation)
require.NoError(t, err)
require.Contains(t, formatted, "lateral (select")
require.Equal(t, 1, strings.Count(formatted, "ordered_edges_to_path"), formatted)
require.Equal(t, 1, strings.Count(formatted.Statement, "ordered_edges_to_path"), formatted)
require.Contains(t, formatted, ".nodes")
require.Contains(t, formatted, ".edges")
}
Expand All @@ -117,8 +117,8 @@ func TestProjectionStagesRepeatedPathComponents(t *testing.T) {
formatted, err := Translated(translation)
require.NoError(t, err)
require.Contains(t, formatted, "lateral (select")
require.Equal(t, 1, strings.Count(formatted, "ordered_edges_to_path"), formatted)
require.Equal(t, 1, strings.Count(formatted, "from unnest"), formatted)
require.Equal(t, 1, strings.Count(formatted.Statement, "ordered_edges_to_path"), formatted)
require.Equal(t, 1, strings.Count(formatted.Statement, "from unnest"), formatted)
require.Contains(t, formatted, ".nodes")
require.Contains(t, formatted, ".edges")
}
Expand All @@ -136,7 +136,7 @@ func TestRelationshipEndpointFunctionsUseEdgeCompositeArguments(t *testing.T) {

formatted, err := Translated(translation)
require.NoError(t, err)
normalized := strings.Join(strings.Fields(formatted), " ")
normalized := strings.Join(strings.Fields(formatted.Statement), " ")

require.Contains(t, normalized, "start_node(((s0.e0).id, (s0.e0).start_id, (s0.e0).end_id, (s0.e0).kind_id, (s0.e0).properties)::edgecomposite)")
require.Contains(t, normalized, "end_node(((s0.e0).id, (s0.e0).start_id, (s0.e0).end_id, (s0.e0).kind_id, (s0.e0).properties)::edgecomposite)")
Expand All @@ -159,7 +159,7 @@ RETURN p

formatted, err := Translated(translation)
require.NoError(t, err)
normalized := strings.Join(strings.Fields(formatted), " ")
normalized := strings.Join(strings.Fields(formatted.Statement), " ")

require.Contains(t, normalized, "from edge i0")
require.Contains(t, normalized, "start_node((i0.id, i0.start_id, i0.end_id, i0.kind_id, i0.properties)::edgecomposite)")
Expand Down Expand Up @@ -193,7 +193,7 @@ func TestCollectMembershipOnlyProjectionUsesIDs(t *testing.T) {

formatted, err := Translated(translation)
require.NoError(t, err)
normalized := strings.Join(strings.Fields(formatted), " ")
normalized := strings.Join(strings.Fields(formatted.Statement), " ")

require.Contains(t, normalized, "array_agg((n0).id)")
require.Contains(t, normalized, "array []::int8[]")
Expand All @@ -215,7 +215,7 @@ func TestReturnedCollectNodeKeepsCompositeArray(t *testing.T) {

formatted, err := Translated(translation)
require.NoError(t, err)
normalized := strings.Join(strings.Fields(formatted), " ")
normalized := strings.Join(strings.Fields(formatted.Statement), " ")

require.Contains(t, normalized, "array []::nodecomposite[]")
require.NotContains(t, normalized, "array_agg((n0).id)")
Expand Down
6 changes: 3 additions & 3 deletions cypher/models/pgsql/translate/predicate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func translatePredicateQuery(t *testing.T, cypherQuery string, parameters map[st
formatted, err := Translated(translation)
require.NoError(t, err)

return formatted
return formatted.Statement
}

func TestExclusiveDisjunctionTranslates(t *testing.T) {
Expand Down Expand Up @@ -271,8 +271,8 @@ RETURN n`)

// Extract the individual CTE bodies so each assertion is scoped to the CTE it
// describes, rather than matching anywhere in the flattened query string.
s1Body := extractCTEBody(t, formatted, "s1")
s2Body := extractCTEBody(t, formatted, "s2")
s1Body := extractCTEBody(t, formatted.Statement, "s1")
s2Body := extractCTEBody(t, formatted.Statement, "s2")

// The predicate root CTE (s1) must NOT have the outer MATCH frame (s0) as a
// comma-joined FROM source. OmitPreviousFrameSource suppresses it so the subquery
Expand Down
3 changes: 2 additions & 1 deletion cypher/models/pgsql/visualization/visualizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ func SQLToDigraph(node pgsql.SyntaxNode) (Graph, error) {
if title, err := format.SyntaxNode(node); err != nil {
return Graph{}, err
} else {
visualizer.Graph.Title = title
// TODO: do we need to use Parameters here somehow?
visualizer.Graph.Title = title.Statement
}

return visualizer.Graph, walk.PgSQL(node, visualizer)
Expand Down
Loading
Loading