// Copyright ©2022 The Gonum Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package rdf_test import ( "fmt" "io" "log" "os" "strings" "gonum.org/v1/gonum/graph/formats/rdf" ) func ExampleGraph() { f, err := os.Open("path/to/graph.nq") if err != nil { log.Fatal(err) } dec := rdf.NewDecoder(f) var statements []*rdf.Statement for { s, err := dec.Unmarshal() if err != nil { if err != io.EOF { log.Fatalf("error during decoding: %v", err) } break } // Statements can be filtered at this point to exclude unwanted // or irrelevant parts of the graph. statements = append(statements, s) } f.Close() // Canonicalize blank nodes to reduce memory footprint. statements, err = rdf.URDNA2015(statements, statements) if err != nil { log.Fatal(err) } g := rdf.NewGraph() for _, s := range statements { g.AddStatement(s) } // Do something with the graph. } const gods = ` _:alcmene "human" . _:alcmene "Alcmene" . _:cerberus _:cerberushome . _:cerberus "monster" . _:cerberus "Cerberus" . _:cerberushome _:tartarus . _:cronos "titan" . _:cronos "Cronos" . _:hades _:hadeshome . _:hades _:poseidon . _:hades _:zeus . _:hades _:cerberus . _:hades "god" . _:hades "Hades" . _:hadeshome _:tartarus . _:hadeshome "it is peaceful" . _:heracles _:cerberus . _:heracles _:hydra . _:heracles _:nemean . _:heracles _:zeus . _:heracles _:alcmene . _:heracles "demigod" . _:heracles "Heracles" . _:hydra "monster" . _:hydra "Lernean Hydra" . _:nemean "monster" . _:nemean "Nemean Lion" . _:olympus "location" . _:olympus "Olympus" . _:poseidon _:poseidonhome . _:poseidon _:hades . _:poseidon _:zeus . _:poseidon "god" . _:poseidon "Poseidon" . _:poseidonhome _:sea . _:poseidonhome "it was given to him" . _:sea "location" . _:sea "Sea" . _:tartarus "location" . _:tartarus "Tartarus" . _:theseus _:cerberus . _:theseus _:poseidon . _:theseus "human" . _:theseus "Theseus" . _:zeus _:zeushome . _:zeus _:hades . _:zeus _:poseidon . _:zeus _:cronos . _:zeus "god" . _:zeus "Zeus" . _:zeushome _:olympus . _:zeushome "he can see everything" . ` func ExampleQuery() { g := rdf.NewGraph() dec := rdf.NewDecoder(strings.NewReader(gods)) for { s, err := dec.Unmarshal() if err != nil { if err != io.EOF { log.Fatalf("error during decoding: %v", err) } break } g.AddStatement(s) } it := g.Nodes() nodes := make([]rdf.Term, 0, it.Len()) for it.Next() { nodes = append(nodes, it.Node().(rdf.Term)) } // Construct a query start point. This can be reused. If a specific // node is already known it can be used to reduce the work required here. heracles := g.Query(nodes...).In(func(s *rdf.Statement) bool { // Traverse in from the name "Heracles". return s.Predicate.Value == "" && s.Object.Value == `"Heracles"` }) // father and name filter statements on their predicate values. These // are used in the queries that follow. father := func(s *rdf.Statement) bool { // Traverse across . return s.Predicate.Value == "" } name := func(s *rdf.Statement) bool { // Traverse across . return s.Predicate.Value == "" } // g.V().has('name', 'heracles').out('father').out('father').values('name') for _, r := range heracles. Out(father). // Traverse out across to get to Zeus. Out(father). // and again to get to Cronos. Out(name). // Retrieve the name by traversing the edges. Result() { fmt.Printf("Heracles' grandfather: %s\n", r.Value) } // g.V().has('name', 'heracles').repeat(out('father')).emit().values('name') var i int heracles.Repeat(func(q rdf.Query) (rdf.Query, bool) { q = q.Out(father) for _, r := range q.Out(name).Result() { fmt.Printf("Heracles' lineage %d: %s\n", i, r.Value) } i++ return q, true }) // parents and typ are helper filters for queries below. parents := func(s *rdf.Statement) bool { // Traverse across or return s.Predicate.Value == "" || s.Predicate.Value == "" } typ := func(s *rdf.Statement) bool { // Traverse across . return s.Predicate.Value == "" } // g.V(heracles).out('father', 'mother').label() for _, r := range heracles.Out(parents).Out(typ).Result() { fmt.Printf("Heracles' parents' types: %s\n", r.Value) } // battled is a helper filter for queries below. battled := func(s *rdf.Statement) bool { // Traverse across . return s.Predicate.Value == "" } // g.V(heracles).out('battled').label() for _, r := range heracles.Out(battled).Out(typ).Result() { fmt.Printf("Heracles' antagonists' types: %s\n", r.Value) } // g.V(heracles).out('battled').valueMap() for _, r := range heracles.Out(battled).Result() { m := make(map[string]string) g.Query(r).Out(func(s *rdf.Statement) bool { // Store any p: namespace in the map. if strings.HasPrefix(s.Predicate.Value, "") m[prop] = s.Object.Value } // But don't store the result into the query. return false }) fmt.Println(m) } // g.V(heracles).as('h').out('battled').in('battled').where(neq('h')).values('name') for _, r := range heracles.Out(battled).In(battled).Not(heracles).Out(name).Result() { fmt.Printf("Heracles' allies: %s\n", r.Value) } // Construct a query start point for Hades, this time using a restricted // starting set only including the name. It would also be possible to // start directly from a query with the term _:hades, but that depends // on the blank node identity, which may be altered, for example by // canonicalization. h, ok := g.TermFor(`"Hades"`) if !ok { log.Fatal("could not find term for Hades") } hades := g.Query(h).In(name) // g.V(hades).as('x').out('lives').in('lives').where(neq('x')).values('name') // // This is more complex with RDF since properties are encoded by // attachment to anonymous blank nodes, so we take two steps, the // first to the blank node for where Hades lives and then the second // to get the actual location. lives := func(s *rdf.Statement) bool { // Traverse across . return s.Predicate.Value == "" } location := func(s *rdf.Statement) bool { // Traverse across . return s.Predicate.Value == "" } for _, r := range hades.Out(lives).Out(location).In(location).In(lives).Not(hades).Out(name).Result() { fmt.Printf("Hades lives with: %s\n", r.Value) } // g.V(hades).out('brother').as('god').out('lives').as('place').select('god', 'place').by('name') brother := func(s *rdf.Statement) bool { // Traverse across . return s.Predicate.Value == "" } for _, r := range hades.Out(brother).Result() { m := make(map[string]string) as := func(key string) func(s *rdf.Statement) bool { return func(s *rdf.Statement) bool { // Store any objects in the map. if s.Predicate.Value == "" { m[key] = s.Object.Value } // But don't store the result into the query. return false } } sub := g.Query(r) sub.Out(as("god")) sub.Out(lives).Out(location).Out(as("place")) fmt.Println(m) } // The query above but with the reason for their choice. for _, r := range hades.Out(brother).Result() { m := make(map[string]string) // as stores the query result under the provided key // for m, and if cont is not nil, allows the chain // to continue. as := func(query, key string, cont func(s *rdf.Statement) bool) func(s *rdf.Statement) bool { return func(s *rdf.Statement) bool { // Store any objects matching the query in the map. if s.Predicate.Value == query { m[key] = s.Object.Value } // Continue with chain if cont is not nil and // the statement satisfies its condition. if cont == nil { return false } return cont(s) } } sub := g.Query(r) sub.Out(as("", "god", nil)) sub.Out(lives). Out(as("", "reason", location)). Out(as("", "place", nil)) fmt.Println(m) } // Unordered output: // // Heracles' grandfather: "Cronos" // Heracles' lineage 0: "Zeus" // Heracles' lineage 1: "Cronos" // Heracles' parents' types: "god" // Heracles' parents' types: "human" // Heracles' antagonists' types: "monster" // Heracles' antagonists' types: "monster" // Heracles' antagonists' types: "monster" // map[name:"Cerberus"] // map[name:"Lernean Hydra"] // map[name:"Nemean Lion"] // Heracles' allies: "Theseus" // Hades lives with: "Cerberus" // map[god:"Zeus" place:"Olympus"] // map[god:"Poseidon" place:"Sea"] // map[god:"Zeus" place:"Olympus" reason:"he can see everything"] // map[god:"Poseidon" place:"Sea" reason:"it was given to him"] }