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
2 changes: 2 additions & 0 deletions cypher/frontend/expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,8 @@ type NonArithmeticOperatorExpressionVisitor struct {
func (s *NonArithmeticOperatorExpressionVisitor) EnterOC_NodeLabels(ctx *parser.OC_NodeLabelsContext) {
s.Expression = &cypher.KindMatcher{
Reference: s.Expression,
// Cypher-generated `KindMatcher`s should _always_ be exclusive to jive with the spec
IsExclusive: true,
}
}

Expand Down
20 changes: 13 additions & 7 deletions cypher/models/cypher/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ func (s AssignmentOperator) String() string {
return string(s)
}

type SyntaxNode any
type Expression SyntaxNode
type (
SyntaxNode any
Expression SyntaxNode
)

type ExpressionList interface {
Add(expression Expression)
Expand Down Expand Up @@ -744,12 +746,15 @@ func NewRangeQuantifier(value string) *RangeQuantifier {
type KindMatcher struct {
Reference Expression
Kinds graph.Kinds
// IsExclusive changes the kind matching operator from overlap (PG &&) to a stricter "left contains right" (PG @>)
IsExclusive bool
}

func NewKindMatcher(reference Expression, kinds graph.Kinds) *KindMatcher {
func NewKindMatcher(reference Expression, kinds graph.Kinds, isExclusive bool) *KindMatcher {
return &KindMatcher{
Reference: reference,
Kinds: kinds,
Reference: reference,
Kinds: kinds,
IsExclusive: isExclusive,
}
}

Expand All @@ -759,8 +764,9 @@ func (s *KindMatcher) copy() *KindMatcher {
}

return &KindMatcher{
Reference: Copy(s.Reference),
Kinds: Copy(s.Kinds),
Reference: Copy(s.Reference),
Kinds: Copy(s.Kinds),
IsExclusive: s.IsExclusive,
}
}

Expand Down
19 changes: 16 additions & 3 deletions cypher/models/pgsql/translate/kind.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,30 @@ import (
"github.com/specterops/dawgs/cypher/models/pgsql/pgd"
)

func newPGKindIDMatcher(scope *Scope, treeTranslator *ExpressionTreeTranslator, binding *BoundIdentifier, kindIDs []int16) error {
func newPGKindIDMatcher(scope *Scope, treeTranslator *ExpressionTreeTranslator, binding *BoundIdentifier, kindIDs []int16, isExclusive bool) error {
kindIDsLiteral := pgsql.NewLiteral(kindIDs, pgsql.Int2Array)

switch binding.DataType {
case pgsql.NodeComposite, pgsql.ExpansionRootNode, pgsql.ExpansionTerminalNode:
treeTranslator.PushOperand(pgd.Column(binding.Identifier, pgsql.ColumnKindIDs))
treeTranslator.PushOperand(kindIDsLiteral)

return treeTranslator.CompleteBinaryExpression(scope, pgsql.OperatorPGArrayLHSContainsRHS)
// In an exclusive kind match, if there are no kind IDs to be matched on,
// the behavior of the contains (`@>`) operator will select all nodes, which drastically differs from
// the overlap operator's behavior (matches nothing), so preserve the previous behavior.
//
// There shouldn't be a case in the Cypher frontend where a kind ID matcher is
// created without any kind IDs to match on, but `query.Kind`/`query.KindIn` can create those
// edge cases. We want any existing `query.Kind`/`query.KindIn` usages to match the previous behavior
// expectations by using overlap/`&&`, which protects from the empty RHS problem.
if isExclusive && len(kindIDs) > 0 {
return treeTranslator.CompleteBinaryExpression(scope, pgsql.OperatorPGArrayLHSContainsRHS)
} else {
return treeTranslator.CompleteBinaryExpression(scope, pgsql.OperatorPGArrayOverlap)
}

case pgsql.EdgeComposite, pgsql.ExpansionEdge:
// Edge kind checking is a strict equality, so the IsExclusive condition does not apply here.
treeTranslator.PushOperand(pgsql.CompoundIdentifier{binding.Identifier, pgsql.ColumnKindID})
treeTranslator.PushOperand(pgsql.NewAnyExpressionHinted(kindIDsLiteral))

Expand All @@ -39,6 +52,6 @@ func (s *Translator) translateKindMatcher(kindMatcher *cypher.KindMatcher) error
} else if kindIDs, err := s.kindMapper.MapKinds(kindMatcher.Kinds); err != nil {
return fmt.Errorf("failed to translate kinds: %w", err)
} else {
return newPGKindIDMatcher(s.scope, s.treeTranslator, binding, kindIDs)
return newPGKindIDMatcher(s.scope, s.treeTranslator, binding, kindIDs, kindMatcher.IsExclusive)
}
}
21 changes: 11 additions & 10 deletions query/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ import (
)

func convertCriteria[T any](criteria ...graph.Criteria) []T {
var (
converted = make([]T, len(criteria))
)
converted := make([]T, len(criteria))

for idx, nextCriteria := range criteria {
converted[idx] = nextCriteria.(T)
Expand Down Expand Up @@ -67,8 +65,9 @@ func DeleteKind(reference graph.Criteria, kind graph.Kind) *cypherModel.Updating
return cypherModel.NewUpdatingClause(&cypherModel.Remove{
Items: []*cypherModel.RemoveItem{{
KindMatcher: &cypherModel.KindMatcher{
Reference: reference,
Kinds: graph.Kinds{kind},
Reference: reference,
Kinds: graph.Kinds{kind},
IsExclusive: false,
},
}},
})
Expand All @@ -78,8 +77,9 @@ func DeleteKinds(reference graph.Criteria, kinds graph.Kinds) *cypherModel.Updat
return cypherModel.NewUpdatingClause(&cypherModel.Remove{
Items: []*cypherModel.RemoveItem{{
KindMatcher: &cypherModel.KindMatcher{
Reference: reference,
Kinds: kinds,
Reference: reference,
Kinds: kinds,
IsExclusive: false,
},
}},
})
Expand Down Expand Up @@ -131,13 +131,14 @@ func DeleteProperties(reference graph.Criteria, propertyNames ...string) *cypher

func Kind(reference graph.Criteria, kinds ...graph.Kind) *cypherModel.KindMatcher {
return &cypherModel.KindMatcher{
Reference: reference,
Kinds: kinds,
Reference: reference,
Kinds: kinds,
IsExclusive: false,
}
}

func KindIn(reference graph.Criteria, kinds ...graph.Kind) *cypherModel.KindMatcher {
return cypherModel.NewKindMatcher(reference, kinds)
return cypherModel.NewKindMatcher(reference, kinds, false)
}

func NodeProperty(name string) *cypherModel.PropertyLookup {
Expand Down