diff --git a/bql/grammar/grammar.go b/bql/grammar/grammar.go index 18cc1dcb..b73d25c0 100644 --- a/bql/grammar/grammar.go +++ b/bql/grammar/grammar.go @@ -918,7 +918,7 @@ func SemanticBQL() *Grammar { } setElementHook(semanticBQL, varSymbols, semantic.VarAccumulatorHook(), nil) - // Collect and valiadate group by bindinds. + // Collect and validate group by bindings. grpSymbols := []semantic.Symbol{"GROUP_BY", "GROUP_BY_BINDINGS"} setElementHook(semanticBQL, grpSymbols, semantic.GroupByBindings(), nil) setClauseHook(semanticBQL, []semantic.Symbol{"GROUP_BY"}, nil, semantic.GroupByBindingsChecker()) @@ -952,5 +952,12 @@ func SemanticBQL() *Grammar { }) setClauseHook(semanticBQL, []semantic.Symbol{"START"}, nil, semantic.GroupByBindingsChecker()) + // CONSTRUCT clause semantic hooks. + setClauseHook(semanticBQL, []semantic.Symbol{"CONSTRUCT"}, nil, semantic.TypeBindingClauseHook(semantic.Construct)) + setClauseHook(semanticBQL, []semantic.Symbol{"CONSTRUCT_FACTS"}, semantic.InitWorkingConstructClauseHook(), nil) + constructTriplesSymbols := []semantic.Symbol{"CONSTRUCT_TRIPLES", "MORE_CONSTRUCT_TRIPLES"} + setClauseHook(semanticBQL, constructTriplesSymbols, semantic.NextWorkingConstructClauseHook(), semantic.NextWorkingConstructClauseHook()) + + return semanticBQL } diff --git a/bql/semantic/hooks.go b/bql/semantic/hooks.go index 4781e317..254b5276 100644 --- a/bql/semantic/hooks.go +++ b/bql/semantic/hooks.go @@ -140,6 +140,16 @@ func CollectGlobalBounds() ElementHook { return collectGlobalBounds() } +// InitWorkingConstructClauseHook returns the singleton for clause accumulation within the construct statement. +func InitWorkingConstructClauseHook() ClauseHook { + return InitWorkingConstructClause() +} + +// NextWorkingConstructClauseHook returns the singleton for clause accumulation within the construct statement. +func NextWorkingConstructClauseHook() ClauseHook { + return NextWorkingConstructClause() +} + // TypeBindingClauseHook returns a ClauseHook that sets the binding type. func TypeBindingClauseHook(t StatementType) ClauseHook { var f ClauseHook @@ -567,7 +577,7 @@ func whereObjectClause() ElementHook { return f } -// whereObjectClause returns an element hook that updates the object +// varAccumulator returns an element hook that updates the object // modifiers on the working graph clause. func varAccumulator() ElementHook { var ( @@ -862,3 +872,26 @@ func collectGlobalBounds() ElementHook { } return f } + +// InitWorkingConstructClause returns a clause hook to initialize a new working +// construct clause. +func InitWorkingConstructClause() ClauseHook { + var f ClauseHook + f = func(s *Statement, _ Symbol) (ClauseHook, error) { + s.ResetWorkingConstructClause() + return f, nil + } + return f +} + + +// NextWorkingConstructClause returns a clause hook to close the current working +// construct clause and start a new working construct clause. +func NextWorkingConstructClause() ClauseHook { + var f ClauseHook + f = func(s *Statement, _ Symbol) (ClauseHook, error) { + s.AddWorkingConstructClause() + return f, nil + } + return f +} diff --git a/bql/semantic/hooks_test.go b/bql/semantic/hooks_test.go index 998f6835..304536cc 100644 --- a/bql/semantic/hooks_test.go +++ b/bql/semantic/hooks_test.go @@ -136,7 +136,7 @@ func TestTypeBindingClauseHook(t *testing.T) { st := &Statement{} f(st, Symbol("FOO")) if got, want := st.Type(), Insert; got != want { - t.Errorf("semantic.TypeBidingHook failed to set the right type; got %s, want %s", got, want) + t.Errorf("semantic.TypeBindingHook failed to set the right type; got %s, want %s", got, want) } } @@ -153,9 +153,14 @@ func TestWhereWorkingClauseHook(t *testing.T) { f := whereNextWorkingClause() st := &Statement{} st.ResetWorkingGraphClause() + wcs := st.WorkingClause() + wcs.SBinding = "?a" f(st, Symbol("FOO")) + wcs = st.WorkingClause() + wcs.SBinding = "?b" f(st, Symbol("FOO")) - if got, want := len(st.GraphPatternClauses()), 0; got != want { + + if got, want := len(st.GraphPatternClauses()), 2; got != want { t.Errorf("semantic.whereNextWorkingClause should have returned two clauses for statement %v; got %d, want %d", st, got, want) } } @@ -1169,7 +1174,7 @@ func TestBindingsGraphChecker(t *testing.T) { want bool }{ { - id: "missing biding", + id: "missing binding", s: &Statement{ pattern: []*GraphClause{ {}, @@ -1199,7 +1204,7 @@ func TestBindingsGraphChecker(t *testing.T) { want: false, }, { - id: "all bidings available", + id: "all bindings available", s: &Statement{ pattern: []*GraphClause{ {}, @@ -1913,3 +1918,27 @@ func TestCollectGlobalBounds(t *testing.T) { } } } + +func TestInitWorkingConstructClauseHook(t *testing.T) { + f := InitWorkingConstructClause() + st := &Statement{} + f(st, Symbol("FOO")) + if st.WorkingConstructClause() == nil { + t.Errorf("semantic.InitConstructWorkingClause should have returned a valid working clause for statement %v", st) + } +} + +func TestNextWorkingConstructClauseHook(t *testing.T) { + f := NextWorkingConstructClause() + st := &Statement{} + st.ResetWorkingConstructClause() + wcs := st.WorkingConstructClause() + wcs.SBinding = "?a" + f(st, Symbol("FOO")) + wcs = st.WorkingConstructClause() + wcs.SBinding = "?b" + f(st, Symbol("FOO")) + if got, want := len(st.ConstructClauses()), 2; got != want { + t.Errorf("semantic.NextConstructWorkingClause should have returned two clauses for statement %v; got %d, want %d", st, got, want) + } +} diff --git a/bql/semantic/semantic.go b/bql/semantic/semantic.go index 2782ad3b..c160e318 100644 --- a/bql/semantic/semantic.go +++ b/bql/semantic/semantic.go @@ -692,11 +692,20 @@ func (s *Statement) GlobalLookupOptions() *storage.LookupOptions { return &lo } +// ConstructClauses returns the list of construct clauses in the statement. +func (s *Statement) ConstructClauses() []*ConstructClause { + return s.constructClauses +} + // ResetWorkingConstructClause resets the current working construct clause. func (s *Statement) ResetWorkingConstructClause() { - s.workingConstructClause = &ConstructClause{} + s.workingConstructClause = &ConstructClause{} } +// WorkingConstructClause returns the current working clause. +func (s *Statement) WorkingConstructClause() *ConstructClause { + return s.workingConstructClause +} // AddWorkingConstructClause adds the current working construct clause to the set // of construct clauses that form the construct statement.