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