From e34c0a230816fa223a56161c2fe9d63097901870 Mon Sep 17 00:00:00 2001 From: Sean Johnson Date: Tue, 21 Jul 2026 11:24:31 -0500 Subject: [PATCH] fix: handle grave-escaped property names properly BED-8967 --- .../pgsql/test/translation_cases/nodes.sql | 21 ++++++++++++ cypher/models/pgsql/translate/expression.go | 33 ++++++++++++++++--- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/cypher/models/pgsql/test/translation_cases/nodes.sql b/cypher/models/pgsql/test/translation_cases/nodes.sql index 0f3ba8d2..4e14dfdb 100644 --- a/cypher/models/pgsql/test/translation_cases/nodes.sql +++ b/cypher/models/pgsql/test/translation_cases/nodes.sql @@ -47,6 +47,27 @@ with s0 as (select (n0.id, n0.kind_ids, n0.properties)::nodecomposite as n0 from -- case: match (n) where n.name = '1234' return n with s0 as (select (n0.id, n0.kind_ids, n0.properties)::nodecomposite as n0 from node n0 where ((jsonb_typeof((n0.properties -> 'name')) = 'string' and (n0.properties ->> 'name') = '1234'))) select s0.n0 as n from s0; +-- case: match (n) where n.`a-aaa` = "123" return n +with s0 as (select (n0.id, n0.kind_ids, n0.properties)::nodecomposite as n0 from node n0 where ((jsonb_typeof((n0.properties -> 'a-aaa')) = 'string' and (n0.properties ->> 'a-aaa') = '123'))) select s0.n0 as n from s0; + +-- case: match (n) where n.`b_bbb` = "123" return n +with s0 as (select (n0.id, n0.kind_ids, n0.properties)::nodecomposite as n0 from node n0 where ((jsonb_typeof((n0.properties -> 'b_bbb')) = 'string' and (n0.properties ->> 'b_bbb') = '123'))) select s0.n0 as n from s0; + +-- case: match (n) where n.`has``tick` = "123" return n +with s0 as (select (n0.id, n0.kind_ids, n0.properties)::nodecomposite as n0 from node n0 where ((jsonb_typeof((n0.properties -> 'has`tick')) = 'string' and (n0.properties ->> 'has`tick') = '123'))) select s0.n0 as n from s0; + +-- case: match (n) where n.```starts-tick` = "123" return n +with s0 as (select (n0.id, n0.kind_ids, n0.properties)::nodecomposite as n0 from node n0 where ((jsonb_typeof((n0.properties -> '`starts-tick')) = 'string' and (n0.properties ->> '`starts-tick') = '123'))) select s0.n0 as n from s0; + +-- case: match (n) where n.```super-wrapped``` = "123" return n +with s0 as (select (n0.id, n0.kind_ids, n0.properties)::nodecomposite as n0 from node n0 where ((jsonb_typeof((n0.properties -> '`super-wrapped`')) = 'string' and (n0.properties ->> '`super-wrapped`') = '123'))) select s0.n0 as n from s0; + +-- case: match (n) where n.```` = "123" return n +with s0 as (select (n0.id, n0.kind_ids, n0.properties)::nodecomposite as n0 from node n0 where ((jsonb_typeof((n0.properties -> '`')) = 'string' and (n0.properties ->> '`') = '123'))) select s0.n0 as n from s0; + +-- case: match (n) where (n).`a-aaa` = "123" return n +with s0 as (select (n0.id, n0.kind_ids, n0.properties)::nodecomposite as n0 from node n0) select s0.n0 as n from s0 where ((jsonb_typeof((((s0.n0)).properties -> 'a-aaa')) = 'string' and (((s0.n0)).properties ->> 'a-aaa') = '123')); + -- case: match (n:NodeKind1 {name: "SOME NAME"}) return n with s0 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[] and (jsonb_typeof((n0.properties -> 'name')) = 'string' and (n0.properties ->> 'name') = 'SOME NAME')) select s0.n0 as n from s0; diff --git a/cypher/models/pgsql/translate/expression.go b/cypher/models/pgsql/translate/expression.go index 6123f8a5..cd4dac9e 100644 --- a/cypher/models/pgsql/translate/expression.go +++ b/cypher/models/pgsql/translate/expression.go @@ -54,6 +54,20 @@ func (s *Translator) translateCompositePropertyLookup(target pgsql.Expression, l } } +func isEscapedPropertyLookupName(literal pgsql.Literal) bool { + if litString, typeOK := literal.Value.(string); !typeOK { + return false + } else if len(litString) > 2 && litString[0] == '`' && litString[len(litString)-1] == '`' { + return true + } + + return false +} + +func unescapePropertyLookupName(name string) string { + return strings.ReplaceAll(name[1:len(name)-1], "``", "`") +} + func (s *Translator) translatePropertyLookup(lookup *cypher.PropertyLookup) error { if translatedAtom, err := s.treeTranslator.PopOperand(); err != nil { return err @@ -64,7 +78,13 @@ func (s *Translator) translatePropertyLookup(lookup *cypher.PropertyLookup) erro return err } else { s.treeTranslator.PushOperand(pgsql.CompoundIdentifier{typedTranslatedAtom, pgsql.ColumnProperties}) - s.treeTranslator.PushOperand(fieldIdentifierLiteral) + if !isEscapedPropertyLookupName(fieldIdentifierLiteral) { + s.treeTranslator.PushOperand(fieldIdentifierLiteral) + } else if unescapedLiteral, err := pgsql.AsLiteral(unescapePropertyLookupName(fieldIdentifierLiteral.Value.(string))); err != nil { + return err + } else { + s.treeTranslator.PushOperand(unescapedLiteral) + } if err := s.treeTranslator.CompleteBinaryExpression(s.scope, pgsql.OperatorPropertyLookup); err != nil { return err @@ -83,7 +103,13 @@ func (s *Translator) translatePropertyLookup(lookup *cypher.PropertyLookup) erro Identifier: translatedAtom, Column: pgsql.ColumnProperties, }) - s.treeTranslator.PushOperand(fieldIdentifierLiteral) + if !isEscapedPropertyLookupName(fieldIdentifierLiteral) { + s.treeTranslator.PushOperand(fieldIdentifierLiteral) + } else if unescapedLiteral, err := pgsql.AsLiteral(unescapePropertyLookupName(fieldIdentifierLiteral.Value.(string))); err != nil { + return err + } else { + s.treeTranslator.PushOperand(unescapedLiteral) + } if err := s.treeTranslator.CompleteBinaryExpression(s.scope, pgsql.OperatorPropertyLookup); err != nil { return err @@ -300,7 +326,7 @@ func lookupRequiresElementType(typeHint pgsql.DataType, operator pgsql.Operator, func TypeCastExpression(expression pgsql.Expression, dataType pgsql.DataType) (pgsql.Expression, error) { if propertyLookup, isPropertyLookup := expressionToPropertyLookupBinaryExpression(expression); isPropertyLookup { - var lookupTypeHint = dataType + lookupTypeHint := dataType if lookupRequiresElementType(dataType, propertyLookup.Operator, propertyLookup.ROperand) { // Take the base type of the array type hint: in @@ -750,7 +776,6 @@ func rewriteIdentityOperands(scope *Scope, newExpression *pgsql.BinaryExpression } } } - } }