Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FILTER keyword for latest anchor queries #146

Closed
wants to merge 34 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
7310bd4
Add three new triples to the ?test graph of "planner_test.go" (to bet…
rogerlucena Sep 19, 2020
69adc4c
Add tests for FILTER with latest anchor (in "planner_test.go")
rogerlucena Aug 25, 2020
bbd9a9e
Add tests for FILTER with latest anchor (in "grammar_test.go")
rogerlucena Sep 2, 2020
fec2c13
Add tests for FILTER with latest anchor (in "lexer_test.go")
rogerlucena Sep 3, 2020
b935f40
Add token type "ItemFilter" to the lexer
rogerlucena Sep 2, 2020
613fa9b
Add token type "ItemFilterFunction" to the lexer
rogerlucena Sep 2, 2020
614b73b
Add support for FILTER clauses in the grammar
rogerlucena Sep 2, 2020
6b83a4d
Add support for FILTER clauses in the lexer
rogerlucena Sep 2, 2020
1c226b5
Add "filters" to the "Statement" type
rogerlucena Sep 8, 2020
66cf463
Add "filters" to the "queryPlan" type
rogerlucena Sep 8, 2020
d94ceb9
Add "workingFilter" to the "Statement" type
rogerlucena Sep 9, 2020
605c18f
Create FILTER clause hook
rogerlucena Sep 9, 2020
fb0521e
Add String method to FilterClause
rogerlucena Sep 9, 2020
c6160ec
Update String method of queryPlan to show filters as well
rogerlucena Sep 9, 2020
17f9234
Add FilterOptions to LookupOptions in "storage.go"
rogerlucena Sep 9, 2020
cf74253
Add support for FILTER clauses in the planner
rogerlucena Sep 10, 2020
19403c1
Make the volatile driver consume lo.FilterOptions (focusing on latest…
rogerlucena Sep 10, 2020
fac42d2
Update String method of LookupOptions to show FilterOptions as well
rogerlucena Sep 11, 2020
707abdc
Add tests for FILTER clauses in the semantic.Statement level (in "sem…
rogerlucena Sep 14, 2020
74b082b
Add tests for WhereFilterClauseHook (in "hooks_test.go")
rogerlucena Sep 15, 2020
dd1a749
Minor renaming inside "memory_test.go" to avoid confusion
rogerlucena Sep 15, 2020
ed97713
Fix problem with partial predicate UUIDs in the volatile driver and m…
rogerlucena Sep 18, 2020
f1d7525
Add tests for the FILTER execution in the volatile driver (in "memory…
rogerlucena Sep 16, 2020
3529758
Allow user to specify for which bindings in the clause each filter ca…
rogerlucena Sep 24, 2020
54d5ead
Use a closure to make planner more performant (especially with the ad…
rogerlucena Sep 25, 2020
aef0002
Use an "enum" instead of a "string" for filter operations (less error…
rogerlucena Oct 1, 2020
338dff6
Update "memory.go" and "memory_test.go" since filter operations are "…
rogerlucena Oct 1, 2020
8037781
Update "hooks_test.go" and "semantic_test.go" since filter operations…
rogerlucena Oct 1, 2020
0ad7f00
Move "storage.FilteringOptions" to "filter.go" as "filter.StorageOpti…
rogerlucena Oct 1, 2020
9eb2f86
Add a String method for filter.StorageOptions
rogerlucena Oct 1, 2020
a241406
Use an "enum" instead of a "string" for filter fields (less error-pro…
rogerlucena Oct 2, 2020
3b7b182
Update "memory.go" and "memory_test.go" since filter fields are "enum…
rogerlucena Oct 2, 2020
daad7ab
Add an "isEmpty" method for "filter.Operation"
rogerlucena Oct 5, 2020
3c5c9ff
Update comments for "semantic.FilterClause" and "filter.StorageOptions"
rogerlucena Oct 5, 2020
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
36 changes: 36 additions & 0 deletions bql/grammar/grammar.go
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ func moreClauses() []*Clause {
Elements: []Element{
NewTokenType(lexer.ItemDot),
NewSymbol("CLAUSES"),
NewSymbol("FILTER_CLAUSES"),
},
},
{},
Expand Down Expand Up @@ -365,6 +366,33 @@ func clauses() []*Clause {
}
}

func moreFilterClauses() []*Clause {
return []*Clause{
{
Elements: []Element{
NewTokenType(lexer.ItemDot),
NewSymbol("FILTER_CLAUSES"),
},
},
{},
}
}
func filterClauses() []*Clause {
return []*Clause{
{
Elements: []Element{
NewTokenType(lexer.ItemFilter),
NewTokenType(lexer.ItemFilterFunction),
NewTokenType(lexer.ItemLPar),
NewTokenType(lexer.ItemBinding),
NewTokenType(lexer.ItemRPar),
NewSymbol("MORE_FILTER_CLAUSES"),
},
},
{},
}
}

func optionalClauses() []*Clause {
return []*Clause{
{
Expand Down Expand Up @@ -1266,6 +1294,8 @@ func BQL() *Grammar {
"MORE_CLAUSES": moreClauses(),
"CLAUSES": clauses(),
"OPTIONAL_CLAUSE": optionalClauses(),
"FILTER_CLAUSES": filterClauses(),
"MORE_FILTER_CLAUSES": moreFilterClauses(),
"SUBJECT_EXTRACT": subjectExtractClauses(),
"SUBJECT_TYPE": subjectTypeClauses(),
"SUBJECT_ID": subjectIDClauses(),
Expand Down Expand Up @@ -1400,6 +1430,12 @@ func SemanticBQL() *Grammar {
}
setElementHook(semanticBQL, objSymbols, semantic.WhereObjectClauseHook(), nil)

// Filter clause hook.
filterSymbols := []semantic.Symbol{
"FILTER_CLAUSES",
}
setElementHook(semanticBQL, filterSymbols, semantic.WhereFilterClauseHook(), nil)

// Collect binding variables variables.
varSymbols := []semantic.Symbol{
"VARS", "VARS_AS", "MORE_VARS", "COUNT_DISTINCT",
Expand Down
94 changes: 94 additions & 0 deletions bql/grammar/grammar_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,20 @@ func TestAcceptByParse(t *testing.T) {
?n "_object"@[] ?o};`,
// Show the graphs.
`show graphs;`,
// Test FILTER clause inside WHERE.
`select ?a
from ?b
where {
?s ?p ?o .
FILTER latest(?p)
};`,
`select ?a
from ?b
where {
?s ?p ?o .
FILTER latest(?p) .
FILTER latest(?o)
};`,
// Test optional trailing dot after the last clause inside WHERE.
`select ?a
from ?b
Expand Down Expand Up @@ -229,6 +243,19 @@ func TestAcceptByParse(t *testing.T) {
?s ?p ?o .
/u<paul> ?p ?o
};`,
`select ?a
from ?b
where {
?s ?p ?o .
FILTER latest(?p) .
};`,
`select ?a
from ?b
where {
?s ?p ?o .
FILTER latest(?p) .
FILTER latest(?o) .
};`,
}
p, err := NewParser(BQL())
if err != nil {
Expand Down Expand Up @@ -386,6 +413,46 @@ func TestRejectByParse(t *testing.T) {
where {?n "_subject"@[] ?s.
?n "_predicate"@[] ?p.
?n "_object"@[] ?o};`,
// Test invalid FILTER clause inside WHERE.
`select ?a
from ?b
where {
?s ?p ?o .
FILTER latest ?p
};`,
`select ?a
from ?b
where {
?s ?p ?o .
FILTER latest (?p)
};`,
`select ?a
from ?b
where {
FILTER latest(?p) .
?s ?p ?o
};`,
`select ?a
from ?b
where {
?s ?p ?o .
FILTER latest(?p) .
/u<paul> ?p ?o
};`,
`select ?a
from ?b
where {
?s ?p ?o .
?s ?p ?o .
FILTER latest(?p) .
/u<paul> ?p ?o
};`,
`select ?a
from ?b
where {
?s ?p ?o .
FILTER late^st(?p)
};`,
// Test invalid trailing dot use inside WHERE.
`select ?a
from ?b
Expand All @@ -411,6 +478,19 @@ func TestRejectByParse(t *testing.T) {
?s ?p ?o
/u<paul> ?p ?o
};`,
`select ?a
from ?b
where {
?s ?p ?o
FILTER latest(?p)
};`,
`select ?a
from ?b
where {
?s ?p ?o .
FILTER latest(?p)
FILTER latest(?o)
};`,
}
p, err := NewParser(BQL())
if err != nil {
Expand Down Expand Up @@ -565,6 +645,13 @@ func TestAcceptQueryBySemanticParse(t *testing.T) {
`select ?s from ?g where{/_<foo> as ?s ?p "id"@[?foo, ?bar] as ?o} order by ?s;`,
`select ?s as ?a, ?o as ?b, ?o as ?c from ?g where{?s ?p ?o} order by ?a ASC, ?b DESC;`,
`select ?s as ?a, ?o as ?b, ?o as ?c from ?g where{?s ?p ?o} order by ?a ASC, ?b DESC, ?a ASC, ?b DESC, ?c;`,
// Test valid FILTER clause for grammar with hooks.
`select ?p
from ?b
where {
?s ?p ?o .
FILTER latest(?p)
};`,
}
p, err := NewParser(SemanticBQL())
if err != nil {
Expand Down Expand Up @@ -596,6 +683,13 @@ func TestRejectByParseAndSemantic(t *testing.T) {
`select ?s as ?a, ?o as ?b, ?o as ?c from ?g where{?s ?p ?o} order by ?a ASC, ?a DESC;`,
// Wrong limit literal.
`select ?s as ?a, ?o as ?b, ?o as ?c from ?g where{?s ?p ?o} LIMIT "true"^^type:bool;`,
// Reject not supported FILTER function.
`select ?p, ?o
from ?test
where {
/u<peter> ?p ?o .
FILTER notSupportedFilterFunction(?p)
};`,
}
p, err := NewParser(SemanticBQL())
if err != nil {
Expand Down
35 changes: 35 additions & 0 deletions bql/lexer/lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ const (
ItemGraphs
// ItemOptional identifies optional graph pattern clauses.
ItemOptional
// ItemFilter represents the filter keyword in BQL.
ItemFilter
// ItemFilterFunction represents a filter function in BQL.
ItemFilterFunction
)

func (tt TokenType) String() string {
Expand Down Expand Up @@ -253,6 +257,10 @@ func (tt TokenType) String() string {
return "GRAPHS"
case ItemOptional:
return "OPTIONAL"
case ItemFilter:
return "FILTER"
case ItemFilterFunction:
return "FILTER_FUNCTION"
default:
return "UNKNOWN"
}
Expand Down Expand Up @@ -294,6 +302,7 @@ const (
from = "from"
where = "where"
optional = "optional"
filter = "filter"
as = "as"
before = "before"
after = "after"
Expand Down Expand Up @@ -402,6 +411,9 @@ func lexToken(l *lexer) stateFn {
return lexPredicateOrLiteral
}
if unicode.IsLetter(r) {
if l.lastTokenType == ItemFilter {
return lexFilterFunction
}
return lexKeyword
}
}
Expand Down Expand Up @@ -617,6 +629,10 @@ func lexKeyword(l *lexer) stateFn {
consumeKeyword(l, ItemOptional)
return lexSpace
}
if strings.EqualFold(input, filter) {
consumeKeyword(l, ItemFilter)
return lexSpace
}
if strings.EqualFold(input, typeKeyword) {
consumeKeyword(l, ItemType)
return lexSpace
Expand Down Expand Up @@ -648,6 +664,25 @@ func lexKeyword(l *lexer) stateFn {
return nil
}

// lexFilterFunction lexes a filter function out of the input (used in FILTER clauses).
func lexFilterFunction(l *lexer) stateFn {
l.next()
var nr rune
for {
nr = l.next()
if nr == leftPar {
l.backup()
break
}
if !unicode.IsLetter(nr) {
l.emitError(`invalid rune in filter function: "` + string(nr) + `"; filter functions should be formed only by letters`)
return nil
}
}
l.emit(ItemFilterFunction)
return lexSpace
}

func lexNode(l *lexer) stateFn {
ltID := false
for done := false; !done; {
Expand Down
55 changes: 55 additions & 0 deletions bql/lexer/lexer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ func TestTokenTypeString(t *testing.T) {
{ItemShow, "SHOW"},
{ItemGraphs, "GRAPHS"},
{ItemOptional, "OPTIONAL"},
{ItemFilter, "FILTER"},
{ItemFilterFunction, "FILTER_FUNCTION"},
{TokenType(-1), "UNKNOWN"},
}

Expand Down Expand Up @@ -388,6 +390,29 @@ func TestIndividualTokens(t *testing.T) {
{Type: ItemEOF},
},
},
{
`FILTER latest(?p)`,
[]Token{
{Type: ItemFilter, Text: "FILTER"},
{Type: ItemFilterFunction, Text: "latest"},
{Type: ItemLPar, Text: "("},
{Type: ItemBinding, Text: "?p"},
{Type: ItemRPar, Text: ")"},
{Type: ItemEOF},
},
},
{
`FILTER latest(?p) .`,
[]Token{
{Type: ItemFilter, Text: "FILTER"},
{Type: ItemFilterFunction, Text: "latest"},
{Type: ItemLPar, Text: "("},
{Type: ItemBinding, Text: "?p"},
{Type: ItemRPar, Text: ")"},
{Type: ItemDot, Text: "."},
{Type: ItemEOF},
},
},
}

for _, test := range table {
Expand Down Expand Up @@ -578,6 +603,36 @@ func TestValidTokenQuery(t *testing.T) {
ItemHaving, ItemBinding, ItemEQ, ItemTime, ItemSemicolon, ItemEOF,
},
},
{
`select ?s ?p ?o
from ?foo
where {
?s ?p ?o .
FILTER latest(?p)
};`,
[]TokenType{
ItemQuery, ItemBinding, ItemBinding, ItemBinding, ItemFrom, ItemBinding,
ItemWhere, ItemLBracket, ItemBinding, ItemBinding, ItemBinding, ItemDot,
ItemFilter, ItemFilterFunction, ItemLPar, ItemBinding, ItemRPar,
ItemRBracket, ItemSemicolon, ItemEOF,
},
},
{
`select ?s ?p ?o
from ?foo
where {
?s ?p ?o .
FILTER latest(?p) .
FILTER latest(?o)
};`,
[]TokenType{
ItemQuery, ItemBinding, ItemBinding, ItemBinding, ItemFrom, ItemBinding,
ItemWhere, ItemLBracket, ItemBinding, ItemBinding, ItemBinding, ItemDot,
ItemFilter, ItemFilterFunction, ItemLPar, ItemBinding, ItemRPar, ItemDot,
ItemFilter, ItemFilterFunction, ItemLPar, ItemBinding, ItemRPar,
ItemRBracket, ItemSemicolon, ItemEOF,
},
},
}

for _, test := range table {
Expand Down
7 changes: 4 additions & 3 deletions bql/planner/data_access.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,10 @@ var _ error = (*skippableError)(nil)
// provided graph clause.
func updateTimeBounds(lo *storage.LookupOptions, cls *semantic.GraphClause) *storage.LookupOptions {
nlo := &storage.LookupOptions{
MaxElements: lo.MaxElements,
LowerAnchor: lo.LowerAnchor,
UpperAnchor: lo.UpperAnchor,
MaxElements: lo.MaxElements,
LowerAnchor: lo.LowerAnchor,
UpperAnchor: lo.UpperAnchor,
FilterOptions: lo.FilterOptions,
}
if cls.PLowerBound != nil {
if lo.LowerAnchor == nil || (lo.LowerAnchor != nil && cls.PLowerBound.After(*lo.LowerAnchor)) {
Expand Down
Loading