Skip to content
Open
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
4 changes: 2 additions & 2 deletions cmd/benchmark/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ func main() {
driver = flag.String("driver", "pg", "database driver (pg, neo4j)")
connStr = flag.String("connection", "", "database connection string (or PG_CONNECTION_STRING)")
iterations = flag.Int("iterations", 10, "timed iterations per scenario")
output = flag.String("output", "", "markdown output file (default: stdout)")
datasetDir = flag.String("dataset-dir", "integration/testdata", "path to testdata directory")
output = flag.String("output", "", "markdown output file (default: stdout)")
datasetDir = flag.String("dataset-dir", "integration/testdata", "path to testdata directory")
localDataset = flag.String("local-dataset", "", "additional local dataset (e.g. local/phantom)")
onlyDataset = flag.String("dataset", "", "run only this dataset (e.g. diamond, local/phantom)")
)
Expand Down
9 changes: 8 additions & 1 deletion cypher/models/pgsql/format/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -530,7 +530,11 @@ func Expression(expression pgsql.SyntaxNode, builder *OutputBuilder) (string, er
}

func formatSelect(builder *OutputBuilder, selectStmt pgsql.Select) error {
builder.Write("select ")
if selectStmt.Distinct {
builder.Write("select distinct ")
} else {
builder.Write("select ")
}

for idx, projection := range selectStmt.Projection {
if idx > 0 {
Expand Down Expand Up @@ -783,6 +787,9 @@ func formatSetExpression(builder *OutputBuilder, expression pgsql.SetExpression)
case pgsql.Values:
return formatNode(builder, typedSetExpression)

case pgsql.Insert:
return formatInsertStatement(builder, typedSetExpression)

case pgsql.Update:
return formatUpdateStatement(builder, typedSetExpression)

Expand Down
8 changes: 8 additions & 0 deletions cypher/models/pgsql/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -956,6 +956,14 @@ type Insert struct {
Returning []SelectItem
}

func (s Insert) AsExpression() Expression {
return s
}

func (s Insert) AsSetExpression() SetExpression {
return s
}

func (s Insert) AsStatement() Statement {
return s
}
Expand Down
2 changes: 1 addition & 1 deletion cypher/models/pgsql/test/query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func TestQuery_KindGeneratesInclusiveKindMatcher(t *testing.T) {
t.Errorf("could not build query: %v", err)
}

translatedQuery, err := translate.Translate(context.Background(), builtQuery, mapper, nil)
translatedQuery, err := translate.Translate(context.Background(), builtQuery, mapper, nil, 0)
if err != nil {
t.Errorf("could not translate query: %#v: %v", builtQuery, err)
}
Expand Down
11 changes: 8 additions & 3 deletions cypher/models/pgsql/test/testcase.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ func (s *TranslationTestCase) WriteTo(output io.Writer, kindMapper pgsql.KindMap
}
}

if translation, err := translate.Translate(context.Background(), regularQuery, kindMapper, nil); err != nil {
if translation, err := translate.Translate(context.Background(), regularQuery, kindMapper, nil, 0); err != nil {
return err
} else if formattedQuery, err := translate.Translated(translation); err != nil {
return err
Expand Down Expand Up @@ -164,7 +164,7 @@ func (s *TranslationTestCase) Assert(t *testing.T, expectedSQL string, kindMappe
}
}

if translation, err := translate.Translate(context.Background(), regularQuery, kindMapper, nil); err != nil {
if translation, err := translate.Translate(context.Background(), regularQuery, kindMapper, nil, 0); err != nil {
t.Fatalf("Failed to translate cypher query: %s - %v", s.Cypher, err)
} else if formattedQuery, err := translate.Translated(translation); err != nil {
t.Fatalf("Failed to format SQL translatedQuery: %v", err)
Expand Down Expand Up @@ -200,7 +200,12 @@ func (s *TranslationTestCase) AssertLive(ctx context.Context, t *testing.T, driv
}
}

if translation, err := translate.Translate(context.Background(), regularQuery, driver.KindMapper(), s.CypherParams); err != nil {
defaultGraph, hasDefaultGraph := driver.DefaultGraph()
if !hasDefaultGraph {
t.Fatalf("Driver has no default graph set")
}

if translation, err := translate.Translate(context.Background(), regularQuery, driver.KindMapper(), s.CypherParams, defaultGraph.ID); err != nil {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use the passed ctx instead of context.Background() in AssertLive.

AssertLive accepts a ctx context.Context and correctly threads it into driver.Run on Line 213, but translate.Translate here is invoked with context.Background(). If the test's context carries a deadline/cancellation or test-scoped values (e.g., for the context-aware kind mapper), they are ignored during translation. The pre-existing WriteTo/Assert paths have no caller-provided context, but AssertLive does.

🛠️ Proposed fix
-		if translation, err := translate.Translate(context.Background(), regularQuery, driver.KindMapper(), s.CypherParams, defaultGraph.ID); err != nil {
+		if translation, err := translate.Translate(ctx, regularQuery, driver.KindMapper(), s.CypherParams, defaultGraph.ID); err != nil {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if translation, err := translate.Translate(context.Background(), regularQuery, driver.KindMapper(), s.CypherParams, defaultGraph.ID); err != nil {
if translation, err := translate.Translate(ctx, regularQuery, driver.KindMapper(), s.CypherParams, defaultGraph.ID); err != nil {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cypher/models/pgsql/test/testcase.go` at line 208, In AssertLive,
translate.Translate is incorrectly called with context.Background(), which
ignores the test-provided ctx; replace context.Background() with the passed-in
ctx so the translation respects cancellation/deadlines and context values (i.e.,
change the translate.Translate call in AssertLive to use ctx when invoking
translate.Translate(ctx, regularQuery, driver.KindMapper(), s.CypherParams,
defaultGraph.ID)).

t.Fatalf("Failed to translate cypher query: %s - %v", s.Cypher, err)
} else if formattedQuery, err := translate.Translated(translation); err != nil {
t.Fatalf("Failed to format SQL translatedQuery: %v", err)
Expand Down
73 changes: 73 additions & 0 deletions cypher/models/pgsql/test/translation_cases/create.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
-- Copyright 2026 Specter Ops, Inc.
--
-- Licensed under the Apache License, Version 2.0
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
-- SPDX-License-Identifier: Apache-2.0

-- case: create (n:NodeKind1 {name: 'Bob'}) return n
with s0 as (insert into node (graph_id, kind_ids, properties) values (0, array [1]::int2[], jsonb_build_object('name', 'Bob')::jsonb) returning (id, kind_ids, properties)::nodecomposite as n0) select s0.n0 as n from s0;

-- case: create (n:NodeKind1)
with s0 as (insert into node (graph_id, kind_ids, properties) values (0, array [1]::int2[], jsonb_build_object()::jsonb) returning (id, kind_ids, properties)::nodecomposite as n0) select 1;

-- case: create (n)
with s0 as (insert into node (graph_id, kind_ids, properties) values (0, array []::int2[], jsonb_build_object()::jsonb) returning (id, kind_ids, properties)::nodecomposite as n0) select 1;

-- case: create (n:NodeKind1:NodeKind2 {name: 'Bob', value: 1}) return n
with s0 as (insert into node (graph_id, kind_ids, properties) values (0, array [1, 2]::int2[], jsonb_build_object('name', 'Bob', 'value', 1)::jsonb) returning (id, kind_ids, properties)::nodecomposite as n0) select s0.n0 as n from s0;

-- case: create (n) return n
with s0 as (insert into node (graph_id, kind_ids, properties) values (0, array []::int2[], jsonb_build_object()::jsonb) returning (id, kind_ids, properties)::nodecomposite as n0) select s0.n0 as n from s0;

-- case: create (n:NodeKind1 {name: 'Alice', value: 42}) return n
with s0 as (insert into node (graph_id, kind_ids, properties) values (0, array [1]::int2[], jsonb_build_object('name', 'Alice', 'value', 42)::jsonb) returning (id, kind_ids, properties)::nodecomposite as n0) select s0.n0 as n from s0;

-- case: match (n:NodeKind1) with n create (m:NodeKind2) return m
with s0 as (with s1 as (select (n0.id, n0.kind_ids, n0.properties)::nodecomposite as n0 from node n0 where n0.kind_ids operator (pg_catalog.@>) array [1]::int2[]) select s1.n0 as n0 from s1), s2 as (insert into node (graph_id, kind_ids, properties) values (0, array [2]::int2[], jsonb_build_object()::jsonb) returning (id, kind_ids, properties)::nodecomposite as n1) select s2.n1 as m from s2;

-- case: match (n:NodeKind1) with n create (m:NodeKind2 {name: 'Bob'}) return m
with s0 as (with s1 as (select (n0.id, n0.kind_ids, n0.properties)::nodecomposite as n0 from node n0 where n0.kind_ids operator (pg_catalog.@>) array [1]::int2[]) select s1.n0 as n0 from s1), s2 as (insert into node (graph_id, kind_ids, properties) values (0, array [2]::int2[], jsonb_build_object('name', 'Bob')::jsonb) returning (id, kind_ids, properties)::nodecomposite as n1) select s2.n1 as m from s2;

-- case: match (n:NodeKind1) with n create (m:NodeKind2)
with s0 as (with s1 as (select (n0.id, n0.kind_ids, n0.properties)::nodecomposite as n0 from node n0 where n0.kind_ids operator (pg_catalog.@>) array [1]::int2[]) select s1.n0 as n0 from s1), s2 as (insert into node (graph_id, kind_ids, properties) values (0, array [2]::int2[], jsonb_build_object()::jsonb) returning (id, kind_ids, properties)::nodecomposite as n1) select 1;

-- case: create (a:NodeKind1)-[:EdgeKind1]->(b:NodeKind2)
with s0 as (insert into node (graph_id, kind_ids, properties) values (0, array [1]::int2[], jsonb_build_object()::jsonb) returning (id, kind_ids, properties)::nodecomposite as n0), s1 as (insert into node (graph_id, kind_ids, properties) values (0, array [2]::int2[], jsonb_build_object()::jsonb) returning (id, kind_ids, properties)::nodecomposite as n1), s2 as (insert into edge (start_id, end_id, kind_id, properties) select (s0.n0).id, (s1.n1).id, 3, jsonb_build_object()::jsonb from s0, s1 returning (id, start_id, end_id, kind_id, properties)::edgecomposite as e0) select 1;

-- case: create (a:NodeKind1)-[r:EdgeKind1]->(b:NodeKind2) return r
with s0 as (insert into node (graph_id, kind_ids, properties) values (0, array [1]::int2[], jsonb_build_object()::jsonb) returning (id, kind_ids, properties)::nodecomposite as n0), s1 as (insert into node (graph_id, kind_ids, properties) values (0, array [2]::int2[], jsonb_build_object()::jsonb) returning (id, kind_ids, properties)::nodecomposite as n1), s2 as (insert into edge (start_id, end_id, kind_id, properties) select (s0.n0).id, (s1.n1).id, 3, jsonb_build_object()::jsonb from s0, s1 returning (id, start_id, end_id, kind_id, properties)::edgecomposite as e0) select s2.e0 as r from s2;

-- case: create (a:NodeKind1)-[:EdgeKind1 {name: 'rel'}]->(b:NodeKind2)
with s0 as (insert into node (graph_id, kind_ids, properties) values (0, array [1]::int2[], jsonb_build_object()::jsonb) returning (id, kind_ids, properties)::nodecomposite as n0), s1 as (insert into node (graph_id, kind_ids, properties) values (0, array [2]::int2[], jsonb_build_object()::jsonb) returning (id, kind_ids, properties)::nodecomposite as n1), s2 as (insert into edge (start_id, end_id, kind_id, properties) select (s0.n0).id, (s1.n1).id, 3, jsonb_build_object('name', 'rel')::jsonb from s0, s1 returning (id, start_id, end_id, kind_id, properties)::edgecomposite as e0) select 1;

-- case: create (a:NodeKind1)<-[:EdgeKind1]-(b:NodeKind2)
with s0 as (insert into node (graph_id, kind_ids, properties) values (0, array [1]::int2[], jsonb_build_object()::jsonb) returning (id, kind_ids, properties)::nodecomposite as n0), s1 as (insert into node (graph_id, kind_ids, properties) values (0, array [2]::int2[], jsonb_build_object()::jsonb) returning (id, kind_ids, properties)::nodecomposite as n1), s2 as (insert into edge (start_id, end_id, kind_id, properties) select (s1.n1).id, (s0.n0).id, 3, jsonb_build_object()::jsonb from s1, s0 returning (id, start_id, end_id, kind_id, properties)::edgecomposite as e0) select 1;

-- case: match (a:NodeKind1) with a create (a)-[:EdgeKind1]->(b:NodeKind2)
with s0 as (with s1 as (select (n0.id, n0.kind_ids, n0.properties)::nodecomposite as n0 from node n0 where n0.kind_ids operator (pg_catalog.@>) array [1]::int2[]) select s1.n0 as n0 from s1), s2 as (insert into node (graph_id, kind_ids, properties) values (0, array [2]::int2[], jsonb_build_object()::jsonb) returning (id, kind_ids, properties)::nodecomposite as n1), s3 as (insert into edge (start_id, end_id, kind_id, properties) select (s0.n0).id, (s2.n1).id, 3, jsonb_build_object()::jsonb from s0, s2 returning (id, start_id, end_id, kind_id, properties)::edgecomposite as e0) select 1;

-- case: match (a:NodeKind1) with a create (a)-[r:EdgeKind1]->(b:NodeKind2) return r
with s0 as (with s1 as (select (n0.id, n0.kind_ids, n0.properties)::nodecomposite as n0 from node n0 where n0.kind_ids operator (pg_catalog.@>) array [1]::int2[]) select s1.n0 as n0 from s1), s2 as (insert into node (graph_id, kind_ids, properties) values (0, array [2]::int2[], jsonb_build_object()::jsonb) returning (id, kind_ids, properties)::nodecomposite as n1), s3 as (insert into edge (start_id, end_id, kind_id, properties) select (s0.n0).id, (s2.n1).id, 3, jsonb_build_object()::jsonb from s0, s2 returning (id, start_id, end_id, kind_id, properties)::edgecomposite as e0) select s3.e0 as r from s3;

-- case: create (a:NodeKind1 {name: 'abc'})-[:EdgeKind1 {prop: 123}]->(:NodeKind2 {name: 'test'}) return a
with s0 as (insert into node (graph_id, kind_ids, properties) values (0, array [1]::int2[], jsonb_build_object('name', 'abc')::jsonb) returning (id, kind_ids, properties)::nodecomposite as n0), s1 as (insert into node (graph_id, kind_ids, properties) values (0, array [2]::int2[], jsonb_build_object('name', 'test')::jsonb) returning (id, kind_ids, properties)::nodecomposite as n1), s2 as (insert into edge (start_id, end_id, kind_id, properties) select (s0.n0).id, (s1.n1).id, 3, jsonb_build_object('prop', 123)::jsonb from s0, s1 returning (id, start_id, end_id, kind_id, properties)::edgecomposite as e0) select s0.n0 as a from s0;

-- case: create (:NodeKind1 {name: 'abc'})-[:EdgeKind1 {prop: 123}]->(c:NodeKind2 {name: 'test'}) return c
with s0 as (insert into node (graph_id, kind_ids, properties) values (0, array [1]::int2[], jsonb_build_object('name', 'abc')::jsonb) returning (id, kind_ids, properties)::nodecomposite as n0), s1 as (insert into node (graph_id, kind_ids, properties) values (0, array [2]::int2[], jsonb_build_object('name', 'test')::jsonb) returning (id, kind_ids, properties)::nodecomposite as n1), s2 as (insert into edge (start_id, end_id, kind_id, properties) select (s0.n0).id, (s1.n1).id, 3, jsonb_build_object('prop', 123)::jsonb from s0, s1 returning (id, start_id, end_id, kind_id, properties)::edgecomposite as e0) select s1.n1 as c from s1;

-- case: create (:NodeKind1 {name: 'abc'})-[:EdgeKind1 {prop: 123}]->(c:NodeKind2 {name: 'test'})<-[:EdgeKind2]-(:NodeKind1 {name: 'other'}) return c
with s0 as (insert into node (graph_id, kind_ids, properties) values (0, array [1]::int2[], jsonb_build_object('name', 'abc')::jsonb) returning (id, kind_ids, properties)::nodecomposite as n0), s1 as (insert into node (graph_id, kind_ids, properties) values (0, array [2]::int2[], jsonb_build_object('name', 'test')::jsonb) returning (id, kind_ids, properties)::nodecomposite as n1), s2 as (insert into node (graph_id, kind_ids, properties) values (0, array [1]::int2[], jsonb_build_object('name', 'other')::jsonb) returning (id, kind_ids, properties)::nodecomposite as n2), s3 as (insert into edge (start_id, end_id, kind_id, properties) select (s0.n0).id, (s1.n1).id, 3, jsonb_build_object('prop', 123)::jsonb from s0, s1 returning (id, start_id, end_id, kind_id, properties)::edgecomposite as e0), s4 as (insert into edge (start_id, end_id, kind_id, properties) select (s2.n2).id, (s1.n1).id, 4, jsonb_build_object()::jsonb from s2, s1 returning (id, start_id, end_id, kind_id, properties)::edgecomposite as e1) select s1.n1 as c from s1;

-- case: create p = (:NodeKind1 {name: 'abc'})-[:EdgeKind1 {prop: 123}]->(:NodeKind2 {name: 'test'}) return p
with s0 as (insert into node (graph_id, kind_ids, properties) values (0, array [1]::int2[], jsonb_build_object('name', 'abc')::jsonb) returning (id, kind_ids, properties)::nodecomposite as n0), s1 as (insert into node (graph_id, kind_ids, properties) values (0, array [2]::int2[], jsonb_build_object('name', 'test')::jsonb) returning (id, kind_ids, properties)::nodecomposite as n1), s2 as (insert into edge (start_id, end_id, kind_id, properties) select (s0.n0).id, (s1.n1).id, 3, jsonb_build_object('prop', 123)::jsonb from s0, s1 returning (id, start_id, end_id, kind_id, properties)::edgecomposite as e0) select edges_to_path(variadic array [(s2.e0).id]::int8[])::pathcomposite as p from s0, s2, s1;

Loading
Loading