diff --git a/bql/grammar/grammar.go b/bql/grammar/grammar.go index 34d53ed1..3d6a3a69 100644 --- a/bql/grammar/grammar.go +++ b/bql/grammar/grammar.go @@ -797,7 +797,7 @@ func BQL() *Grammar { NewTokenType(lexer.ItemNode), NewSymbol("CONSTRUCT_PREDICATE"), NewSymbol("CONSTRUCT_OBJECT"), - NewSymbol("REIFICATION_CLAUSE"), + NewSymbol("MORE_CONSTRUCT_PREDICATE_OBJECT_PAIRS"), NewSymbol("MORE_CONSTRUCT_TRIPLES"), }, }, @@ -806,7 +806,7 @@ func BQL() *Grammar { NewTokenType(lexer.ItemBlankNode), NewSymbol("CONSTRUCT_PREDICATE"), NewSymbol("CONSTRUCT_OBJECT"), - NewSymbol("REIFICATION_CLAUSE"), + NewSymbol("MORE_CONSTRUCT_PREDICATE_OBJECT_PAIRS"), NewSymbol("MORE_CONSTRUCT_TRIPLES"), }, }, @@ -815,7 +815,7 @@ func BQL() *Grammar { NewTokenType(lexer.ItemBinding), NewSymbol("CONSTRUCT_PREDICATE"), NewSymbol("CONSTRUCT_OBJECT"), - NewSymbol("REIFICATION_CLAUSE"), + NewSymbol("MORE_CONSTRUCT_PREDICATE_OBJECT_PAIRS"), NewSymbol("MORE_CONSTRUCT_TRIPLES"), }, }, @@ -859,56 +859,17 @@ func BQL() *Grammar { }, }, }, - "REIFICATION_CLAUSE": []*Clause{ + "MORE_CONSTRUCT_PREDICATE_OBJECT_PAIRS": []*Clause{ { Elements: []Element{ NewTokenType(lexer.ItemSemicolon), - NewSymbol("REIFICATION_PREDICATE"), - NewSymbol("REIFICATION_OBJECT"), - NewSymbol("REIFICATION_CLAUSE"), + NewSymbol("CONSTRUCT_PREDICATE"), + NewSymbol("CONSTRUCT_OBJECT"), + NewSymbol("MORE_CONSTRUCT_PREDICATE_OBJECT_PAIRS"), }, }, {}, }, - "REIFICATION_PREDICATE": []*Clause{ - { - Elements: []Element{ - NewTokenType(lexer.ItemPredicate), - }, - }, - { - Elements: []Element{ - NewTokenType(lexer.ItemBinding), - }, - }, - }, - "REIFICATION_OBJECT": []*Clause{ - { - Elements: []Element{ - NewTokenType(lexer.ItemNode), - }, - }, - { - Elements: []Element{ - NewTokenType(lexer.ItemBlankNode), - }, - }, - { - Elements: []Element{ - NewTokenType(lexer.ItemPredicate), - }, - }, - { - Elements: []Element{ - NewTokenType(lexer.ItemLiteral), - }, - }, - { - Elements: []Element{ - NewTokenType(lexer.ItemBinding), - }, - }, - }, "MORE_CONSTRUCT_TRIPLES": []*Clause{ { Elements: []Element{ @@ -1042,17 +1003,14 @@ func SemanticBQL() *Grammar { // CONSTRUCT clause semantic hooks. setClauseHook(semanticBQL, []semantic.Symbol{"CONSTRUCT_FACTS"}, semantic.InitWorkingConstructClauseHook(), semantic.TypeBindingClauseHook(semantic.Construct)) - constructTriplesSymbols := []semantic.Symbol{"CONSTRUCT_TRIPLES", "MORE_CONSTRUCT_TRIPLES"} setClauseHook(semanticBQL, constructTriplesSymbols, semantic.NextWorkingConstructClauseHook(), semantic.NextWorkingConstructClauseHook()) + setClauseHook(semanticBQL, []semantic.Symbol{"CONSTRUCT_PREDICATE"}, semantic.NextWorkingConstructPredicateObjectPairClauseHook(), nil) + setClauseHook(semanticBQL, []semantic.Symbol{"CONSTRUCT_OBJECT"}, nil, semantic.NextWorkingConstructPredicateObjectPairClauseHook()) - setElementHook(semanticBQL, []semantic.Symbol{"CONSTRUCT_TRIPLES"}, semantic.ConstructSubjectClauseHook(), nil) - setElementHook(semanticBQL, []semantic.Symbol{"CONSTRUCT_PREDICATE"}, semantic.ConstructPredicateClauseHook(), nil) - setElementHook(semanticBQL, []semantic.Symbol{"CONSTRUCT_OBJECT"}, semantic.ConstructObjectClauseHook(), nil) - - setClauseHook(semanticBQL, []semantic.Symbol{"REIFICATION_CLAUSE"}, semantic.NextWorkingReificationClauseHook(), semantic.NextWorkingReificationClauseHook()) - setElementHook(semanticBQL, []semantic.Symbol{"REIFICATION_PREDICATE"}, semantic.ReificationPredicateClauseHook(), nil) - setElementHook(semanticBQL, []semantic.Symbol{"REIFICATION_OBJECT"}, semantic.ReificationObjectClauseHook(), nil) + setElementHook(semanticBQL, []semantic.Symbol{"CONSTRUCT_TRIPLES"}, semantic.ConstructSubjectHook(), nil) + setElementHook(semanticBQL, []semantic.Symbol{"CONSTRUCT_PREDICATE"}, semantic.ConstructPredicateHook(), nil) + setElementHook(semanticBQL, []semantic.Symbol{"CONSTRUCT_OBJECT"}, semantic.ConstructObjectHook(), nil) return semanticBQL } diff --git a/bql/grammar/grammar_test.go b/bql/grammar/grammar_test.go index b1a8d81a..e3688dba 100644 --- a/bql/grammar/grammar_test.go +++ b/bql/grammar/grammar_test.go @@ -117,24 +117,34 @@ func TestAcceptByParse(t *testing.T) { // Test Construct clause. `construct {?s "new_predicate"@[] ?o} into ?a from ?b where {?s "old_predicate"@[,] ?o} having ?s = ?o;`, `construct {?s "new_predicate"@[] ?o} into ?a from ?b where {?s "old_predicate"@[,] ?o};`, - `construct {?s ?p ?o} into ?a from ?b where {?n "_subject"@[] ?s. - ?n "_predicate"@[] ?p. - ?n "_object"@[] ?o};`, + `construct {?s ?p ?o} + into ?a + from ?b + where {?n "_subject"@[] ?s. + ?n "_predicate"@[] ?p. + ?n "_object"@[] ?o};`, `construct {?s ?p ?o. _:v "_subject"@[] ?s. _:v "_predicate"@[] ?p. - _:v "_object"@[] ?o} into ?a from ?b where {?n "_subject"@[] ?s. - ?n "_predicate"@[] ?p. - ?n "_object"@[] ?o};`, + _:v "_object"@[] ?o} + into ?a + from ?b + where {?n "_subject"@[] ?s. + ?n "_predicate"@[] ?p. + ?n "_object"@[] ?o};`, `construct {?s "predicate_1"@[] ?o1; - "predicate_2"@[] ?o2} into ?a from ?b where {?s "old_predicate_1"@[,] ?o1. - ?s "old_predicate_2"@[,] ?o2};`, - + "predicate_2"@[] ?o2} + into ?a + from ?b + where {?s "old_predicate_1"@[,] ?o1. + ?s "old_predicate_2"@[,] ?o2};`, `construct {?s "predicate_1"@[] ?o1; "predicate_2"@[] ?o2. - ?s "predicate_3"@[] ?o3} into ?a from ?b where {?s "old_predicate_1"@[,] ?o1. - ?s "old_predicate_2"@[,] ?o2. - ?s "old_predicate_3"@[,] ?o3};`, + ?s "predicate_3"@[] ?o3} + into ?a + from ?b where {?s "old_predicate_1"@[,] ?o1. + ?s "old_predicate_2"@[,] ?o2. + ?s "old_predicate_3"@[,] ?o3};`, } p, err := NewParser(BQL()) if err != nil { @@ -235,15 +245,23 @@ func TestRejectByParse(t *testing.T) { `construct {?s "foo"@[,] ?o} from ?b where{?s "foo"@[,] ?o} having ?s = ?o;`, // Construct clause with badly formed blank node. `construct {?s ?p ?o. - _v "some_pred"@[] ?k } into ?a from ?b where {?s "foo"@[,] ?o};`, + _v "some_pred"@[] ?k} + into ?a + from ?b + where {?s "foo"@[,] ?o};`, // Construct clause with badly formed triple. `construct {?s ?p ?o. - _:v "some_pred"@[]} into ?a from ?b where {?s "foo"@[,] ?o};`, - // Construct clause with badly formed reification clause. + _:v "some_pred"@[]} + into ?a + from ?b + where {?s "foo"@[,] ?o};`, + // Construct clause with badly formed predicate-object pair. `construct {?s "predicate_1"@[] ?o1; - ?s "predicate_2"@[] ?o2} into ?a from ?b where {?s "old_predicate_1"@[,] ?o1. - ?s "old_predicate_2"@[,] ?o2};`, - + ?s "predicate_2"@[] ?o2} + into ?a + from ?b + where {?s "old_predicate_1"@[,] ?o1. + ?s "old_predicate_2"@[,] ?o2};`, } p, err := NewParser(BQL()) if err != nil { @@ -294,16 +312,21 @@ func TestAcceptGraphOpsByParseAndSemantic(t *testing.T) { // Construct data. Graphs can be input or output graphs. {`construct {?s "predicate_1"@[] ?o1; - "predicate_2"@[] ?o2} into ?a from ?b where {?s "old_predicate_1"@[,] ?o1. - ?s "old_predicate_2"@[,] ?o2. - ?s "old_predicate_3"@[,] ?o3};`, - empty, []string{"?b"}, []string{"?a"}, 0}, + "predicate_2"@[] ?o2} + into ?a + from ?b + where {?s "old_predicate_1"@[,] ?o1. + ?s "old_predicate_2"@[,] ?o2. + ?s "old_predicate_3"@[,] ?o3};`, empty, []string{"?b"}, []string{"?a"}, 0}, + // construct data into multiple output graphs from multple input graphs. {`construct {?s "predicate_1"@[] ?o1; - "predicate_2"@[] ?o2} into ?a, ?b from ?c, ?d where {?s "old_predicate_1"@[,] ?o1. - ?s "old_predicate_2"@[,] ?o2. - ?s "old_predicate_3"@[,] ?o3};`, - empty, []string{"?c", "?d"}, []string{"?a", "?b"}, 0}, + "predicate_2"@[] ?o2} + into ?a, ?b + from ?c, ?d + where {?s "old_predicate_1"@[,] ?o1. + ?s "old_predicate_2"@[,] ?o2. + ?s "old_predicate_3"@[,] ?o3};`, empty, []string{"?c", "?d"}, []string{"?a", "?b"}, 0}, } p, err := NewParser(SemanticBQL()) if err != nil { @@ -445,18 +468,24 @@ func TestSemanticStatementConstructClausesLengthCorrectness(t *testing.T) { }{ { query: `construct {?s "predicate_1"@[] ?o1; - "predicate_2"@[] ?o2} into ?a from ?b where {?s "old_predicate_1"@[,] ?o1. - ?s "old_predicate_2"@[,] ?o2. - ?s "old_predicate_3"@[,] ?o3};`, - want: 1, + "predicate_2"@[] ?o2} + into ?a + from ?b + where {?s "old_predicate_1"@[,] ?o1. + ?s "old_predicate_2"@[,] ?o2. + ?s "old_predicate_3"@[,] ?o3};`, + want: 1, }, { query: `construct {?s "predicate_1"@[] ?o1; - "predicate_2"@[] ?o2. - ?s "predicate_3"@[] ?o3} into ?a from ?b where {?s "old_predicate_1"@[,] ?o1. - ?s "old_predicate_2"@[,] ?o2. - ?s "old_predicate_3"@[,] ?o3};`, - want: 2, + "predicate_2"@[] ?o2. + ?s "predicate_3"@[] ?o3} + into ?a + from ?b + where {?s "old_predicate_1"@[,] ?o1. + ?s "old_predicate_2"@[,] ?o2. + ?s "old_predicate_3"@[,] ?o3};`, + want: 2, }, } p, err := NewParser(SemanticBQL()) @@ -474,67 +503,81 @@ func TestSemanticStatementConstructClausesLengthCorrectness(t *testing.T) { } } -func TestSemanticStatementReificationClausesLengthCorrectness(t *testing.T) { +func TestSemanticStatementPredicateObjectPairsLengthCorrectness(t *testing.T) { table := []struct { - query string - want_one int - want_two int + query string + want_one int + want_two int }{ { query: `construct {?s "predicate_1"@[] ?o1; - "predicate_2"@[] ?o2. - ?s "predicate_3"@[] ?o3} into ?a from ?b where {?s "old_predicate_1"@[,] ?o1. - ?s "old_predicate_2"@[,] ?o2. - ?s "old_predicate_3"@[,] ?o3};`, - want_one: 1, - want_two: 0, + "predicate_2"@[] ?o2. + ?s "predicate_3"@[] ?o3} + into ?a + from ?b + where {?s "old_predicate_1"@[,] ?o1. + ?s "old_predicate_2"@[,] ?o2. + ?s "old_predicate_3"@[,] ?o3};`, + want_one: 2, + want_two: 1, }, { query: `construct {?s "predicate_1"@[] ?o1; - "predicate_2"@[] ?o2; - "predicate_3"@[] ?o3. - ?s1 "predicate_1"@[] ?o1; - "predicate_2"@[] ?o2; - "predicate_3"@[] ?o3} into ?a from ?b where {?s "old_predicate_1"@[,] ?o1. - ?s "old_predicate_2"@[,] ?o2. - ?s1 "old_predicate_3"@[,] ?o3};`, - want_one: 2, - want_two: 2, + "predicate_2"@[] ?o2; + "predicate_3"@[] ?o3. + ?s1 "predicate_1"@[] ?o1; + "predicate_2"@[] ?o2; + "predicate_3"@[] ?o3} + into ?a + from ?b + where {?s "old_predicate_1"@[,] ?o1. + ?s "old_predicate_2"@[,] ?o2. + ?s1 "old_predicate_3"@[,] ?o3};`, + want_one: 3, + want_two: 3, }, { query: `construct {?s "predicate_1"@[2015-07-19T13:12:04.669618843-07:00] ?o1; - "predicate_2"@[2015-07-19T13:12:04.669618843-07:00] ?o2. - ?s "predicate_3"@[2015-07-19T13:12:04.669618843-07:00] ?o3} into ?a from ?b where {?s "old_predicate_1"@[,] ?o1. - ?s "old_predicate_2"@[,] ?o2. - ?s "old_predicate_3"@[,] ?o3};`, - want_one: 1, - want_two: 0, + "predicate_2"@[2015-07-19T13:12:04.669618843-07:00] ?o2. + ?s "predicate_3"@[2015-07-19T13:12:04.669618843-07:00] ?o3} + into ?a + from ?b + where {?s "old_predicate_1"@[,] ?o1. + ?s "old_predicate_2"@[,] ?o2. + ?s "old_predicate_3"@[,] ?o3};`, + want_one: 2, + want_two: 1, }, { query: `construct {?s "predicate_1"@[2015-07-19T13:12:04.669618843-07:00] ?o1; - "predicate_2"@[2015-07-19T13:12:04.669618843-07:00] ?o2; - "predicate_3"@[2015-07-19T13:12:04.669618843-07:00] ?o3. - ?s1 "predicate_1"@[2015-07-19T13:12:04.669618843-07:00] ?o1; - "predicate_2"@[2015-07-19T13:12:04.669618843-07:00] ?o2; - "predicate_3"@[2015-07-19T13:12:04.669618843-07:00] ?o3} into ?a from ?b where {?s "old_predicate_1"@[,] ?o1. - ?s "old_predicate_2"@[,] ?o2. - ?s1 "old_predicate_3"@[,] ?o3};`, - want_one: 2, - want_two: 2, + "predicate_2"@[2015-07-19T13:12:04.669618843-07:00] ?o2; + "predicate_3"@[2015-07-19T13:12:04.669618843-07:00] ?o3. + ?s1 "predicate_1"@[2015-07-19T13:12:04.669618843-07:00] ?o1; + "predicate_2"@[2015-07-19T13:12:04.669618843-07:00] ?o2; + "predicate_3"@[2015-07-19T13:12:04.669618843-07:00] ?o3} + into ?a + from ?b + where {?s "old_predicate_1"@[,] ?o1. + ?s "old_predicate_2"@[,] ?o2. + ?s1 "old_predicate_3"@[,] ?o3};`, + want_one: 3, + want_two: 3, }, { query: `construct {?s "predicate_1"@[] ?o1; - "predicate_2"@[] ?o2; - "predicate_3"@[?t] ?o3. - ?s1 "predicate_1"@[] ?o1; - "predicate_2"@[] ?o2; - "predicate_3"@[?t] ?o3} into ?a from ?b where {?s "old_predicate_1"@[,] ?o1. - ?s "old_predicate_2"@[,] ?o2. - ?s1 "old_predicate_3"@[,] AT ?t ?o3};`, - want_one: 2, - want_two: 2, + "predicate_2"@[] ?o2; + "predicate_3"@[?t] ?o3. + ?s1 "predicate_1"@[] ?o1; + "predicate_2"@[] ?o2; + "predicate_3"@[?t] ?o3} + into ?a + from ?b + where {?s "old_predicate_1"@[,] ?o1. + ?s "old_predicate_2"@[,] ?o2. + ?s1 "old_predicate_3"@[,] AT ?t ?o3};`, + want_one: 3, + want_two: 3, }, - } p, err := NewParser(SemanticBQL()) if err != nil { @@ -545,11 +588,11 @@ func TestSemanticStatementReificationClausesLengthCorrectness(t *testing.T) { if err := p.Parse(NewLLk(entry.query, 1), st); err != nil { t.Errorf("Parser.consume: Failed to accept valid semantic entry %q", entry.query) } - if got, want := len(st.ConstructClauses()[0].ReificationClauses()), entry.want_one; got != want { - t.Errorf("Invalid number of reification clauses for query %q; got %d, want %d; %v", entry.query, got, want, st.ConstructClauses()[0].ReificationClauses()) + if got, want := len(st.ConstructClauses()[0].PredicateObjectPairs()), entry.want_one; got != want { + t.Errorf("Invalid number of predicate-object pairs for query %q; got %d, want %d; %v", entry.query, got, want, st.ConstructClauses()[0].PredicateObjectPairs()) } - if got, want := len(st.ConstructClauses()[1].ReificationClauses()), entry.want_two; got != want { - t.Errorf("Invalid number of reification clauses for query %q; got %d, want %d; %v", entry.query, got, want, st.ConstructClauses()[0].ReificationClauses()) + if got, want := len(st.ConstructClauses()[1].PredicateObjectPairs()), entry.want_two; got != want { + t.Errorf("Invalid number of predicate-object pairs for query %q; got %d, want %d; %v", entry.query, got, want, st.ConstructClauses()[0].PredicateObjectPairs()) } } } diff --git a/bql/planner/planner.go b/bql/planner/planner.go index 16df4b01..bccc0c52 100644 --- a/bql/planner/planner.go +++ b/bql/planner/planner.go @@ -832,138 +832,87 @@ func (p *constructPlan) Type() string { return "CONSTRUCT" } -func (p *constructPlan) processConstructClause(cc *semantic.ConstructClause, tbl *table.Table, r table.Row) (*triple.Triple, error) { - var err error - sbj, prd, obj := cc.S, cc.P, cc.O - if sbj == nil && tbl.HasBinding(cc.SBinding) { - v, ok := r[cc.SBinding] - if !ok { - return nil, fmt.Errorf("row %+v misses binding %q", r, cc.SBinding) - } - if v.N == nil { - return nil, fmt.Errorf("binding %q requires a node, got %+v instead", cc.SBinding, v) - } - sbj = v.N - } - if prd == nil { - if tbl.HasBinding(cc.PBinding) { - // Try to bind the predicate. - v, ok := r[cc.PBinding] - if !ok { - return nil, fmt.Errorf("row %+v misses binding %q", r, cc.PBinding) - } - if v.P == nil { - return nil, fmt.Errorf("binding %q requires a predicate, got %+v instead", cc.PBinding, v) - } - prd = v.P - } else if cc.PTemporal && cc.PAnchorBinding != "" { - // Try to bind the predicate anchor. - v, ok := r[cc.PAnchorBinding] - if !ok { - return nil, fmt.Errorf("row %+v misses binding %q", r, cc.PAnchorBinding) - } - if v.T == nil { - return nil, fmt.Errorf("binding %q requires a time, got %+v instead", cc.PAnchorBinding, v) - } - prd, err = predicate.NewTemporal(cc.PID, *v.T) - if err != nil { - return nil, err - } - } - } - if obj == nil { - if tbl.HasBinding(cc.OBinding) { - // Try to bind the object - v, ok := r[cc.OBinding] - if !ok { - return nil, fmt.Errorf("row %+v misses binding %q", r, cc.OBinding) - } - co, err := cellToObject(v) - if err != nil { - return nil, err - } - obj = co - } else if cc.OTemporal && cc.OAnchorBinding != "" { - // Try to bind the object anchor. - v, ok := r[cc.OAnchorBinding] - if !ok { - return nil, fmt.Errorf("row %+v misses binding %q", r, cc.OAnchorBinding) - } - if v.T == nil { - return nil, fmt.Errorf("binding %q requires a time, got %+v instead", cc.OAnchorBinding, v) - } - op, err := predicate.NewTemporal(cc.OID, *v.T) - if err != nil { - return nil, err - } - obj = triple.NewPredicateObject(op) - } - } - t, err := triple.New(sbj, prd, obj) - return t, err -} - -func (p *constructPlan) processReificationClause(rc *semantic.ReificationClause, tbl *table.Table, r table.Row) (*predicate.Predicate, *triple.Object, error) { +func (p *constructPlan) processPredicateObjectPair(pop *semantic.ConstructPredicateObjectPair, tbl *table.Table, r table.Row) (*predicate.Predicate, *triple.Object, error) { var err error - rprd, robj := rc.P, rc.O + rprd, robj := pop.P, pop.O if rprd == nil { - if tbl.HasBinding(rc.PBinding) { + if tbl.HasBinding(pop.PBinding) { // Try to bind the predicate. - v, ok := r[rc.PBinding] + v, ok := r[pop.PBinding] if !ok { - return nil, nil, fmt.Errorf("row %+v misses binding %q", r, rc.PBinding) + return nil, nil, fmt.Errorf("row %+v misses binding %q", r, pop.PBinding) } if v.P == nil { - return nil, nil, fmt.Errorf("binding %q requires a predicate, got %+v instead", rc.PBinding, v) + return nil, nil, fmt.Errorf("binding %q requires a predicate, got %+v instead", pop.PBinding, v) } rprd = v.P - } else if rc.PTemporal && rc.PAnchorBinding != "" { + } else if pop.PTemporal && pop.PAnchorBinding != "" { // Try to bind the predicate anchor. - v, ok := r[rc.PAnchorBinding] + v, ok := r[pop.PAnchorBinding] if !ok { - return nil, nil, fmt.Errorf("row %+v misses binding %q", r, rc.PAnchorBinding) + return nil, nil, fmt.Errorf("row %+v misses binding %q", r, pop.PAnchorBinding) } if v.T == nil { - return nil, nil, fmt.Errorf("binding %q requires a time, got %+v instead", rc.PAnchorBinding, v) + return nil, nil, fmt.Errorf("binding %q requires a time, got %+v instead", pop.PAnchorBinding, v) } - rprd, err = predicate.NewTemporal(rc.PID, *v.T) + rprd, err = predicate.NewTemporal(pop.PID, *v.T) if err != nil { return nil, nil, err } } } if robj == nil { - if tbl.HasBinding(rc.OBinding) { + if tbl.HasBinding(pop.OBinding) { // Try to bind the object - v, ok := r[rc.OBinding] + v, ok := r[pop.OBinding] if !ok { - return nil, nil, fmt.Errorf("row %+v misses binding %q", r, rc.OBinding) + return nil, nil, fmt.Errorf("row %+v misses binding %q", r, pop.OBinding) } co, err := cellToObject(v) if err != nil { return nil, nil, err } robj = co - } else if rc.OTemporal && rc.OAnchorBinding != "" { + } else if pop.OTemporal && pop.OAnchorBinding != "" { // Try to bind the object anchor. - v, ok := r[rc.OAnchorBinding] + v, ok := r[pop.OAnchorBinding] if !ok { - return nil, nil, fmt.Errorf("row %+v misses binding %q", r, rc.OAnchorBinding) + return nil, nil, fmt.Errorf("row %+v misses binding %q", r, pop.OAnchorBinding) } if v.T == nil { - return nil, nil, fmt.Errorf("binding %q requires a time, got %+v instead", rc.OAnchorBinding, v) + return nil, nil, fmt.Errorf("binding %q requires a time, got %+v instead", pop.OAnchorBinding, v) } - rop, err := predicate.NewTemporal(rc.OID, *v.T) + rop, err := predicate.NewTemporal(pop.OID, *v.T) if err != nil { return nil, nil, err } robj = triple.NewPredicateObject(rop) } - } return rprd, robj, nil } +func (p *constructPlan) processConstructClause(cc *semantic.ConstructClause, tbl *table.Table, r table.Row) (*triple.Triple, error) { + var err error + sbj := cc.S + if sbj == nil && tbl.HasBinding(cc.SBinding) { + v, ok := r[cc.SBinding] + if !ok { + return nil, fmt.Errorf("row %+v misses binding %q", r, cc.SBinding) + } + if v.N == nil { + return nil, fmt.Errorf("binding %q requires a node, got %+v instead", cc.SBinding, v) + } + sbj = v.N + } + prd, obj, err := p.processPredicateObjectPair(cc.PredicateObjectPairs()[0], tbl, r) + if err != nil { + return nil, err + } + t, err := triple.New(sbj, prd, obj) + return t, err +} + func (p *constructPlan) Execute(ctx context.Context) (*table.Table, error) { tbl, err := p.queryPlan.Execute(ctx) if err != nil { @@ -1000,7 +949,7 @@ func (p *constructPlan) Execute(ctx context.Context) (*table.Table, error) { if err != nil { return nil, err } - if len(cc.ReificationClauses()) > 0 { + if len(cc.PredicateObjectPairs()) > 1 { // We need to reify a blank node. rts, bn, err := t.Reify() if err != nil { @@ -1010,8 +959,8 @@ func (p *constructPlan) Execute(ctx context.Context) (*table.Table, error) { for _, trpl := range rts[1:] { tripChan <- trpl } - for _, rc := range cc.ReificationClauses() { - rprd, robj, err := p.processReificationClause(rc, tbl, r) + for _, pop := range cc.PredicateObjectPairs()[1:] { + rprd, robj, err := p.processPredicateObjectPair(pop, tbl, r) if err != nil { return nil, err } @@ -1021,7 +970,6 @@ func (p *constructPlan) Execute(ctx context.Context) (*table.Table, error) { } tripChan <- rt } - } else { tripChan <- t } @@ -1030,7 +978,6 @@ func (p *constructPlan) Execute(ctx context.Context) (*table.Table, error) { close(tripChan) // Wait until all triples are added to the store. <-done - return tbl, nil } diff --git a/bql/semantic/hooks.go b/bql/semantic/hooks.go index 1b107c86..867974bb 100644 --- a/bql/semantic/hooks.go +++ b/bql/semantic/hooks.go @@ -160,40 +160,29 @@ func NextWorkingConstructClauseHook() ClauseHook { return NextWorkingConstructClause() } -// ConstructSubjectClauseHook returns the singleton for populating the subject in the +// ConstructSubjectHook returns the singleton for populating the subject in the // working construct clause. -func ConstructSubjectClauseHook() ElementHook { - return constructSubjectClause() +func ConstructSubjectHook() ElementHook { + return constructSubject() } -// ConstructPredicateClauseHook returns the singleton for populating the predicate in the -// working construct clause. -func ConstructPredicateClauseHook() ElementHook { - return constructPredicateClause() -} - -// ConstructObjectClauseHook returns the singleton for populating the object in the -// working construct clause. -func ConstructObjectClauseHook() ElementHook { - return constructObjectClause() -} - -// NextWorkingReificationClauseHook returns the singleton for adding the current reification clause -// and initializing a new reification clause within the working construct statement. -func NextWorkingReificationClauseHook() ClauseHook { - return NextWorkingReificationClause() +// ConstructPredicateHook returns the singleton for populating the predicate in the +// current predicate-object pair in the working construct clause. +func ConstructPredicateHook() ElementHook { + return constructPredicate() } -// ReificationPredicateClauseHook returns the singleton for populating the predicate in the -// current reification clause within the working construct clause. -func ReificationPredicateClauseHook() ElementHook { - return reificationPredicateClause() +// ConstructObjectHook returns the singleton for populating the object in the +// current predicate-object pair in the working construct clause. +func ConstructObjectHook() ElementHook { + return constructObject() } -// ReificationPredicateClauseHook returns the singleton for populating the object in the -// current reification clause within the working construct clause. -func ReificationObjectClauseHook() ElementHook { - return reificationObjectClause() +// NextWorkingConstructPredicateObjectPairClauseHook returns the singleton for adding the current predicate-object pair +// to the set of predicate-objects pairs within the working construct statement and initializing a new +// working predicate-object pair. +func NextWorkingConstructPredicateObjectPairClauseHook() ClauseHook { + return NextWorkingConstructPredicateObjectPair() } // TypeBindingClauseHook returns a ClauseHook that sets the binding type. @@ -972,7 +961,6 @@ func InitWorkingConstructClause() ClauseHook { return f } - // NextWorkingConstructClause returns a clause hook to close the current working // construct clause and start a new working construct clause. func NextWorkingConstructClause() ClauseHook { @@ -984,9 +972,9 @@ func NextWorkingConstructClause() ClauseHook { return f } -// constructSubjectClause returns an element hook that updates the subject +// constructSubject returns an element hook that updates the subject // modifiers on the working construct clause. -func constructSubjectClause() ElementHook { +func constructSubject() ElementHook { var f ElementHook f = func(st *Statement, ce ConsumedElement) (ElementHook, error) { if ce.IsSymbol() { @@ -1015,58 +1003,58 @@ func constructSubjectClause() ElementHook { return f } -// constructPredicateClause returns an element hook that updates the predicate -// modifiers on the working construct clause. -func constructPredicateClause() ElementHook { +// constructPredicate returns an element hook that updates the predicate +// modifiers on the current predicate-object pair of the working graph clause. +func constructPredicate() ElementHook { var f ElementHook f = func(st *Statement, ce ConsumedElement) (ElementHook, error) { if ce.IsSymbol() { return f, nil } tkn := ce.Token() - c := st.WorkingConstructClause() - if c.P != nil { - return nil, fmt.Errorf("invalid predicate %v in construct clause, predicate already set to %v", tkn.Type, c.P) + p := st.WorkingConstructClause().WorkingPredicateObjectPair() + if p.P != nil { + return nil, fmt.Errorf("invalid predicate %v in construct clause, predicate already set to %v", tkn.Type, p.P) } - if c.PID != "" { - return nil, fmt.Errorf("invalid predicate %v in construct clause, predicate already set to %v", tkn.Type, c.PID) + if p.PID != "" { + return nil, fmt.Errorf("invalid predicate %v in construct clause, predicate already set to %v", tkn.Type, p.PID) } - if c.PBinding != "" { - return nil, fmt.Errorf("invalid predicate %v in construct clause, predicate already set to %v", tkn.Type, c.PBinding) + if p.PBinding != "" { + return nil, fmt.Errorf("invalid predicate %v in construct clause, predicate already set to %v", tkn.Type, p.PBinding) } switch tkn.Type { case lexer.ItemPredicate: - p, pID, pAnchorBinding, pTemporal, err := processPredicate(ce) + pred, pID, pAnchorBinding, pTemporal, err := processPredicate(ce) if err != nil { return nil, err } - c.P, c.PID, c.PAnchorBinding, c.PTemporal = p, pID, pAnchorBinding, pTemporal + p.P, p.PID, p.PAnchorBinding, p.PTemporal = pred, pID, pAnchorBinding, pTemporal case lexer.ItemBinding: - c.PBinding = tkn.Text + p.PBinding = tkn.Text } return f, nil } return f } -// constructObjectClause returns an element hook that updates the object -// modifiers on the working graph clause. -func constructObjectClause() ElementHook { +// constructObject returns an element hook that updates the object +// modifiers on the current predicate-object pair of the working graph clause. +func constructObject() ElementHook { var f ElementHook f = func(st *Statement, ce ConsumedElement) (ElementHook, error) { if ce.IsSymbol() { return f, nil } tkn := ce.Token() - c := st.WorkingConstructClause() - if c.O != nil { - return nil, fmt.Errorf("invalid object %v in construct clause, object already set to %v", tkn.Text, c.O) + p := st.WorkingConstructClause().WorkingPredicateObjectPair() + if p.O != nil { + return nil, fmt.Errorf("invalid object %v in construct clause, object already set to %v", tkn.Text, p.O) } - if c.OID != "" { - return nil, fmt.Errorf("invalid object %v in construct clause, objct already set to %v", tkn.Type, c.OID) + if p.OID != "" { + return nil, fmt.Errorf("invalid object %v in construct clause, object already set to %v", tkn.Type, p.OID) } - if c.OBinding != "" { - return nil, fmt.Errorf("invalid object %v in construct clause, object already set to %v", tkn.Type, c.OBinding) + if p.OBinding != "" { + return nil, fmt.Errorf("invalid object %v in construct clause, object already set to %v", tkn.Type, p.OBinding) } switch tkn.Type { case lexer.ItemNode, lexer.ItemBlankNode, lexer.ItemLiteral: @@ -1074,109 +1062,34 @@ func constructObjectClause() ElementHook { if err != nil { return nil, err } - c.O = obj + p.O = obj case lexer.ItemPredicate: var ( pred *predicate.Predicate err error ) - pred, c.OID, c.OAnchorBinding, c.OTemporal, err = processPredicate(ce) + pred, p.OID, p.OAnchorBinding, p.OTemporal, err = processPredicate(ce) if err != nil { return nil, err } if pred != nil { - c.O = triple.NewPredicateObject(pred) + p.O = triple.NewPredicateObject(pred) } case lexer.ItemBinding: - c.OBinding = tkn.Text + p.OBinding = tkn.Text } return f, nil } return f } -// NextWorkingReificationClause returns a clause hook to close the current reifcation -// clause and start a new reification clause within the working construct clause. -func NextWorkingReificationClause() ClauseHook { +// NextWorkingConstructPredicateObjectPair returns a clause hook to close the current +// predicate-object pair and start a new predicate-object pair within the working +// construct clause. +func NextWorkingConstructPredicateObjectPair() ClauseHook { var f ClauseHook f = func(s *Statement, _ Symbol) (ClauseHook, error) { - s.WorkingConstructClause().AddWorkingReificationClause() - return f, nil - } - return f -} - -func reificationPredicateClause() ElementHook { - var f ElementHook - f = func(st *Statement, ce ConsumedElement) (ElementHook, error) { - if ce.IsSymbol() { - return f, nil - } - tkn := ce.Token() - c := st.WorkingConstructClause().WorkingReificationClause() - if c.P != nil { - return nil, fmt.Errorf("invalid predicate %v in construct clause, predicate already set to %v", tkn.Type, c.P) - } - if c.PID != "" { - return nil, fmt.Errorf("invalid predicate %v in construct clause, predicate already set to %v", tkn.Type, c.PID) - } - if c.PBinding != "" { - return nil, fmt.Errorf("invalid predicate %v in construct clause, predicate already set to %v", tkn.Type, c.PBinding) - } - switch tkn.Type { - case lexer.ItemPredicate: - p, pID, pAnchorBinding, pTemporal, err := processPredicate(ce) - if err != nil { - return nil, err - } - c.P, c.PID, c.PAnchorBinding, c.PTemporal = p, pID, pAnchorBinding, pTemporal - case lexer.ItemBinding: - c.PBinding = tkn.Text - } - return f, nil - } - return f -} - -func reificationObjectClause() ElementHook { - var f ElementHook - f = func(st *Statement, ce ConsumedElement) (ElementHook, error) { - if ce.IsSymbol() { - return f, nil - } - tkn := ce.Token() - c := st.WorkingConstructClause().WorkingReificationClause() - if c.O != nil { - return nil, fmt.Errorf("invalid object %v in construct clause, object already set to %v", tkn.Text, c.O) - } - if c.OID != "" { - return nil, fmt.Errorf("invalid object %v in construct clause, objct already set to %v", tkn.Type, c.OID) - } - if c.OBinding != "" { - return nil, fmt.Errorf("invalid object %v in construct clause, object already set to %v", tkn.Type, c.OBinding) - } - switch tkn.Type { - case lexer.ItemNode, lexer.ItemBlankNode, lexer.ItemLiteral: - obj, err := triple.ParseObject(tkn.Text, literal.DefaultBuilder()) - if err != nil { - return nil, err - } - c.O = obj - case lexer.ItemPredicate: - var ( - pred *predicate.Predicate - err error - ) - pred, c.OID, c.OAnchorBinding, c.OTemporal, err = processPredicate(ce) - if err != nil { - return nil, err - } - if pred != nil { - c.O = triple.NewPredicateObject(pred) - } - case lexer.ItemBinding: - c.OBinding = tkn.Text - } + s.WorkingConstructClause().AddWorkingPredicateObjectPair() return f, nil } return f diff --git a/bql/semantic/hooks_test.go b/bql/semantic/hooks_test.go index 883017de..7c4b985f 100644 --- a/bql/semantic/hooks_test.go +++ b/bql/semantic/hooks_test.go @@ -1974,18 +1974,35 @@ func TestNextWorkingConstructClauseHook(t *testing.T) { 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) + t.Errorf("semantic.NextWorkingConstructClause should have returned two clauses for statement %v; got %d, want %d", st, got, want) } } -type testConstructClauseTable struct { +func TestNextWorkingConstructPredicateObjectPairClauseHook(t *testing.T) { + f := NextWorkingConstructPredicateObjectPair() + st := &Statement{} + st.ResetWorkingConstructClause() + wcc := st.WorkingConstructClause() + wcc.ResetWorkingPredicateObjectPair() + wrs := wcc.WorkingPredicateObjectPair() + wrs.PBinding = "?a" + f(st, Symbol("FOO")) + wrs = wcc.WorkingPredicateObjectPair() + wrs.PBinding = "?b" + f(st, Symbol("FOO")) + if got, want := len(wcc.PredicateObjectPairs()), 2; got != want { + t.Errorf("semantic.NextWorkingConstructPredicateObjectPair should have returned two clauses for statement %v; got %d, want %d", st, got, want) + } +} + +type testConstructSubjectHookTable struct { valid bool id string ces []ConsumedElement want *ConstructClause } -func runTabulatedConstructClauseHookTest(t *testing.T, testName string, f ElementHook, table []testConstructClauseTable) { +func runTabulatedConstructSubjectHookTest(t *testing.T, testName string, f ElementHook, table []testConstructSubjectHookTable) { st := &Statement{} st.ResetWorkingConstructClause() failed := false @@ -2012,9 +2029,9 @@ func runTabulatedConstructClauseHookTest(t *testing.T, testName string, f Elemen } } -func TestConstructSubjectClauseHook(t *testing.T) { +func TestConstructSubjectHook(t *testing.T) { st := &Statement{} - f := constructSubjectClause() + f := constructSubject() st.ResetWorkingConstructClause() n, err := node.Parse("/_") if err != nil { @@ -2024,7 +2041,7 @@ func TestConstructSubjectClauseHook(t *testing.T) { if err != nil { t.Fatalf("node.Parse called for '_:v1' failed with error %v", err) } - runTabulatedConstructClauseHookTest(t, "semantic.constructSubjectClause", f, []testConstructClauseTable{ + runTabulatedConstructSubjectHookTest(t, "semantic.constructSubject", f, []testConstructSubjectHookTable{ { valid: true, id: "valid node", @@ -2064,7 +2081,7 @@ func TestConstructSubjectClauseHook(t *testing.T) { }), }, want: &ConstructClause{ - SBinding: "?foo", + SBinding: "?foo", }, }, { @@ -2087,279 +2104,18 @@ func TestConstructSubjectClauseHook(t *testing.T) { }) } -func TestConstructPredicateClauseHook(t *testing.T) { - st := &Statement{} - f := constructPredicateClause() - st.ResetWorkingConstructClause() - ip, err := predicate.Parse(`"foo"@[]`) - if err != nil { - t.Fatalf("predicate.Parse failed with error %v", err) - } - tp, err := predicate.Parse(`"foo"@[2015-07-19T13:12:04.669618843-07:00]`) - if err != nil { - t.Fatalf("predicate.Parse failed with error %v", err) - } - runTabulatedConstructClauseHookTest(t, "semantic.constructPredicateClause", f, []testConstructClauseTable{ - { - valid: true, - id: "valid immutable predicate", - ces: []ConsumedElement{ - NewConsumedSymbol("CONSTRUCT_PREDICATE"), - NewConsumedToken(&lexer.Token{ - Type: lexer.ItemPredicate, - Text: `"foo"@[]`, - }), - }, - want: &ConstructClause{ - P: ip, - PTemporal: false, - }, - }, - { - valid: true, - id: "valid temporal predicate", - ces: []ConsumedElement{ - NewConsumedSymbol("CONSTRUCT_PREDICATE"), - NewConsumedToken(&lexer.Token{ - Type: lexer.ItemPredicate, - Text: `"foo"@[2015-07-19T13:12:04.669618843-07:00]`, - }), - }, - want: &ConstructClause{ - P: tp, - PTemporal: true, - }, - }, - { - valid: true, - id: "valid temporal predicate with bound time anchor", - ces: []ConsumedElement{ - NewConsumedSymbol("CONSTRUCT_PREDICATE"), - NewConsumedToken(&lexer.Token{ - Type: lexer.ItemPredicate, - Text: `"foo"@[?bar]`, - }), - }, - want: &ConstructClause{ - PID: "foo", - PAnchorBinding: "?bar", - PTemporal: true, - }, - }, - { - valid: true, - id: "valid binding", - ces: []ConsumedElement{ - NewConsumedSymbol("CONSTRUCT_PREDICATE"), - NewConsumedToken(&lexer.Token{ - Type: lexer.ItemBinding, - Text: "?foo", - }), - }, - want: &ConstructClause{ - PBinding: "?foo", - }, - }, - { - valid: false, - id: "invalid temporal predicate and binding", - ces: []ConsumedElement{ - NewConsumedSymbol("CONSTRUCT_PREDICATE"), - NewConsumedToken(&lexer.Token{ - Type: lexer.ItemPredicate, - Text: `"foo"@[?bar]`, - }), - NewConsumedSymbol("FOO"), - NewConsumedToken(&lexer.Token{ - Type: lexer.ItemBinding, - Text: "?foo", - }), - }, - want: &ConstructClause{}, - }, - }) -} - -func TestConstructObjectClauseHook(t *testing.T) { - st := &Statement{} - f := constructObjectClause() - st.ResetWorkingConstructClause() - n, err := node.Parse("/_") - if err != nil { - t.Fatalf("node.Parse failed with error %v", err) - } - no := triple.NewNodeObject(n) - bn, err := node.Parse("_:v1") - if err != nil { - t.Fatalf("node.Parse failed with error %v", err) - } - bno := triple.NewNodeObject(bn) - ip, err := predicate.Parse(`"foo"@[]`) - if err != nil { - t.Fatalf("predicate.Parse failed with error %v", err) - } - ipo := triple.NewPredicateObject(ip) - tp, err := predicate.Parse(`"foo"@[2015-07-19T13:12:04.669618843-07:00]`) - if err != nil { - t.Fatalf("predicate.Parse failed with error %v", err) - } - tpo := triple.NewPredicateObject(tp) - l, err := triple.ParseObject(`"1"^^type:int64`, literal.DefaultBuilder()) - if err != nil { - t.Fatalf("literal.Parse should never fail to parse %s with error %v", `"1"^^type:int64`, err) - } - runTabulatedConstructClauseHookTest(t, "semantic.constructObjectClause", f, []testConstructClauseTable{ - { - valid: true, - id: "valid node object", - ces: []ConsumedElement{ - NewConsumedSymbol("CONSTRUCT_OBJECT"), - NewConsumedToken(&lexer.Token{ - Type: lexer.ItemNode, - Text: "/_", - }), - }, - want: &ConstructClause{ - O: no, - }, - }, - { - valid: true, - id: "valid blank node object", - ces: []ConsumedElement{ - NewConsumedSymbol("CONSTRUCT_OBJECT"), - NewConsumedToken(&lexer.Token{ - Type: lexer.ItemBlankNode, - Text: "_:v1", - }), - }, - want: &ConstructClause{ - O: bno, - }, - }, - { - valid: true, - id: "valid literal object", - ces: []ConsumedElement{ - NewConsumedSymbol("CONSTRUCT_OBJECT"), - NewConsumedToken(&lexer.Token{ - Type: lexer.ItemLiteral, - Text: `"1"^^type:int64`, - }), - }, - want: &ConstructClause{ - O: l, - }, - }, - { - valid: true, - id: "valid immutable predicate object", - ces: []ConsumedElement{ - NewConsumedSymbol("CONSTRUCT_OBJECT"), - NewConsumedToken(&lexer.Token{ - Type: lexer.ItemPredicate, - Text: `"foo"@[]`, - }), - }, - want: &ConstructClause{ - O: ipo, - OTemporal: false, - }, - }, - { - valid: true, - id: "valid temporal predicate object", - ces: []ConsumedElement{ - NewConsumedSymbol("CONSTRUCT_OBJECT"), - NewConsumedToken(&lexer.Token{ - Type: lexer.ItemPredicate, - Text: `"foo"@[2015-07-19T13:12:04.669618843-07:00]`, - }), - }, - want: &ConstructClause{ - O: tpo, - OTemporal: true, - }, - }, - { - valid: true, - id: "valid temporal predicate object with bound time anchor", - ces: []ConsumedElement{ - NewConsumedSymbol("CONSTRUCT_OBJECT"), - NewConsumedToken(&lexer.Token{ - Type: lexer.ItemPredicate, - Text: `"foo"@[?bar]`, - }), - }, - want: &ConstructClause{ - OID: "foo", - OAnchorBinding: "?bar", - OTemporal: true, - }, - }, - { - valid: true, - id: "valid binding", - ces: []ConsumedElement{ - NewConsumedSymbol("CONSTRUCT_OBJECT"), - NewConsumedToken(&lexer.Token{ - Type: lexer.ItemBinding, - Text: "?foo", - }), - }, - want: &ConstructClause{ - OBinding: "?foo", - }, - }, - { - valid: false, - id: "invalid temporal predicate and binding objects", - ces: []ConsumedElement{ - NewConsumedSymbol("CONSTRUCT_OBJECT"), - NewConsumedToken(&lexer.Token{ - Type: lexer.ItemPredicate, - Text: `"foo"@[?bar]`, - }), - NewConsumedSymbol("FOO"), - NewConsumedToken(&lexer.Token{ - Type: lexer.ItemBinding, - Text: "?foo", - }), - }, - want: &ConstructClause{}, - }, - }) -} - -func TestNextWorkingReificationClauseHook(t *testing.T) { - f := NextWorkingReificationClause() - st := &Statement{} - st.ResetWorkingConstructClause() - wcc := st.WorkingConstructClause() - wcc.ResetWorkingReificationClause() - wrs := wcc.WorkingReificationClause() - wrs.PBinding = "?a" - f(st, Symbol("FOO")) - wrs = wcc.WorkingReificationClause() - wrs.PBinding = "?b" - f(st, Symbol("FOO")) - if got, want := len(wcc.ReificationClauses()), 2; got != want { - t.Errorf("semantic.NextReificationWorkingClause should have returned two clauses for statement %v; got %d, want %d", st, got, want) - } -} - -type testReificationClauseTable struct { +type testConstructPredicateObjectHooksTable struct { valid bool id string ces []ConsumedElement - want *ReificationClause + want *ConstructPredicateObjectPair } -func runTabulatedReificationClauseHookTest(t *testing.T, testName string, f ElementHook, table []testReificationClauseTable) { +func runTabulatedConstructPredicateObjectHooksTest(t *testing.T, testName string, f ElementHook, table []testConstructPredicateObjectHooksTable) { st := &Statement{} st.ResetWorkingConstructClause() wcc := st.WorkingConstructClause() - wcc.ResetWorkingReificationClause() + wcc.ResetWorkingPredicateObjectPair() failed := false for _, entry := range table { for _, ce := range entry.ces { @@ -2372,7 +2128,7 @@ func runTabulatedReificationClauseHookTest(t *testing.T, testName string, f Elem } } if entry.valid { - if got, want := wcc.WorkingReificationClause(), entry.want; !reflect.DeepEqual(got, want) { + if got, want := wcc.WorkingPredicateObjectPair(), entry.want; !reflect.DeepEqual(got, want) { t.Errorf("%s case %q should have populated all required fields; got %+v, want %+v", testName, entry.id, got, want) } } else { @@ -2380,16 +2136,16 @@ func runTabulatedReificationClauseHookTest(t *testing.T, testName string, f Elem t.Errorf("%s failed to reject invalid case %q", testName, entry.id) } } - wcc.ResetWorkingReificationClause() + wcc.ResetWorkingPredicateObjectPair() } } -func TestReificationPredicateClauseHook(t *testing.T) { +func TestConstructPredicateHook(t *testing.T) { st := &Statement{} - f := reificationPredicateClause() + f := constructPredicate() st.ResetWorkingConstructClause() wcc := st.WorkingConstructClause() - wcc.ResetWorkingReificationClause() + wcc.ResetWorkingPredicateObjectPair() ip, err := predicate.Parse(`"foo"@[]`) if err != nil { t.Fatalf("predicate.Parse failed with error %v", err) @@ -2398,18 +2154,18 @@ func TestReificationPredicateClauseHook(t *testing.T) { if err != nil { t.Fatalf("predicate.Parse failed with error %v", err) } - runTabulatedReificationClauseHookTest(t, "semantic.reificationPredicateClause", f, []testReificationClauseTable{ + runTabulatedConstructPredicateObjectHooksTest(t, "semantic.constructPredicateClause", f, []testConstructPredicateObjectHooksTable{ { valid: true, id: "valid immutable predicate", ces: []ConsumedElement{ - NewConsumedSymbol("REIFICATION_PREDICATE"), + NewConsumedSymbol("CONSTRUCT_PREDICATE"), NewConsumedToken(&lexer.Token{ Type: lexer.ItemPredicate, Text: `"foo"@[]`, }), }, - want: &ReificationClause{ + want: &ConstructPredicateObjectPair{ P: ip, PTemporal: false, }, @@ -2418,13 +2174,13 @@ func TestReificationPredicateClauseHook(t *testing.T) { valid: true, id: "valid temporal predicate", ces: []ConsumedElement{ - NewConsumedSymbol("REIFICATION_PREDICATE"), + NewConsumedSymbol("CONSTRUCT_PREDICATE"), NewConsumedToken(&lexer.Token{ Type: lexer.ItemPredicate, Text: `"foo"@[2015-07-19T13:12:04.669618843-07:00]`, }), }, - want: &ReificationClause{ + want: &ConstructPredicateObjectPair{ P: tp, PTemporal: true, }, @@ -2433,13 +2189,13 @@ func TestReificationPredicateClauseHook(t *testing.T) { valid: true, id: "valid temporal predicate with bound time anchor", ces: []ConsumedElement{ - NewConsumedSymbol("REIFICATION_PREDICATE"), + NewConsumedSymbol("CONSTRUCT_PREDICATE"), NewConsumedToken(&lexer.Token{ Type: lexer.ItemPredicate, Text: `"foo"@[?bar]`, }), }, - want: &ReificationClause{ + want: &ConstructPredicateObjectPair{ PID: "foo", PAnchorBinding: "?bar", PTemporal: true, @@ -2449,13 +2205,13 @@ func TestReificationPredicateClauseHook(t *testing.T) { valid: true, id: "valid binding", ces: []ConsumedElement{ - NewConsumedSymbol("REIFICATION_PREDICATE"), + NewConsumedSymbol("CONSTRUCT_PREDICATE"), NewConsumedToken(&lexer.Token{ Type: lexer.ItemBinding, Text: "?foo", }), }, - want: &ReificationClause{ + want: &ConstructPredicateObjectPair{ PBinding: "?foo", }, }, @@ -2463,7 +2219,7 @@ func TestReificationPredicateClauseHook(t *testing.T) { valid: false, id: "invalid temporal predicate and binding", ces: []ConsumedElement{ - NewConsumedSymbol("REIFICATION_PREDICATE"), + NewConsumedSymbol("CONSTRUCT_PREDICATE"), NewConsumedToken(&lexer.Token{ Type: lexer.ItemPredicate, Text: `"foo"@[?bar]`, @@ -2474,17 +2230,17 @@ func TestReificationPredicateClauseHook(t *testing.T) { Text: "?foo", }), }, - want: &ReificationClause{}, + want: &ConstructPredicateObjectPair{}, }, }) } -func TestReificationObjectClauseHook(t *testing.T) { +func TestConstructObjectHook(t *testing.T) { st := &Statement{} - f := reificationObjectClause() + f := constructObject() st.ResetWorkingConstructClause() wcc := st.WorkingConstructClause() - wcc.ResetWorkingReificationClause() + wcc.ResetWorkingPredicateObjectPair() n, err := node.Parse("/_") if err != nil { t.Fatalf("node.Parse failed with error %v", err) @@ -2509,18 +2265,18 @@ func TestReificationObjectClauseHook(t *testing.T) { if err != nil { t.Fatalf("literal.Parse should never fail to parse %s with error %v", `"1"^^type:int64`, err) } - runTabulatedReificationClauseHookTest(t, "semantic.reificationObjectClause", f, []testReificationClauseTable{ + runTabulatedConstructPredicateObjectHooksTest(t, "semantic.constructObjectClause", f, []testConstructPredicateObjectHooksTable{ { valid: true, id: "valid node object", ces: []ConsumedElement{ - NewConsumedSymbol("REIFICATION_OBJECT"), + NewConsumedSymbol("CONSTRUCT_OBJECT"), NewConsumedToken(&lexer.Token{ Type: lexer.ItemNode, Text: "/_", }), }, - want: &ReificationClause{ + want: &ConstructPredicateObjectPair{ O: no, }, }, @@ -2528,13 +2284,13 @@ func TestReificationObjectClauseHook(t *testing.T) { valid: true, id: "valid blank node object", ces: []ConsumedElement{ - NewConsumedSymbol("REIFICATION_OBJECT"), + NewConsumedSymbol("CONSTRUCT_OBJECT"), NewConsumedToken(&lexer.Token{ Type: lexer.ItemBlankNode, Text: "_:v1", }), }, - want: &ReificationClause{ + want: &ConstructPredicateObjectPair{ O: bno, }, }, @@ -2542,13 +2298,13 @@ func TestReificationObjectClauseHook(t *testing.T) { valid: true, id: "valid literal object", ces: []ConsumedElement{ - NewConsumedSymbol("REIFICATION_OBJECT"), + NewConsumedSymbol("CONSTRUCT_OBJECT"), NewConsumedToken(&lexer.Token{ Type: lexer.ItemLiteral, Text: `"1"^^type:int64`, }), }, - want: &ReificationClause{ + want: &ConstructPredicateObjectPair{ O: l, }, }, @@ -2556,13 +2312,13 @@ func TestReificationObjectClauseHook(t *testing.T) { valid: true, id: "valid immutable predicate object", ces: []ConsumedElement{ - NewConsumedSymbol("REIFICATION_OBJECT"), + NewConsumedSymbol("CONSTRUCT_OBJECT"), NewConsumedToken(&lexer.Token{ Type: lexer.ItemPredicate, Text: `"foo"@[]`, }), }, - want: &ReificationClause{ + want: &ConstructPredicateObjectPair{ O: ipo, OTemporal: false, }, @@ -2571,13 +2327,13 @@ func TestReificationObjectClauseHook(t *testing.T) { valid: true, id: "valid temporal predicate object", ces: []ConsumedElement{ - NewConsumedSymbol("REIFICATION_OBJECT"), + NewConsumedSymbol("CONSTRUCT_OBJECT"), NewConsumedToken(&lexer.Token{ Type: lexer.ItemPredicate, Text: `"foo"@[2015-07-19T13:12:04.669618843-07:00]`, }), }, - want: &ReificationClause{ + want: &ConstructPredicateObjectPair{ O: tpo, OTemporal: true, }, @@ -2586,13 +2342,13 @@ func TestReificationObjectClauseHook(t *testing.T) { valid: true, id: "valid temporal predicate object with bound time anchor", ces: []ConsumedElement{ - NewConsumedSymbol("REIFICATION_OBJECT"), + NewConsumedSymbol("CONSTRUCT_OBJECT"), NewConsumedToken(&lexer.Token{ Type: lexer.ItemPredicate, Text: `"foo"@[?bar]`, }), }, - want: &ReificationClause{ + want: &ConstructPredicateObjectPair{ OID: "foo", OAnchorBinding: "?bar", OTemporal: true, @@ -2602,13 +2358,13 @@ func TestReificationObjectClauseHook(t *testing.T) { valid: true, id: "valid binding", ces: []ConsumedElement{ - NewConsumedSymbol("REIFICATION_OBJECT"), + NewConsumedSymbol("CONSTRUCT_OBJECT"), NewConsumedToken(&lexer.Token{ Type: lexer.ItemBinding, Text: "?foo", }), }, - want: &ReificationClause{ + want: &ConstructPredicateObjectPair{ OBinding: "?foo", }, }, @@ -2616,7 +2372,7 @@ func TestReificationObjectClauseHook(t *testing.T) { valid: false, id: "invalid temporal predicate and binding objects", ces: []ConsumedElement{ - NewConsumedSymbol("REIFICATION_OBJECT"), + NewConsumedSymbol("CONSTRUCT_OBJECT"), NewConsumedToken(&lexer.Token{ Type: lexer.ItemPredicate, Text: `"foo"@[?bar]`, @@ -2627,7 +2383,7 @@ func TestReificationObjectClauseHook(t *testing.T) { Text: "?foo", }), }, - want: &ReificationClause{}, + want: &ConstructPredicateObjectPair{}, }, }) } diff --git a/bql/semantic/semantic.go b/bql/semantic/semantic.go index bdd90ba4..50fe4141 100644 --- a/bql/semantic/semantic.go +++ b/bql/semantic/semantic.go @@ -138,24 +138,12 @@ type ConstructClause struct { S *node.Node SBinding string - P *predicate.Predicate - PBinding string - PID string - PAnchorBinding string - PTemporal bool - - O *triple.Object - OBinding string - OID string - OAnchorBinding string - OTemporal bool - - reificationClauses []*ReificationClause - workingReificationClause *ReificationClause + predicateObjectPairs []*ConstructPredicateObjectPair + workingPredicateObjectPair *ConstructPredicateObjectPair } -// ReificationClause represents a clause used to reify a triple. -type ReificationClause struct { +// ConstructPredicateObjectPair represents a predicate-object pair within a CONSTRUCT clause. +type ConstructPredicateObjectPair struct { P *predicate.Predicate PBinding string PID string @@ -395,72 +383,11 @@ func (c *ConstructClause) String() string { b.WriteString(c.SBinding) } - // Predicate section. - predicate := false - if c.P != nil { - b.WriteString(" ") - b.WriteString(c.P.String()) - predicate = true - } - if c.PBinding != "" { - b.WriteString(" ") - b.WriteString(c.PBinding) - } - if c.PID != "" { - b.WriteString(" \"") - b.WriteString(c.PID) - b.WriteString("\"") - } - if !predicate { - if !c.PTemporal { - b.WriteString("@[]") - } else { - b.WriteString("@[") - if c.PAnchorBinding != "" { - b.WriteString(c.PAnchorBinding) - } - } - b.WriteString("]") - } - - // Object section. - // Node portion. - object := false - if c.O != nil { - b.WriteString(" ") - b.WriteString(c.O.String()) - object = true - } else { - b.WriteString(" ") - b.WriteString(c.OBinding) - object = true - } - // Predicate portion. - if !object { - if c.OBinding != "" { - b.WriteString(" ") - b.WriteString(c.OBinding) - } - if c.OID != "" { - b.WriteString(" \"") - b.WriteString(c.OID) - b.WriteString("\"") - } - if !c.OTemporal { - b.WriteString("[]") - } else { - b.WriteString("[") - if c.OAnchorBinding != "" { - b.WriteString(c.OAnchorBinding) - } - b.WriteString("]") - } - } - - // Reification clauses portion. - for _, rc := range c.ReificationClauses() { - b.WriteString(fmt.Sprintf(";%v", rc)) + // Predicate-object pairs section. + for _, pop := range c.PredicateObjectPairs() { + b.WriteString(fmt.Sprintf("%v;", pop)) } + b.Truncate(b.Len()-1) b.WriteString(" }") return b.String() } @@ -470,8 +397,8 @@ func (c *ConstructClause) IsEmpty() bool { return reflect.DeepEqual(c, &ConstructClause{}) } -// String returns a readable representation of a reification clause. -func (c *ReificationClause) String() string { +// String returns a readable representation of a ConstructPredicateObjectPair. +func (c *ConstructPredicateObjectPair) String() string { b := bytes.NewBufferString("") // Predicate section. @@ -538,9 +465,9 @@ func (c *ReificationClause) String() string { return b.String() } -// IsEmpty will return true if there are no set values in the reification clause. -func (c *ReificationClause) IsEmpty() bool { - return reflect.DeepEqual(c, &ReificationClause{}) +// IsEmpty will return true if there are no set values in the predicate-object pair. +func (c *ConstructPredicateObjectPair) IsEmpty() bool { + return reflect.DeepEqual(c, &ConstructPredicateObjectPair{}) } // BindType sets the type of a statement. @@ -832,30 +759,18 @@ func (s *Statement) InputBindings() []string { if c.SBinding != "" { res = append(res, c.SBinding) } - if c.PBinding != "" { - res = append(res, c.PBinding) - } - if c.PAnchorBinding != "" { - res = append(res, c.PAnchorBinding) - } - if c.OBinding != "" { - res = append(res, c.OBinding) - } - if c.OAnchorBinding != "" { - res = append(res, c.OAnchorBinding) - } - for _, r := range c.reificationClauses { - if r.PBinding != "" { - res = append(res, r.PBinding) + for _, p := range c.predicateObjectPairs { + if p.PBinding != "" { + res = append(res, p.PBinding) } - if r.PAnchorBinding != "" { - res = append(res, r.PAnchorBinding) + if p.PAnchorBinding != "" { + res = append(res, p.PAnchorBinding) } - if r.OBinding != "" { - res = append(res, r.OBinding) + if p.OBinding != "" { + res = append(res, p.OBinding) } - if r.OAnchorBinding != "" { - res = append(res, r.OAnchorBinding) + if p.OAnchorBinding != "" { + res = append(res, p.OAnchorBinding) } } } @@ -881,38 +796,22 @@ func (s *Statement) OutputBindings() []string { res = append(res, c.SBinding) set[c.SBinding] = true } - if _, ok := set[c.PBinding]; !ok { - res = append(res, c.PBinding) - set[c.PBinding] = true - } - if _, ok := set[c.PAnchorBinding]; !ok { - res = append(res, c.PAnchorBinding) - set[c.PAnchorBinding] = true - } - if _, ok := set[c.OBinding]; !ok { - res = append(res, c.OBinding) - set[c.OBinding] = true - } - if _, ok := set[c.OAnchorBinding]; !ok { - res = append(res, c.OAnchorBinding) - set[c.OAnchorBinding] = true - } - for _, r := range c.reificationClauses { - if _, ok := set[r.PBinding]; !ok { - res = append(res, r.PBinding) - set[r.PBinding] = true + for _, p := range c.predicateObjectPairs { + if _, ok := set[p.PBinding]; !ok { + res = append(res, p.PBinding) + set[p.PBinding] = true } - if _, ok := set[r.PAnchorBinding]; !ok { - res = append(res, r.PAnchorBinding) - set[r.PAnchorBinding] = true + if _, ok := set[p.PAnchorBinding]; !ok { + res = append(res, p.PAnchorBinding) + set[p.PAnchorBinding] = true } - if _, ok := set[r.OBinding]; !ok { - res = append(res, r.OBinding) - set[r.OBinding] = true + if _, ok := set[p.OBinding]; !ok { + res = append(res, p.OBinding) + set[p.OBinding] = true } - if _, ok := set[r.OAnchorBinding]; !ok { - res = append(res, r.OAnchorBinding) - set[r.OAnchorBinding] = true + if _, ok := set[p.OAnchorBinding]; !ok { + res = append(res, p.OAnchorBinding) + set[p.OAnchorBinding] = true } } } @@ -982,29 +881,29 @@ func (s *Statement) AddWorkingConstructClause() { s.ResetWorkingConstructClause() } -// ReificationClauses returns the list of reification clauses within the construct +// PredicateObjectPairs returns the list of predicate-object pairs within the construct // clause. -func (c *ConstructClause) ReificationClauses() []*ReificationClause { - return c.reificationClauses +func (c *ConstructClause) PredicateObjectPairs() []*ConstructPredicateObjectPair { + return c.predicateObjectPairs } -// ResetWorkingReificationClause resets the working reification clause in the +// ResetWorkingPredicateObjectPair resets the working predicate-object pair in the // construct clause. -func (c *ConstructClause) ResetWorkingReificationClause() { - c.workingReificationClause = &ReificationClause{} +func (c *ConstructClause) ResetWorkingPredicateObjectPair() { + c.workingPredicateObjectPair = &ConstructPredicateObjectPair{} } -// WorkingReificationClause returns the working reification clause in the +// WorkingPredicateObjectPair returns the working predicate-object pair in the // construct clause. -func (c *ConstructClause) WorkingReificationClause() *ReificationClause { - return c.workingReificationClause +func (c *ConstructClause) WorkingPredicateObjectPair() *ConstructPredicateObjectPair { + return c.workingPredicateObjectPair } -// AddWorkingReificationClause adds the working reification clause to the set -// of reification clauses belonging to the construct clause. -func (c *ConstructClause) AddWorkingReificationClause() { - if c.workingReificationClause != nil && !c.workingReificationClause.IsEmpty() { - c.reificationClauses = append(c.reificationClauses, c.workingReificationClause) +// AddWorkingPredicateObjectPair adds the working predicate-object pair to the set +// of predicate-object pairs belonging to the construct clause. +func (c *ConstructClause) AddWorkingPredicateObjectPair() { + if c.workingPredicateObjectPair != nil && !c.workingPredicateObjectPair.IsEmpty() { + c.predicateObjectPairs = append(c.predicateObjectPairs, c.workingPredicateObjectPair) } - c.ResetWorkingReificationClause() + c.ResetWorkingPredicateObjectPair() } diff --git a/bql/semantic/semantic_test.go b/bql/semantic/semantic_test.go index b591e5ed..b1937099 100644 --- a/bql/semantic/semantic_test.go +++ b/bql/semantic/semantic_test.go @@ -223,20 +223,20 @@ func TestConstructClauseManipulation(t *testing.T) { } } -func TestReificationClauseManipulation(t *testing.T) { +func TestConstructPredicateObjectPairsManipulation(t *testing.T) { st := &Statement{} st.ResetWorkingConstructClause() wcc := st.WorkingConstructClause() - if wcc.WorkingReificationClause() != nil { - t.Fatalf("semantic.ConstructClause.WorkingReificationClause should not return a working reification clause without initialization in %v", st) + if wcc.WorkingPredicateObjectPair() != nil { + t.Fatalf("semantic.ConstructClause.WorkingPredicateObjectPair should not return a working predicate-object pair without initialization in %v", st) } - wcc.ResetWorkingReificationClause() - if wcc.WorkingReificationClause() == nil { - t.Fatalf("semantic.ConstructClause.WorkingReificationClause should return a working reification clause after initialization in %v", st) + wcc.ResetWorkingPredicateObjectPair() + if wcc.WorkingPredicateObjectPair() == nil { + t.Fatalf("semantic.ConstructClause.WorkingPredicateObjectPair should return a working predicate-object pair after initialization in %v", st) } - wcc.AddWorkingReificationClause() - if got, want := len(wcc.ReificationClauses()), 0; got != want { - t.Fatalf("semantic.ConstructClause.WorkingReificationClauses returns wrong number of clauses in %v; got %d, want %d", st, got, want) + wcc.AddWorkingPredicateObjectPair() + if got, want := len(wcc.PredicateObjectPairs()), 0; got != want { + t.Fatalf("semantic.ConstructClause.PredicateObjectPairs returns wrong number of predicate-object pairs in %v; got %d, want %d", st, got, want) } } @@ -254,14 +254,20 @@ func TestInputOutputBindings(t *testing.T) { constructClauses: []*ConstructClause{ { SBinding: "?foo1", - PBinding: "?foo2", - OBinding: "?foo3", + predicateObjectPairs: []*ConstructPredicateObjectPair{ + { + PBinding: "?foo2", + OBinding: "?foo3", + }, + }, }, { SBinding: "?foo4", - PBinding: "?foo5", - OBinding: "?foo6", - reificationClauses: []*ReificationClause{ + predicateObjectPairs: []*ConstructPredicateObjectPair{ + { + PBinding: "?foo5", + OBinding: "?foo6", + }, { PBinding: "?foo7", OBinding: "?foo8", @@ -273,9 +279,11 @@ func TestInputOutputBindings(t *testing.T) { }, }, { - PAnchorBinding: "?foo11", - OAnchorBinding: "?foo12", - reificationClauses: []*ReificationClause{ + predicateObjectPairs: []*ConstructPredicateObjectPair{ + { + PAnchorBinding: "?foo11", + OAnchorBinding: "?foo12", + }, { PAnchorBinding: "?foo13", OAnchorBinding: "?foo13", diff --git a/triple/triple.go b/triple/triple.go index 45fe72df..e4eb502e 100644 --- a/triple/triple.go +++ b/triple/triple.go @@ -206,7 +206,7 @@ func Parse(line string, b literal.Builder) (*Triple, error) { // Reify given the current triple it returns the original triple and the newly // reified ones. It also returns the newly created blank node. func (t *Triple) Reify() ([]*Triple, *node.Node, error) { - // Function that create the proper reification predicates. + // Function that creates the proper reification predicates. rp := func(id string, p *predicate.Predicate) (*predicate.Predicate, error) { if p.Type() == predicate.Temporal { ta, _ := p.TimeAnchor() diff --git a/triple/triple_test.go b/triple/triple_test.go index f9527903..0e690c74 100644 --- a/triple/triple_test.go +++ b/triple/triple_test.go @@ -95,7 +95,7 @@ func TestReifyImmutable(t *testing.T) { for _, trpl := range rts[1:] { ps := string(trpl.Predicate().ID()) if ps != "_subject" && ps != "_predicate" && ps != "_object" { - t.Errorf("Inalid reification predicate; found %q", ps) + t.Errorf("Invalid reification predicate; found %q", ps) } } } @@ -115,7 +115,7 @@ func TestReifyTemporal(t *testing.T) { for _, trpl := range rts[1:] { ps := string(trpl.Predicate().ID()) if ps != "_subject" && ps != "_predicate" && ps != "_object" { - t.Errorf("Inalid reification predicate; found %q", ps) + t.Errorf("Invalid reification predicate; found %q", ps) } } }