From 1a3352e22a0925adf7e75761604a460027de047b Mon Sep 17 00:00:00 2001 From: Aravind Rao Date: Wed, 24 May 2017 00:20:25 -0700 Subject: [PATCH 1/2] Initial commit, definitions of constructSubjectClause, constructPredicateClause and constructObjectClause --- bql/grammar/grammar.go | 9 ++- bql/semantic/convert.go | 2 +- bql/semantic/hooks.go | 161 ++++++++++++++++++++++++++++++++++----- bql/semantic/semantic.go | 18 +++-- triple/node/node.go | 12 +-- triple/triple.go | 1 - 6 files changed, 169 insertions(+), 34 deletions(-) diff --git a/bql/grammar/grammar.go b/bql/grammar/grammar.go index b73d25c0..2f3e968a 100644 --- a/bql/grammar/grammar.go +++ b/bql/grammar/grammar.go @@ -802,6 +802,11 @@ func BQL() *Grammar { NewTokenType(lexer.ItemNode), }, }, + { + Elements: []Element{ + NewTokenType(lexer.ItemBlankNode), + }, + }, { Elements: []Element{ NewTokenType(lexer.ItemPredicate), @@ -957,7 +962,9 @@ func SemanticBQL() *Grammar { setClauseHook(semanticBQL, []semantic.Symbol{"CONSTRUCT_FACTS"}, semantic.InitWorkingConstructClauseHook(), nil) constructTriplesSymbols := []semantic.Symbol{"CONSTRUCT_TRIPLES", "MORE_CONSTRUCT_TRIPLES"} setClauseHook(semanticBQL, constructTriplesSymbols, semantic.NextWorkingConstructClauseHook(), semantic.NextWorkingConstructClauseHook()) - + 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) return semanticBQL } diff --git a/bql/semantic/convert.go b/bql/semantic/convert.go index d9029168..af62fc54 100644 --- a/bql/semantic/convert.go +++ b/bql/semantic/convert.go @@ -76,7 +76,7 @@ func ToNode(ce ConsumedElement) (*node.Node, error) { return nil, fmt.Errorf("semantic.ToNode cannot convert symbol %v to a node", ce) } tkn := ce.Token() - if tkn.Type != lexer.ItemNode { + if tkn.Type != lexer.ItemNode && tkn.Type != lexer.ItemBlankNode { return nil, fmt.Errorf("semantic.ToNode cannot convert token type %s to a node", tkn.Type) } return node.Parse(tkn.Text) diff --git a/bql/semantic/hooks.go b/bql/semantic/hooks.go index 254b5276..2e1bf213 100644 --- a/bql/semantic/hooks.go +++ b/bql/semantic/hooks.go @@ -150,6 +150,24 @@ func NextWorkingConstructClauseHook() ClauseHook { return NextWorkingConstructClause() } +// ConstructSubjectClauseHook returns the singleton for populating the subject in the +// working construct clause. +func ConstructSubjectClauseHook() ElementHook { + return constructSubjectClause() +} + +// 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() +} + // TypeBindingClauseHook returns a ClauseHook that sets the binding type. func TypeBindingClauseHook(t StatementType) ClauseHook { var f ClauseHook @@ -298,7 +316,7 @@ func whereSubjectClause() ElementHook { } if lastNopToken.Type == lexer.ItemAs { if c.SAlias != "" { - return nil, fmt.Errorf("AS alias binding for subject has already being assined on %v", st) + return nil, fmt.Errorf("AS alias binding for subject has already being assigned on %v", st) } c.SAlias = tkn.Text lastNopToken = nil @@ -306,7 +324,7 @@ func whereSubjectClause() ElementHook { } if lastNopToken.Type == lexer.ItemType { if c.STypeAlias != "" { - return nil, fmt.Errorf("TYPE alias binding for subject has already being assined on %v", st) + return nil, fmt.Errorf("TYPE alias binding for subject has already being assigned on %v", st) } c.STypeAlias = tkn.Text lastNopToken = nil @@ -314,7 +332,7 @@ func whereSubjectClause() ElementHook { } if c.SIDAlias == "" && lastNopToken.Type == lexer.ItemID { if c.SIDAlias != "" { - return nil, fmt.Errorf("ID alias binding for subject has already being assined on %v", st) + return nil, fmt.Errorf("ID alias binding for subject has already being assigned on %v", st) } c.SIDAlias = tkn.Text lastNopToken = nil @@ -327,9 +345,8 @@ func whereSubjectClause() ElementHook { return f } -// processPredicate updates the working graph clause if there is an available -// predicate. -func processPredicate(c *GraphClause, ce ConsumedElement, lastNopToken *lexer.Token) (*predicate.Predicate, string, string, bool, error) { +// processPredicate parses a consumed element and returns a predicate and its attributes if possible. +func processPredicate(ce ConsumedElement) (*predicate.Predicate, string, string, bool, error) { var ( nP *predicate.Predicate pID string @@ -357,9 +374,8 @@ func processPredicate(c *GraphClause, ce ConsumedElement, lastNopToken *lexer.To return nil, pID, pAnchorBinding, temporal, nil } -// processPredicateBound updates the working graph clause if there is an -// available predicate bound. -func processPredicateBound(c *GraphClause, ce ConsumedElement, lastNopToken *lexer.Token) (string, string, string, *time.Time, *time.Time, bool, error) { +// processPredicate parses a consumed element and returns a bound predicate and its attributes if possible. +func processPredicateBound(ce ConsumedElement) (string, string, string, *time.Time, *time.Time, bool, error) { var ( pID string pLowerBoundAlias string @@ -428,7 +444,7 @@ func wherePredicateClause() ElementHook { if c.P != nil { return nil, fmt.Errorf("invalid predicate %s on graph clause since already set to %s", tkn.Text, c.P) } - p, pID, pAnchorBinding, pTemporal, err := processPredicate(c, ce, lastNopToken) + p, pID, pAnchorBinding, pTemporal, err := processPredicate(ce) if err != nil { return nil, err } @@ -439,7 +455,7 @@ func wherePredicateClause() ElementHook { if c.PLowerBound != nil || c.PUpperBound != nil || c.PLowerBoundAlias != "" || c.PUpperBoundAlias != "" { return nil, fmt.Errorf("invalid predicate bound %s on graph clause since already set to %s", tkn.Text, c.P) } - pID, pLowerBoundAlias, pUpperBoundAlias, pLowerBound, pUpperBound, pTemp, err := processPredicateBound(c, ce, lastNopToken) + pID, pLowerBoundAlias, pUpperBoundAlias, pLowerBound, pUpperBound, pTemp, err := processPredicateBound(ce) if err != nil { return nil, err } @@ -456,17 +472,17 @@ func wherePredicateClause() ElementHook { switch lastNopToken.Type { case lexer.ItemAs: if c.PAlias != "" { - return nil, fmt.Errorf("AS alias binding for predicate has already being assined on %v", st) + return nil, fmt.Errorf("AS alias binding for predicate has already being assigned on %v", st) } c.PAlias = tkn.Text case lexer.ItemID: if c.PIDAlias != "" { - return nil, fmt.Errorf("ID alias binding for predicate has already being assined on %v", st) + return nil, fmt.Errorf("ID alias binding for predicate has already being assigned on %v", st) } c.PIDAlias = tkn.Text case lexer.ItemAt: if c.PAnchorAlias != "" { - return nil, fmt.Errorf("AT alias binding for predicate has already being assined on %v", st) + return nil, fmt.Errorf("AT alias binding for predicate has already being assigned on %v", st) } c.PAnchorAlias = tkn.Text default: @@ -515,7 +531,7 @@ func whereObjectClause() ElementHook { pred *predicate.Predicate err error ) - pred, c.OID, c.OAnchorBinding, c.OTemporal, err = processPredicate(c, ce, lastNopToken) + pred, c.OID, c.OAnchorBinding, c.OTemporal, err = processPredicate(ce) if err != nil { return nil, err } @@ -528,7 +544,7 @@ func whereObjectClause() ElementHook { if c.OLowerBound != nil || c.OUpperBound != nil || c.OLowerBoundAlias != "" || c.OUpperBoundAlias != "" { return nil, fmt.Errorf("invalid predicate bound %s on graph clause since already set to %s", tkn.Text, c.O) } - oID, oLowerBoundAlias, oUpperBoundAlias, oLowerBound, oUpperBound, oTemp, err := processPredicateBound(c, ce, lastNopToken) + oID, oLowerBoundAlias, oUpperBoundAlias, oLowerBound, oUpperBound, oTemp, err := processPredicateBound(ce) if err != nil { return nil, err } @@ -548,22 +564,22 @@ func whereObjectClause() ElementHook { switch lastNopToken.Type { case lexer.ItemAs: if c.OAlias != "" { - return nil, fmt.Errorf("AS alias binding for predicate has already being assined on %v", st) + return nil, fmt.Errorf("AS alias binding for predicate has already being assigned on %v", st) } c.OAlias = tkn.Text case lexer.ItemType: if c.OTypeAlias != "" { - return nil, fmt.Errorf("TYPE alias binding for predicate has already being assined on %v", st) + return nil, fmt.Errorf("TYPE alias binding for predicate has already being assigned on %v", st) } c.OTypeAlias = tkn.Text case lexer.ItemID: if c.OIDAlias != "" { - return nil, fmt.Errorf("ID alias binding for predicate has already being assined on %v", st) + return nil, fmt.Errorf("ID alias binding for predicate has already being assigned on %v", st) } c.OIDAlias = tkn.Text case lexer.ItemAt: if c.OAnchorAlias != "" { - return nil, fmt.Errorf("AT alias binding for predicate has already being assined on %v", st) + return nil, fmt.Errorf("AT alias binding for predicate has already being assigned on %v", st) } c.OAnchorAlias = tkn.Text default: @@ -895,3 +911,108 @@ func NextWorkingConstructClause() ClauseHook { } return f } + +// constructSubjectClause returns an element hook that updates the subject +// modifiers on the working construct clause. +func constructSubjectClause() 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.S != nil { + return nil, fmt.Errorf("invalid subject %v in construct clause, subject already set to %v", tkn.Type, c.S) + } + if c.SBinding != "" { + return nil, fmt.Errorf("invalid subject %v in construct clause, subject already set to %v", tkn.Type, c.SBinding) + } + switch tkn.Type { + case lexer.ItemNode, lexer.ItemBlankNode: + n, err := ToNode(ce) + if err != nil { + return nil, err + } + c.S = n + case lexer.ItemBinding: + c.SBinding = tkn.Text + } + return f, nil + } + return f +} + +// constructPredicateClause returns an element hook that updates the predicate +// modifiers on the working construct clause. +func constructPredicateClause() 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) + } + 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 +} + +// constructObjectClause returns an element hook that updates the object +// modifiers on the working graph clause. +func constructObjectClause() ElementHook { + var f ElementHook + f = func(st *Statement, ce ConsumedElement) (ElementHook, error) { + if ce.IsSymbol() { + return f, nil + } + tkn := ce.Token() + c := st.WorkingClause() + 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.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 + } + return f, nil + } + return f +} diff --git a/bql/semantic/semantic.go b/bql/semantic/semantic.go index c160e318..f8634677 100644 --- a/bql/semantic/semantic.go +++ b/bql/semantic/semantic.go @@ -135,11 +135,15 @@ type ConstructClause struct { P *predicate.Predicate PBinding string + PID string PAnchorBinding string PTemporal bool - O *triple.Object - OBinding string + O *triple.Object + OBinding string + OID string + OAnchorBinding string + OTemporal bool ReificationClauses []*ReificationClause } @@ -148,11 +152,15 @@ type ConstructClause struct { type ReificationClause struct { P *predicate.Predicate PBinding string + PID string PAnchorBinding string PTemporal bool - O *triple.Object - OBinding string + O *triple.Object + OBinding string + OID string + OAnchorBinding string + OTemporal bool } // String returns a readable representation of a graph clause. @@ -385,7 +393,7 @@ func (s *Statement) Type() StatementType { return s.sType } -// AddGraph adds a graph to a given https://critique.corp.google.com/#review/101398527statement. +// AddGraph adds a graph to a given statement. func (s *Statement) AddGraph(g string) { s.graphNames = append(s.graphNames, g) } diff --git a/triple/node/node.go b/triple/node/node.go index 90959f5f..e8fcd0b5 100644 --- a/triple/node/node.go +++ b/triple/node/node.go @@ -86,29 +86,29 @@ func Parse(s string) (*Node, error) { case slash: idx := strings.Index(raw, "<") if idx < 0 { - return nil, fmt.Errorf("node.Parser: invalid format, could not find ID in %v", raw) + return nil, fmt.Errorf("node.Parse: invalid format, could not find ID in %v", raw) } t, err := NewType(raw[:idx]) if err != nil { - return nil, fmt.Errorf("node.Parser: invalid type %q, %v", raw[:idx], err) + return nil, fmt.Errorf("node.Parse: invalid type %q, %v", raw[:idx], err) } if raw[len(raw)-1] != '>' { - return nil, fmt.Errorf("node.Parser: pretty printing should finish with '>' in %q", raw) + return nil, fmt.Errorf("node.Parse: pretty printing should finish with '>' in %q", raw) } id, err := NewID(raw[idx+1 : len(raw)-1]) if err != nil { - return nil, fmt.Errorf("node.Parser: invalid ID in %q, %v", raw, err) + return nil, fmt.Errorf("node.Parse: invalid ID in %q, %v", raw, err) } return NewNode(t, id), nil case underscore: id, err := NewID(raw[2:len(raw)]) if err != nil { - return nil, fmt.Errorf("node.Parser: invalid ID in %q, %v", raw, err) + return nil, fmt.Errorf("node.Parse: invalid ID in %q, %v", raw, err) } t, _ := NewType("/_") return NewNode(t, id), nil default: - return nil, fmt.Errorf("node.Parser: node representation should start with '/' or '_' in %v", raw) + return nil, fmt.Errorf("node.Parse: node representation should start with '/' or '_' in %v", raw) } } diff --git a/triple/triple.go b/triple/triple.go index 79754127..45fe72df 100644 --- a/triple/triple.go +++ b/triple/triple.go @@ -98,7 +98,6 @@ func ParseObject(s string, b literal.Builder) (*Object, error) { } o, err := predicate.Parse(s) if err == nil { - return NewPredicateObject(o), nil } return nil, err From a8c69c2f5703fedef705c9ccc3ded1f8627996f7 Mon Sep 17 00:00:00 2001 From: Aravind Rao Date: Thu, 25 May 2017 12:14:23 -0700 Subject: [PATCH 2/2] Added tests for spo hooks. --- bql/semantic/hooks.go | 12 +- bql/semantic/hooks_test.go | 357 ++++++++++++++++++++++++++++++++++++- 2 files changed, 364 insertions(+), 5 deletions(-) diff --git a/bql/semantic/hooks.go b/bql/semantic/hooks.go index 2e1bf213..f6892147 100644 --- a/bql/semantic/hooks.go +++ b/bql/semantic/hooks.go @@ -921,7 +921,7 @@ func constructSubjectClause() ElementHook { return f, nil } tkn := ce.Token() - c := st.workingConstructClause + c := st.WorkingConstructClause() if c.S != nil { return nil, fmt.Errorf("invalid subject %v in construct clause, subject already set to %v", tkn.Type, c.S) } @@ -952,10 +952,13 @@ func constructPredicateClause() ElementHook { return f, nil } tkn := ce.Token() - c := st.workingConstructClause + 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) } + 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) } @@ -983,10 +986,13 @@ func constructObjectClause() ElementHook { return f, nil } tkn := ce.Token() - c := st.WorkingClause() + 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) } + 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) } diff --git a/bql/semantic/hooks_test.go b/bql/semantic/hooks_test.go index 304536cc..2f72ce73 100644 --- a/bql/semantic/hooks_test.go +++ b/bql/semantic/hooks_test.go @@ -188,7 +188,7 @@ func runTabulatedClauseHookTest(t *testing.T, testName string, f ElementHook, ta } if entry.valid { if got, want := st.WorkingClause(), entry.want; !reflect.DeepEqual(got, want) { - t.Errorf("%s case %q should have populated all subject fields; got %+v, want %+v", testName, entry.id, got, want) + t.Errorf("%s case %q should have populated all required fields; got %+v, want %+v", testName, entry.id, got, want) } } else { if !failed { @@ -588,7 +588,7 @@ func TestWhereObjectClauseHook(t *testing.T) { } l, err := triple.ParseObject(`"1"^^type:int64`, literal.DefaultBuilder()) if err != nil { - t.Fatalf("literal.Parse should have never fail to pars %s with error %v", `"1"^^type:int64`, err) + t.Fatalf("literal.Parse should never fail to parse %s with error %v", `"1"^^type:int64`, err) } runTabulatedClauseHookTest(t, "semantic.whereObjectClause", f, []testClauseTable{ @@ -1942,3 +1942,356 @@ func TestNextWorkingConstructClauseHook(t *testing.T) { t.Errorf("semantic.NextConstructWorkingClause should have returned two clauses for statement %v; got %d, want %d", st, got, want) } } + +type testConstructClauseTable struct { + valid bool + id string + ces []ConsumedElement + want *ConstructClause +} + +func runTabulatedConstructClauseHookTest(t *testing.T, testName string, f ElementHook, table []testConstructClauseTable) { + st := &Statement{} + st.ResetWorkingConstructClause() + failed := false + for _, entry := range table { + for _, ce := range entry.ces { + if _, err := f(st, ce); err != nil { + if entry.valid { + t.Errorf("%s case %q should have never failed with error: %v", testName, entry.id, err) + } else { + failed = true + } + } + } + if entry.valid { + if got, want := st.WorkingConstructClause(), 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 { + if !failed { + t.Errorf("%s failed to reject invalid case %q", testName, entry.id) + } + } + st.ResetWorkingConstructClause() + } +} + +func TestConstructSubjectClauseHook(t *testing.T) { + st := &Statement{} + f := constructSubjectClause() + st.ResetWorkingConstructClause() + n, err := node.Parse("/_") + if err != nil { + t.Fatalf("node.Parse called for '/_' failed with error %v", err) + } + bn, err := node.Parse("_:v1") + if err != nil { + t.Fatalf("node.Parse called for '_:v1' failed with error %v", err) + } + runTabulatedConstructClauseHookTest(t, "semantic.constructSubjectClause", f, []testConstructClauseTable{ + { + valid: true, + id: "valid node", + ces: []ConsumedElement{ + NewConsumedSymbol("CONSTRUCT_TRIPLES"), + NewConsumedToken(&lexer.Token{ + Type: lexer.ItemNode, + Text: "/_", + }), + }, + want: &ConstructClause{ + S: n, + }, + }, + { + valid: true, + id: "valid blank node", + ces: []ConsumedElement{ + NewConsumedSymbol("CONSTRUCT_TRIPLES"), + NewConsumedToken(&lexer.Token{ + Type: lexer.ItemBlankNode, + Text: "_:v1", + }), + }, + want: &ConstructClause{ + S: bn, + }, + }, + { + valid: true, + id: "valid binding", + ces: []ConsumedElement{ + NewConsumedSymbol("CONSTRUCT_TRIPLES"), + NewConsumedToken(&lexer.Token{ + Type: lexer.ItemBinding, + Text: "?foo", + }), + }, + want: &ConstructClause{ + SBinding: "?foo", + }, + }, + { + valid: false, + id: "invalid node and binding", + ces: []ConsumedElement{ + NewConsumedSymbol("CONSTRUCT_TRIPLES"), + NewConsumedToken(&lexer.Token{ + Type: lexer.ItemBlankNode, + Text: "_:v1", + }), + NewConsumedSymbol("FOO"), + NewConsumedToken(&lexer.Token{ + Type: lexer.ItemBinding, + Text: "?foo", + }), + }, + want: &ConstructClause{}, + }, + }) +} + +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{}, + }, + }) +}