Skip to content

Commit

Permalink
Make matching of property types stricter and add new error type
Browse files Browse the repository at this point in the history
  • Loading branch information
rowanseymour committed May 14, 2024
1 parent e40930e commit f14cd0e
Show file tree
Hide file tree
Showing 10 changed files with 213 additions and 176 deletions.
26 changes: 14 additions & 12 deletions antlr/ContactQL.g4
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import LexUnicode;
// Lexer rules
fragment HAS: [Hh][Aa][Ss];
fragment IS: [Ii][Ss];
fragment PROPTYPE: (UnicodeLetter)+;
fragment PROPKEY: (UnicodeLetter | UnicodeDigit | '_')+;

LPAREN: '(';
RPAREN: ')';
Expand All @@ -22,7 +24,7 @@ COMPARATOR: (
| IS
);
STRING: '"' (~["] | '\\"')* '"';
NAME: (UnicodeLetter | UnicodeDigit | '_' | '.')+; // e.g. fields.num_goats or urns.tel or name
PROPERTY: (PROPTYPE '.')? PROPKEY;
TEXT: (
UnicodeLetter
| UnicodeDigit
Expand All @@ -44,14 +46,14 @@ ERROR: .;
parse: expression EOF;
expression:
expression AND expression # combinationAnd
| expression expression # combinationImpicitAnd
| expression OR expression # combinationOr
| LPAREN expression RPAREN # expressionGrouping
| NAME COMPARATOR literal # condition
| literal # implicitCondition;
literal:
NAME # textLiteral
| TEXT # textLiteral
| STRING # stringLiteral;
expression AND expression # combinationAnd
| expression expression # combinationImpicitAnd
| expression OR expression # combinationOr
| LPAREN expression RPAREN # expressionGrouping
| PROPERTY COMPARATOR literal # condition
| literal # implicitCondition;
literal:
PROPERTY # textLiteral // it's not really a property, just indistinguishable by lexer
| TEXT # textLiteral
| STRING # stringLiteral;
2 changes: 1 addition & 1 deletion antlr/gen/contactql/ContactQL.interp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ AND
OR
COMPARATOR
STRING
NAME
PROPERTY
TEXT
WS
ERROR
Expand Down
2 changes: 1 addition & 1 deletion antlr/gen/contactql/ContactQL.tokens
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ AND=3
OR=4
COMPARATOR=5
STRING=6
NAME=7
PROPERTY=7
TEXT=8
WS=9
ERROR=10
Expand Down
8 changes: 5 additions & 3 deletions antlr/gen/contactql/ContactQLLexer.interp

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion antlr/gen/contactql/ContactQLLexer.tokens
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ AND=3
OR=4
COMPARATOR=5
STRING=6
NAME=7
PROPERTY=7
TEXT=8
WS=9
ERROR=10
Expand Down
284 changes: 146 additions & 138 deletions antlr/gen/contactql/contactql_lexer.go

Large diffs are not rendered by default.

18 changes: 9 additions & 9 deletions antlr/gen/contactql/contactql_parser.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions contactql/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const (
ErrUnsupportedContains = "unsupported_contains" // `property` the property key
ErrUnsupportedComparison = "unsupported_comparison" // `property` the property key, `operator` one of =>, <, >=, <=
ErrUnsupportedSetCheck = "unsupported_setcheck" // `property` the property key, `operator` one of =, !=
ErrUnknownPropertyType = "unknown_property_type" // `type` the property type
ErrUnknownProperty = "unknown_property" // `property` the property key
ErrRedactedURNs = "redacted_urns"
)
Expand Down
24 changes: 21 additions & 3 deletions contactql/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,28 +282,40 @@ func TestParsingErrors(t *testing.T) {
}{
{
query: `$`,
errMsg: "mismatched input '$' expecting {'(', STRING, NAME, TEXT}",
errMsg: "mismatched input '$' expecting {'(', STRING, PROPERTY, TEXT}",
errCode: "unexpected_token",
errExtra: map[string]string{"token": "$"},
},
{
query: `name = `,
errMsg: "mismatched input '<EOF>' expecting {STRING, NAME, TEXT}",
errMsg: "mismatched input '<EOF>' expecting {STRING, PROPERTY, TEXT}",
errCode: "unexpected_token",
errExtra: map[string]string{"token": "<EOF>"},
},
{
query: `name = "x`,
errMsg: "extraneous input '\"' expecting {STRING, NAME, TEXT}",
errMsg: "extraneous input '\"' expecting {STRING, PROPERTY, TEXT}",
errCode: "",
errExtra: nil,
},
{
query: `nam/e = "x"`,
errMsg: "mismatched input '=' expecting <EOF>", // because .name isn't valid NAME
errCode: "unexpected_token",
errExtra: map[string]string{"token": "="},
},
{
query: `name. = "x"`,
errMsg: "mismatched input '=' expecting <EOF>",
errCode: "unexpected_token",
errExtra: map[string]string{"token": "="},
},
{
query: `.name != "x"`,
errMsg: "mismatched input '!=' expecting <EOF>",
errCode: "unexpected_token",
errExtra: map[string]string{"token": "!="},
},
{
query: `age = XZ`,
errMsg: "can't convert 'XZ' to a number",
Expand Down Expand Up @@ -376,6 +388,12 @@ func TestParsingErrors(t *testing.T) {
errCode: "unknown_property",
errExtra: map[string]string{"property": "beers"},
},
{
query: `xxx.age = 12`,
errMsg: "unknown property type 'xxx'",
errCode: "unknown_property_type",
errExtra: map[string]string{"type": "xxx"},
},
}

env := envs.NewBuilder().WithDefaultCountry("US").Build()
Expand Down
22 changes: 14 additions & 8 deletions contactql/visitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,9 @@ func (v *visitor) VisitImplicitCondition(ctx *gen.ImplicitConditionContext) any
return NewCondition(PropertyTypeAttribute, AttributeName, operator, value)
}

// expression : NAME COMPARATOR literal
// expression : PROPERTY COMPARATOR literal
func (v *visitor) VisitCondition(ctx *gen.ConditionContext) any {
propText := strings.ToLower(ctx.NAME().GetText())
propText := strings.ToLower(ctx.PROPERTY().GetText())
operatorText := strings.ToLower(ctx.COMPARATOR().GetText())
value := v.Visit(ctx.Literal()).(string)

Expand All @@ -129,12 +129,18 @@ func (v *visitor) VisitCondition(ctx *gen.ConditionContext) any {
var propKey string

// check if property type is specified as prefix
if strings.HasPrefix(propText, "fields.") {
propKey = strings.TrimPrefix(propText, "fields.")
propType = PropertyTypeField
} else if strings.HasPrefix(propText, "urns.") {
propKey = strings.TrimPrefix(propText, "urns.")
propType = PropertyTypeURN
if strings.Contains(propText, ".") {
parts := strings.SplitN(propText, ".", 2)

if parts[0] == "fields" {
propType = PropertyTypeField
propKey = parts[1]
} else if parts[0] == "urns" {
propType = PropertyTypeURN
propKey = parts[1]
} else {
v.addError(NewQueryError(ErrUnknownPropertyType, "unknown property type '%s'", parts[0]).withExtra("type", parts[0]))
}
} else {
propKey = propText

Expand Down

0 comments on commit f14cd0e

Please sign in to comment.