Skip to content

Commit

Permalink
Merge pull request #1252 from nyaruka/contactql_prefixes
Browse files Browse the repository at this point in the history
Contact queries with explicit property types part 1
  • Loading branch information
rowanseymour authored May 15, 2024
2 parents 5646549 + f14cd0e commit 4e0a37b
Show file tree
Hide file tree
Showing 20 changed files with 324 additions and 249 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 | '_' | ':')+;
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
2 changes: 1 addition & 1 deletion contactql/es/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func conditionToElastic(env envs.Environment, resolver contactql.Resolver, mappe
return fieldConditionToElastic(env, resolver, c)
case contactql.PropertyTypeAttribute:
return attributeConditionToElastic(env, resolver, mapper, c)
case contactql.PropertyTypeScheme:
case contactql.PropertyTypeURN:
return schemeConditionToElastic(env, c)
default:
panic(fmt.Sprintf("unsupported property type: %s", c.PropertyType()))
Expand Down
2 changes: 1 addition & 1 deletion contactql/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func Inspect(query *ContactQuery) *Inspection {
addRef(assets.NewVariableGroupReference(c.value))
}
}
case PropertyTypeScheme:
case PropertyTypeURN:
schemes[c.propKey] = true
case PropertyTypeField:
if query.resolver != nil {
Expand Down
68 changes: 36 additions & 32 deletions contactql/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ type PropertyType string

const (
// PropertyTypeAttribute is builtin property
PropertyTypeAttribute PropertyType = "attribute"
PropertyTypeAttribute PropertyType = "attr"

// PropertyTypeScheme is a URN scheme
PropertyTypeScheme PropertyType = "scheme"
// PropertyTypeURN is a URN scheme
PropertyTypeURN PropertyType = "urn"

// PropertyTypeField is a custom contact field
PropertyTypeField PropertyType = "field"
Expand All @@ -72,27 +72,27 @@ type QueryNode interface {

// Condition represents a comparison between a keywed value on the contact and a provided value
type Condition struct {
propKey string
propType PropertyType
propKey string
operator Operator
value string
}

func NewCondition(propKey string, propType PropertyType, operator Operator, value string) *Condition {
func NewCondition(propType PropertyType, propKey string, operator Operator, value string) *Condition {
return &Condition{
propKey: propKey,
propType: propType,
propKey: propKey,
operator: operator,
value: value,
}
}

// PropertyKey returns the key for the property being queried
func (c *Condition) PropertyKey() string { return c.propKey }

// PropertyType returns the type (attribute, scheme, field)
func (c *Condition) PropertyType() PropertyType { return c.propType }

// PropertyKey returns the key for the property being queried
func (c *Condition) PropertyKey() string { return c.propKey }

// Operator returns the type of comparison being made
func (c *Condition) Operator() Operator { return c.operator }

Expand Down Expand Up @@ -123,7 +123,7 @@ func (c *Condition) resolveValueType(resolver Resolver) assets.FieldType {
switch c.propType {
case PropertyTypeAttribute:
return attributes[c.propKey]
case PropertyTypeScheme:
case PropertyTypeURN:
return assets.FieldTypeText
case PropertyTypeField:
field := resolver.ResolveField(c.propKey)
Expand Down Expand Up @@ -152,7 +152,7 @@ func (c *Condition) validate(env envs.Environment, resolver Resolver) error {
if len(tokenizeNameValue(c.value)) == 0 {
return NewQueryError(ErrInvalidPartialName, "contains operator on name requires token of minimum length %d", minNameTokenContainsLength).withExtra("min_token_length", strconv.Itoa(minNameTokenContainsLength))
}
} else if c.propKey == AttributeURN || c.propType == PropertyTypeScheme {
} else if c.propKey == AttributeURN || c.propType == PropertyTypeURN {
if len(c.value) < minURNContainsLength {
return NewQueryError(ErrInvalidPartialURN, "contains operator on URN requires value of minimum length %d", minURNContainsLength).withExtra("min_value_length", strconv.Itoa(minURNContainsLength))
}
Expand All @@ -174,7 +174,7 @@ func (c *Condition) validate(env envs.Environment, resolver Resolver) error {
return NewQueryError(ErrUnsupportedSetCheck, "can't check whether '%s' is set or not set", c.propKey).withExtra("property", c.propKey).withExtra("operator", string(c.operator))
}
} else {
// check values are valid for the property type
// check values are valid for the value type
if valueType == assets.FieldTypeNumber {
_, err := c.ValueAsNumber()
if err != nil {
Expand All @@ -185,27 +185,31 @@ func (c *Condition) validate(env envs.Environment, resolver Resolver) error {
if err != nil {
return NewQueryError(ErrInvalidDate, "can't convert '%s' to a date", c.value).withExtra("value", c.value)
}
}

} else if c.propKey == AttributeGroup && resolver != nil {
group := c.ValueAsGroup(resolver)
if group == nil {
return NewQueryError(ErrInvalidGroup, "'%s' is not a valid group name", c.value).withExtra("value", c.value)
}
} else if (c.propKey == AttributeFlow || c.propKey == AttributeHistory) && resolver != nil {
flow := c.ValueAsFlow(resolver)
if flow == nil {
return NewQueryError(ErrInvalidFlow, "'%s' is not a valid flow name", c.value).withExtra("value", c.value)
}
} else if c.propKey == AttributeStatus {
val := strings.ToLower(c.value)
if val != "active" && val != "blocked" && val != "stopped" && val != "archived" {
return NewQueryError(ErrInvalidStatus, "'%s' is not a valid contact status", c.value).withExtra("value", c.value)
}
} else if c.propKey == AttributeLanguage {
if c.value != "" {
_, err := i18n.ParseLanguage(c.value)
if err != nil {
return NewQueryError(ErrInvalidLanguage, "'%s' is not a valid language code", c.value).withExtra("value", c.value)
// for some text attributes, do some additional validation
if c.propType == PropertyTypeAttribute {
if c.propKey == AttributeGroup && resolver != nil {
group := c.ValueAsGroup(resolver)
if group == nil {
return NewQueryError(ErrInvalidGroup, "'%s' is not a valid group name", c.value).withExtra("value", c.value)
}
} else if (c.propKey == AttributeFlow || c.propKey == AttributeHistory) && resolver != nil {
flow := c.ValueAsFlow(resolver)
if flow == nil {
return NewQueryError(ErrInvalidFlow, "'%s' is not a valid flow name", c.value).withExtra("value", c.value)
}
} else if c.propKey == AttributeStatus {
val := strings.ToLower(c.value)
if val != "active" && val != "blocked" && val != "stopped" && val != "archived" {
return NewQueryError(ErrInvalidStatus, "'%s' is not a valid contact status", c.value).withExtra("value", c.value)
}
} else if c.propKey == AttributeLanguage {
if c.value != "" {
_, err := i18n.ParseLanguage(c.value)
if err != nil {
return NewQueryError(ErrInvalidLanguage, "'%s' is not a valid language code", c.value).withExtra("value", c.value)
}
}
}
}
Expand Down
Loading

0 comments on commit 4e0a37b

Please sign in to comment.