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
11 changes: 10 additions & 1 deletion cypher/models/cypher/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -770,14 +770,23 @@ type Literal struct {
}

func NewLiteral(value any, null bool) *Literal {
if !null {
if strValue, typeOK := value.(string); typeOK {
return NewStringLiteral(strValue)
}
}

return &Literal{
Value: value,
Null: null,
}
}

func NewStringLiteral(value string) *Literal {
return NewLiteral("'"+value+"'", false)
return &Literal{
Value: "'" + value + "'",
Null: false,
}
}

func (s *Literal) copy() *Literal {
Expand Down
10 changes: 9 additions & 1 deletion cypher/models/pgsql/format/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"strconv"
"strings"
"time"

"github.com/specterops/dawgs/cypher/models/pgsql"
)
Expand Down Expand Up @@ -70,6 +71,9 @@ func formatSlice[T any, TS []T](builder *OutputBuilder, slice TS, dataType pgsql

func formatValue(builder *OutputBuilder, value any) error {
switch typedValue := value.(type) {
case time.Time:
builder.Write("'", typedValue.Format(time.RFC3339Nano), "'::timestamp with time zone")

case uint:
builder.Write(strconv.FormatUint(uint64(typedValue), 10))

Expand Down Expand Up @@ -116,7 +120,11 @@ func formatValue(builder *OutputBuilder, value any) error {
return formatSlice(builder, typedValue, pgsql.Int8Array)

case string:
builder.Write("'", typedValue, "'")
// Double single quotes per SQL string literal rules
builder.Write("'", strings.ReplaceAll(typedValue, "'", "''"), "'")

case []string:
return formatSlice(builder, typedValue, pgsql.TextArray)

case bool:
builder.Write(strconv.FormatBool(typedValue))
Expand Down
21 changes: 11 additions & 10 deletions cypher/models/pgsql/test/testcase.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,13 @@ import (
"testing"
"time"

"github.com/specterops/dawgs/drivers/pg"

"cuelang.org/go/pkg/regexp"
"github.com/specterops/dawgs/cypher/frontend"
"github.com/specterops/dawgs/cypher/models/cypher"
"github.com/specterops/dawgs/cypher/models/pgsql"
"github.com/specterops/dawgs/cypher/models/pgsql/translate"
"github.com/specterops/dawgs/cypher/models/walk"
"github.com/specterops/dawgs/database"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -183,7 +182,7 @@ func (s *TranslationTestCase) Assert(t *testing.T, expectedSQL string, kindMappe
}
}

func (s *TranslationTestCase) AssertLive(ctx context.Context, t *testing.T, driver *pg.Driver) {
func (s *TranslationTestCase) AssertLive(ctx context.Context, t *testing.T, db database.Instance) {
if regularQuery, err := frontend.ParseCypher(frontend.NewContext(), s.Cypher); err != nil {
t.Fatalf("Failed to compile cypher query: %s - %v", s.Cypher, err)
} else {
Expand All @@ -200,13 +199,15 @@ 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 {
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)
} else {
require.Nil(t, driver.Run(ctx, "explain "+formattedQuery, translation.Parameters))
}
require.NoError(t, db.Session(ctx, func(ctx context.Context, driver database.Driver) error {
result := driver.Explain(ctx, regularQuery, s.CypherParams)

if err := result.Close(ctx); err != nil {
return err
}

return result.Error()
}))
}
}

Expand Down
2 changes: 1 addition & 1 deletion cypher/models/pgsql/test/translation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"strings"
"testing"

"github.com/specterops/dawgs/drivers/pg/pgutil"
"github.com/specterops/dawgs/database/pg/pgutil"

"github.com/specterops/dawgs/cypher/models/pgsql"
"github.com/specterops/dawgs/graph"
Expand Down
39 changes: 15 additions & 24 deletions cypher/models/pgsql/test/validation_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import (
"runtime/debug"
"testing"

"github.com/jackc/pgx/v5/pgxpool"
"github.com/specterops/dawgs/database"

"github.com/specterops/dawgs"
"github.com/specterops/dawgs/drivers/pg"
"github.com/specterops/dawgs/database/pg"
"github.com/specterops/dawgs/graph"
"github.com/specterops/dawgs/util/size"
"github.com/stretchr/testify/require"
Expand All @@ -32,34 +32,25 @@ func TestTranslationTestCases(t *testing.T) {

require.NotEmpty(t, pgConnectionStr)

if pgxPool, err := pgxpool.New(testCtx, pgConnectionStr); err != nil {
t.Fatalf("Failed opening database connection: %v", err)
} else if connection, err := dawgs.Open(context.TODO(), pg.DriverName, dawgs.Config{
if connection, err := dawgs.Open(context.TODO(), pg.DriverName, dawgs.Config{
GraphQueryMemoryLimit: size.Gibibyte,
Pool: pgxPool,
ConnectionString: pgConnectionStr,
}); err != nil {
Comment on lines +35 to 38
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Pass testCtx to dawgs.Open instead of context.TODO()

Use the test’s cancellable context so open/handshakes honor deadlines/cancellation hooks, consistent with the rest of the test.

Apply this diff:

-    if connection, err := dawgs.Open(context.TODO(), pg.DriverName, dawgs.Config{
+    if connection, err := dawgs.Open(testCtx, pg.DriverName, dawgs.Config{
         GraphQueryMemoryLimit: size.Gibibyte,
         ConnectionString:      pgConnectionStr,
     }); err != nil {

Run this to double-check config fields and driver symbol existence:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Config struct (expect: ConnectionString, GraphQueryMemoryLimit):"
rg -n --no-heading $'^type\\s+Config\\s+struct\\s*\\{' -A 30

echo
echo "pg driver name symbol (expect: DriverName):"
rg -n --no-heading $'DriverName' --glob '!**/vendor/**'

Length of output: 2303


Use testCtx when opening the DB connection (replace context.TODO())

Use the test’s cancellable context so open/handshakes honor deadlines/cancellation hooks. Verified: dawgs.Open accepts a context and dawgs.Config contains GraphQueryMemoryLimit and ConnectionString; pg.DriverName exists.

  • File: cypher/models/pgsql/test/validation_integration_test.go (around lines 35–38)

Apply this diff:

-    if connection, err := dawgs.Open(context.TODO(), pg.DriverName, dawgs.Config{
+    if connection, err := dawgs.Open(testCtx, pg.DriverName, dawgs.Config{
         GraphQueryMemoryLimit: size.Gibibyte,
         ConnectionString:      pgConnectionStr,
     }); 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 connection, err := dawgs.Open(context.TODO(), pg.DriverName, dawgs.Config{
GraphQueryMemoryLimit: size.Gibibyte,
Pool: pgxPool,
ConnectionString: pgConnectionStr,
}); err != nil {
if connection, err := dawgs.Open(testCtx, pg.DriverName, dawgs.Config{
GraphQueryMemoryLimit: size.Gibibyte,
ConnectionString: pgConnectionStr,
}); err != nil {
🤖 Prompt for AI Agents
In cypher/models/pgsql/test/validation_integration_test.go around lines 35 to
38, replace the call to context.TODO() passed into dawgs.Open with the test's
cancellable context (testCtx) so the open/handshake respects test
cancellation/deadlines; if testCtx is not already defined in this test, create
it with context.WithTimeout or context.WithCancel before use and defer its
cancel to ensure cleanup.

t.Fatalf("Failed opening database connection: %v", err)
} else if pgConnection, typeOK := connection.(*pg.Driver); !typeOK {
t.Fatalf("Invalid connection type: %T", connection)
} else {
defer connection.Close(testCtx)

graphSchema := graph.Schema{
Graphs: []graph.Graph{{
Name: "test",
Nodes: graph.Kinds{
graph.StringKind("NodeKind1"),
graph.StringKind("NodeKind2"),
},
Edges: graph.Kinds{
graph.StringKind("EdgeKind1"),
graph.StringKind("EdgeKind2"),
},
}},
DefaultGraph: graph.Graph{
Name: "test",
graphSchema := database.NewSchema("test", database.Graph{
Name: "test",
Nodes: graph.Kinds{
graph.StringKind("NodeKind1"),
graph.StringKind("NodeKind2"),
},
}
Edges: graph.Kinds{
graph.StringKind("EdgeKind1"),
graph.StringKind("EdgeKind2"),
},
})

if err := connection.AssertSchema(testCtx, graphSchema); err != nil {
t.Fatalf("Failed asserting graph schema: %v", err)
Expand All @@ -79,7 +70,7 @@ func TestTranslationTestCases(t *testing.T) {
}
}()

testCase.AssertLive(testCtx, t, pgConnection)
testCase.AssertLive(testCtx, t, connection)
})

casesRun += 1
Expand Down
2 changes: 1 addition & 1 deletion cypher/models/pgsql/visualization/visualizer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"context"
"testing"

"github.com/specterops/dawgs/drivers/pg/pgutil"
"github.com/specterops/dawgs/database/pg/pgutil"

"github.com/specterops/dawgs/cypher/frontend"
"github.com/specterops/dawgs/cypher/models/pgsql/translate"
Expand Down
95 changes: 95 additions & 0 deletions database/driver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package database

import (
"context"

"github.com/specterops/dawgs/graph"

"github.com/specterops/dawgs/cypher/models/cypher"
)

type Option int

const (
OptionReadOnly Option = 0
OptionReadWrite Option = 1
)

type Result interface {
HasNext(ctx context.Context) bool
Scan(scanTargets ...any) error
Error() error
Close(ctx context.Context) error

// Values returns the next values array from the result.
//
// Deprecated: This function will be removed in future version.
Values() []any
}

type Driver interface {
WithGraph(target Graph) Driver

// Eventually to be deprecated - this is currently a translation gap where cysql doesn't correctly
// marshal create statements
//
// Deprecated: This function will be removed in future version.
CreateNode(ctx context.Context, node *graph.Node) (graph.ID, error)

// Eventually to be deprecated - this is currently a translation gap where cysql doesn't correctly
// marshal create statements
//
// Deprecated: This function will be removed in future version.
CreateRelationship(ctx context.Context, relationship *graph.Relationship) (graph.ID, error)

Exec(ctx context.Context, query *cypher.RegularQuery, parameters map[string]any) Result
Explain(ctx context.Context, query *cypher.RegularQuery, parameters map[string]any) Result
Profile(ctx context.Context, query *cypher.RegularQuery, parameters map[string]any) Result

// Mapper is supporting backward compat for v1
//
// Deprecated: This function will be removed in future version.
Mapper() graph.ValueMapper
}

type QueryLogic func(ctx context.Context, driver Driver) error

type Instance interface {
AssertSchema(ctx context.Context, schema Schema) error
Session(ctx context.Context, driverLogic QueryLogic, options ...Option) error
Transaction(ctx context.Context, driverLogic QueryLogic, options ...Option) error
Close(ctx context.Context) error

// FetchKinds retrieves the complete list of kinds available to the database.
FetchKinds(ctx context.Context) (graph.Kinds, error)
}

type errorResult struct {
err error
}

func (s errorResult) HasNext(ctx context.Context) bool {
return false
}

func (s errorResult) Scan(scanTargets ...any) error {
return s.err
}

func (s errorResult) Error() error {
return s.err
}

func (s errorResult) Values() []any {
return nil
}

func (s errorResult) Close(ctx context.Context) error {
return nil
}

func NewErrorResult(err error) Result {
return errorResult{
err: err,
}
}
Loading