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
30 changes: 15 additions & 15 deletions cypher/models/pgsql/format/format_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func TestFormat_TypeCastedParenthetical(t *testing.T) {
formattedQuery, err := format.Expression(typeCastedParenthetical, format.NewOutputBuilder())

require.Nil(t, err)
require.Equal(t, "('str')::text", formattedQuery)
require.Equal(t, "('str')::text", formattedQuery.Statement)
}

func TestFormat_Case(t *testing.T) {
Expand All @@ -48,7 +48,7 @@ func TestFormat_Case(t *testing.T) {
}, format.NewOutputBuilder())

require.NoError(t, err)
require.Equal(t, "case when s0.root_id != s0.next_id then true else shortest_path_self_endpoint_error(s0.root_id, s0.next_id) end", formattedQuery)
require.Equal(t, "case when s0.root_id != s0.next_id then true else shortest_path_self_endpoint_error(s0.root_id, s0.next_id) end", formattedQuery.Statement)
}

func TestFormat_SelectDistinct(t *testing.T) {
Expand All @@ -67,7 +67,7 @@ func TestFormat_SelectDistinct(t *testing.T) {
}, format.NewOutputBuilder())

require.Nil(t, err)
require.Equal(t, "select distinct id from node;", formattedQuery)
require.Equal(t, "select distinct id from node;", formattedQuery.Statement)
}

func TestFormat_LateralSubqueryJoin(t *testing.T) {
Expand Down Expand Up @@ -115,7 +115,7 @@ func TestFormat_LateralSubqueryJoin(t *testing.T) {
}, format.NewOutputBuilder())

require.Nil(t, err)
require.Equal(t, "select n.id, e.id from node n join lateral (select e.id from edge e where e.start_id = n.id offset 0) e on true;", formattedQuery)
require.Equal(t, "select n.id, e.id from node n join lateral (select e.id from edge e where e.start_id = n.id offset 0) e on true;", formattedQuery.Statement)
}

func TestFormat_Delete(t *testing.T) {
Expand All @@ -132,7 +132,7 @@ func TestFormat_Delete(t *testing.T) {
}, format.NewOutputBuilder())

require.Nil(t, err)
require.Equal(t, "delete from table t where t.col1 < 4;", formattedQuery)
require.Equal(t, "delete from table t where t.col1 < 4;", formattedQuery.Statement)
}

func TestFormat_Update(t *testing.T) {
Expand All @@ -158,7 +158,7 @@ func TestFormat_Update(t *testing.T) {
}, format.NewOutputBuilder())

require.Nil(t, err)
require.Equal(t, "update table t set col1 = 1, col2 = '12345' where t.col1 < 4;", formattedQuery)
require.Equal(t, "update table t set col1 = 1, col2 = '12345' where t.col1 < 4;", formattedQuery.Statement)
}

func TestFormat_Insert(t *testing.T) {
Expand All @@ -177,7 +177,7 @@ func TestFormat_Insert(t *testing.T) {
}, format.NewOutputBuilder())

require.Nil(t, err)
require.Equal(t, "insert into table (col1, col2, col3) values ('1', 1, false);", formattedQuery)
require.Equal(t, "insert into table (col1, col2, col3) values ('1', 1, false);", formattedQuery.Statement)

formattedQuery, err = format.Statement(pgsql.Insert{
Table: pgsql.TableReference{
Expand Down Expand Up @@ -206,7 +206,7 @@ func TestFormat_Insert(t *testing.T) {
}, format.NewOutputBuilder())

require.Nil(t, err)
require.Equal(t, "insert into table (col1, col2, col3) select * from other where other.col1 = '1234';", formattedQuery)
require.Equal(t, "insert into table (col1, col2, col3) select * from other where other.col1 = '1234';", formattedQuery.Statement)

formattedQuery, err = format.Statement(pgsql.Insert{
Table: pgsql.TableReference{
Expand Down Expand Up @@ -238,7 +238,7 @@ func TestFormat_Insert(t *testing.T) {
}, format.NewOutputBuilder())

require.Nil(t, err)
require.Equal(t, "insert into table (col1, col2, col3) select * from other where other.col1 = '1234' returning id;", formattedQuery)
require.Equal(t, "insert into table (col1, col2, col3) select * from other where other.col1 = '1234' returning id;", formattedQuery.Statement)

formattedQuery, err = format.Statement(pgsql.Insert{
Table: pgsql.TableReference{
Expand Down Expand Up @@ -289,7 +289,7 @@ func TestFormat_Insert(t *testing.T) {
}, format.NewOutputBuilder())

require.Nil(t, err)
require.Equal(t, "insert into table (col1, col2, col3) select * from other where other.col1 = '1234' on conflict on constraint other.hash_constraint do update set hit_count = hit_count + 1 where hit_count < 9999 returning id, hit_count;", formattedQuery)
require.Equal(t, "insert into table (col1, col2, col3) select * from other where other.col1 = '1234' on conflict on constraint other.hash_constraint do update set hit_count = hit_count + 1 where hit_count < 9999 returning id, hit_count;", formattedQuery.Statement)

formattedQuery, err = format.Statement(pgsql.Insert{
Table: pgsql.TableReference{
Expand Down Expand Up @@ -339,7 +339,7 @@ func TestFormat_Insert(t *testing.T) {
}, format.NewOutputBuilder())

require.Nil(t, err)
require.Equal(t, "insert into table (col1, col2, col3) select * from other where other.col1 = '1234' on conflict (hash) do update set hit_count = hit_count + 1 where hit_count < 9999;", formattedQuery)
require.Equal(t, "insert into table (col1, col2, col3) select * from other where other.col1 = '1234' on conflict (hash) do update set hit_count = hit_count + 1 where hit_count < 9999;", formattedQuery.Statement)
}

func TestFormat_Query(t *testing.T) {
Expand Down Expand Up @@ -367,7 +367,7 @@ func TestFormat_Query(t *testing.T) {

formattedQuery, err := format.Statement(query, format.NewOutputBuilder())
require.Nil(t, err)
require.Equal(t, "select * from table t where t.col1 > 1;", formattedQuery)
require.Equal(t, "select * from table t where t.col1 > 1;", formattedQuery.Statement)
}

func TestFormat_Merge(t *testing.T) {
Expand Down Expand Up @@ -441,7 +441,7 @@ func TestFormat_Merge(t *testing.T) {
}, format.NewOutputBuilder())

require.Nil(t, err)
require.Equal(t, "merge into table t using source s on t.source_id = s.id when matched and t.value > s.value then update set updated_at = now() when matched and t.value <= s.value then update set value = s.value, t.updated_at = now() when matched and t.value = s.value then delete when not matched and t.value = 0 then insert (hit_count) values (0);", formattedQuery)
require.Equal(t, "merge into table t using source s on t.source_id = s.id when matched and t.value > s.value then update set updated_at = now() when matched and t.value <= s.value then update set value = s.value, t.updated_at = now() when matched and t.value = s.value then delete when not matched and t.value = 0 then insert (hit_count) values (0);", formattedQuery.Statement)
}

func TestFormat_CTEs(t *testing.T) {
Expand Down Expand Up @@ -661,7 +661,7 @@ func TestFormat_CTEs(t *testing.T) {
}, format.NewOutputBuilder())

require.Nil(t, err)
require.Equal(t, "with recursive expansion_1(root_id, next_id, depth, stop, is_cycle, path) as materialized (select r.start_id, r.end_id, 1, false, r.start_id = r.end_id, array [r.id] from edge r join node a on a.id = r.start_id where a.kind_ids operator (pg_catalog.&&) array [23]::int2[] union all select expansion_1.root_id, r.end_id, expansion_1.depth + 1, b.kind_ids operator (pg_catalog.&&) array [24]::int2[], r.id = any(expansion_1.path), expansion_1.path || r.id from expansion_1 join edge r on r.start_id = expansion_1.next_id join node b on b.id = r.end_id where not expansion_1.is_cycle and not expansion_1.stop) select a.properties, b.properties from expansion_1 join node a on a.id = expansion_1.root_id join node b on b.id = expansion_1.next_id where not expansion_1.is_cycle and expansion_1.stop;", formattedQuery)
require.Equal(t, "with recursive expansion_1(root_id, next_id, depth, stop, is_cycle, path) as materialized (select r.start_id, r.end_id, 1, false, r.start_id = r.end_id, array [r.id] from edge r join node a on a.id = r.start_id where a.kind_ids operator (pg_catalog.&&) array [23]::int2[] union all select expansion_1.root_id, r.end_id, expansion_1.depth + 1, b.kind_ids operator (pg_catalog.&&) array [24]::int2[], r.id = any(expansion_1.path), expansion_1.path || r.id from expansion_1 join edge r on r.start_id = expansion_1.next_id join node b on b.id = r.end_id where not expansion_1.is_cycle and not expansion_1.stop) select a.properties, b.properties from expansion_1 join node a on a.id = expansion_1.root_id join node b on b.id = expansion_1.next_id where not expansion_1.is_cycle and expansion_1.stop;", formattedQuery.Statement)
}

func TestFormat_QueryInjection(t *testing.T) {
Expand Down Expand Up @@ -689,5 +689,5 @@ func TestFormat_QueryInjection(t *testing.T) {

formattedQuery, err := format.Statement(query, format.NewOutputBuilder())
require.Nil(t, err)
require.Equal(t, `select * from table t where t.col1 = 'alpha'' || select (''malicious'')';`, formattedQuery)
require.Equal(t, `select * from table t where t.col1 = 'alpha'' || select (''malicious'')';`, formattedQuery.Statement)
}
51 changes: 51 additions & 0 deletions cypher/models/pgsql/id_generator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package pgsql

import (
"strconv"
)

// IdentifierGenerator is a map that creates a unique identifier for each call with a given
// data type. This ensures that renamed identifiers in queries do not conflict with each other.
type IdentifierGenerator map[DataType]int

func (s IdentifierGenerator) NewIdentifier(dataType DataType) (Identifier, error) {
var prefixStr string

switch dataType {
case ExpansionPattern:
prefixStr = "ex"
case ExpansionPath:
prefixStr = "ep"
case PathComposite:
prefixStr = "pc"
case NodeComposite:
prefixStr = "n"
case EdgeComposite:
prefixStr = "e"
case PathEdge:
dataType = EdgeComposite
prefixStr = "e"
case Scope:
prefixStr = "s"
case ParameterIdentifier:
prefixStr = "pi"
default:
// Make this data type the unknown generic
dataType = UnknownDataType
prefixStr = "i"
}

var (
nextID = s[dataType]
nextIDStr = strconv.Itoa(nextID)
)

// Increment the ID
s[dataType] = nextID + 1

return Identifier(prefixStr + nextIDStr), nil
}

func NewIdentifierGenerator() IdentifierGenerator {
return IdentifierGenerator{}
}
Loading
Loading