Merge branch 'master' into benchmarks

This commit is contained in:
kortschak 2014-07-31 08:35:34 +09:30
commit 1a0dd13735
77 changed files with 14272 additions and 1476 deletions

View file

@ -1,6 +1,6 @@
{ {
"Arch": "amd64 386", "Arch": "amd64 386",
"Os": "linux darwin windows", "Os": "linux darwin windows",
"ResourcesInclude": "README.md,static,templates,LICENSE,AUTHORS,CONTRIBUTORS,docs,cayley.cfg.example,30kmoviedata.nt.gz,testdata.nt", "ResourcesInclude": "README.md,static,templates,LICENSE,AUTHORS,CONTRIBUTORS,docs,cayley.cfg.example,30kmoviedata.nq.gz,testdata.nq",
"ConfigVersion": "0.9" "ConfigVersion": "0.9"
} }

BIN
30kmoviedata.nq.gz Normal file

Binary file not shown.

Binary file not shown.

View file

@ -72,13 +72,13 @@ cayley> graph.Vertex("dani").Out("follows").All()
For somewhat more interesting data, a sample of 30k movies from Freebase comes in the checkout. For somewhat more interesting data, a sample of 30k movies from Freebase comes in the checkout.
``` ```
./cayley repl --dbpath=30kmoviedata.nt.gz ./cayley repl --dbpath=30kmoviedata.nq.gz
``` ```
To run the web frontend, replace the "repl" command with "http" To run the web frontend, replace the "repl" command with "http"
``` ```
./cayley http --dbpath=30kmoviedata.nt.gz ./cayley http --dbpath=30kmoviedata.nq.gz
``` ```
And visit port 64210 on your machine, commonly [http://localhost:64210](http://localhost:64210) And visit port 64210 on your machine, commonly [http://localhost:64210](http://localhost:64210)
@ -90,13 +90,13 @@ The default environment is based on [Gremlin](http://gremlindocs.com/) and is si
You'll notice we have a special object, `graph` or `g`, which is how you can interact with the graph. You'll notice we have a special object, `graph` or `g`, which is how you can interact with the graph.
The simplest query is merely to return a single vertex. Using the 30kmovies.nt dataset from above, let's walk through some simple queries: The simplest query is merely to return a single vertex. Using the 30kmoviedata.nq dataset from above, let's walk through some simple queries:
```javascript ```javascript
// Query all vertices in the graph, limit to the first 5 vertices found. // Query all vertices in the graph, limit to the first 5 vertices found.
graph.Vertex().GetLimit(5) graph.Vertex().GetLimit(5)
// Start with only one vertex, the literal name "Humphrey Bogart", and retreive all of them. // Start with only one vertex, the literal name "Humphrey Bogart", and retrieve all of them.
graph.Vertex("Humphrey Bogart").All() graph.Vertex("Humphrey Bogart").All()
// `g` and `V` are synonyms for `graph` and `Vertex` respectively, as they are quite common. // `g` and `V` are synonyms for `graph` and `Vertex` respectively, as they are quite common.

View file

@ -26,7 +26,7 @@ Usually something that should be taken care of.
### Bootstraps ### Bootstraps
Start discussing bootstrap triples, things that make the database self-describing, if they exist (though they need not). Talk about sameAs and indexing and type systems and whatnot. Start discussing bootstrap triples, things that make the database self-describing, if they exist (though they need not). Talk about sameAs and indexing and type systems and whatnot.
### Better surfacing of Provenance ### Better surfacing of Label
It exists, it's indexed, but it's basically useless right now It exists, it's indexed, but it's basically useless right now
### Optimize HasA Iterator ### Optimize HasA Iterator
@ -39,7 +39,7 @@ A simple example is just to convert the HasA to a fixed (next them out) if the s
A way to limit the number of subresults at a point, without even running the query. Essentially, much as GetLimit() does for the end, be able to do the same in between A way to limit the number of subresults at a point, without even running the query. Essentially, much as GetLimit() does for the end, be able to do the same in between
#### "Up" and "Down" traversals #### "Up" and "Down" traversals
Getting to the predicates from a node, or the nodes from a predicate, or some odd combinations thereof. Ditto for provenance. Getting to the predicates from a node, or the nodes from a predicate, or some odd combinations thereof. Ditto for label.
#### Value comparison #### Value comparison
Expose the value-comparison iterator in the language Expose the value-comparison iterator in the language

View file

@ -1,6 +1,6 @@
{ {
"database": "mem", "database": "mem",
"db_path": "30k.nt", "db_path": "30kmoviedata.nq.gz",
"read_only": true, "read_only": true,
"load_size": 10000, "load_size": 10000,
"gremlin_timeout": 10 "gremlin_timeout": 10

View file

@ -25,7 +25,8 @@ import (
"github.com/barakmich/glog" "github.com/barakmich/glog"
"github.com/google/cayley/config" "github.com/google/cayley/config"
"github.com/google/cayley/graph" "github.com/google/cayley/graph"
"github.com/google/cayley/nquads" "github.com/google/cayley/quad"
"github.com/google/cayley/quad/cquads"
) )
func Load(ts graph.TripleStore, cfg *config.Config, path string) error { func Load(ts graph.TripleStore, cfg *config.Config, path string) error {
@ -40,7 +41,7 @@ func Load(ts graph.TripleStore, cfg *config.Config, path string) error {
glog.Fatalln(err) glog.Fatalln(err)
} }
dec := nquads.NewDecoder(r) dec := cquads.NewDecoder(r)
bulker, canBulk := ts.(graph.BulkLoader) bulker, canBulk := ts.(graph.BulkLoader)
if canBulk { if canBulk {
@ -56,7 +57,7 @@ func Load(ts graph.TripleStore, cfg *config.Config, path string) error {
return err return err
} }
block := make([]*graph.Triple, 0, cfg.LoadSize) block := make([]*quad.Quad, 0, cfg.LoadSize)
for { for {
t, err := dec.Unmarshal() t, err := dec.Unmarshal()
if err != nil { if err != nil {

View file

@ -26,7 +26,7 @@ import (
"github.com/google/cayley/config" "github.com/google/cayley/config"
"github.com/google/cayley/graph" "github.com/google/cayley/graph"
"github.com/google/cayley/graph/sexp" "github.com/google/cayley/graph/sexp"
"github.com/google/cayley/nquads" "github.com/google/cayley/quad/cquads"
"github.com/google/cayley/query/gremlin" "github.com/google/cayley/query/gremlin"
"github.com/google/cayley/query/mql" "github.com/google/cayley/query/mql"
) )
@ -99,6 +99,11 @@ func Repl(ts graph.TripleStore, queryLanguage string, cfg *config.Config) error
if len(line) == 0 { if len(line) == 0 {
continue continue
} }
line = bytes.TrimSpace(line)
if len(line) == 0 || line[0] == '#' {
line = line[:0]
continue
}
if bytes.HasPrefix(line, []byte(":debug")) { if bytes.HasPrefix(line, []byte(":debug")) {
ses.ToggleDebug() ses.ToggleDebug()
fmt.Println("Debug Toggled") fmt.Println("Debug Toggled")
@ -107,7 +112,7 @@ func Repl(ts graph.TripleStore, queryLanguage string, cfg *config.Config) error
} }
if bytes.HasPrefix(line, []byte(":a")) { if bytes.HasPrefix(line, []byte(":a")) {
var tripleStmt = line[3:] var tripleStmt = line[3:]
triple, err := nquads.Parse(string(tripleStmt)) triple, err := cquads.Parse(string(tripleStmt))
if triple == nil { if triple == nil {
if err != nil { if err != nil {
fmt.Printf("not a valid triple: %v\n", err) fmt.Printf("not a valid triple: %v\n", err)
@ -121,7 +126,7 @@ func Repl(ts graph.TripleStore, queryLanguage string, cfg *config.Config) error
} }
if bytes.HasPrefix(line, []byte(":d")) { if bytes.HasPrefix(line, []byte(":d")) {
var tripleStmt = line[3:] var tripleStmt = line[3:]
triple, err := nquads.Parse(string(tripleStmt)) triple, err := cquads.Parse(string(tripleStmt))
if triple == nil { if triple == nil {
if err != nil { if err != nil {
fmt.Printf("not a valid triple: %v\n", err) fmt.Printf("not a valid triple: %v\n", err)

View file

@ -93,7 +93,7 @@ POST Body: JSON triples
"subject": "Subject Node", "subject": "Subject Node",
"predicate": "Predicate Node", "predicate": "Predicate Node",
"object": "Object node", "object": "Object node",
"provenance": "Provenance node" // Optional "label": "Label node" // Optional
}] // More than one triple allowed. }] // More than one triple allowed.
``` ```
@ -121,7 +121,7 @@ POST Body: JSON triples
"subject": "Subject Node", "subject": "Subject Node",
"predicate": "Predicate Node", "predicate": "Predicate Node",
"object": "Object node", "object": "Object node",
"provenance": "Provenance node" // Optional "label": "Label node" // Optional
}] // More than one triple allowed. }] // More than one triple allowed.
``` ```

View file

@ -28,13 +28,13 @@ You can repeat the `--db` and `--dbpath` flags from here forward instead of the
First we load the data. First we load the data.
```bash ```bash
./cayley load --config=cayley.cfg.overview --triples=30kmoviedata.nt.gz ./cayley load --config=cayley.cfg.overview --triples=30kmoviedata.nq.gz
``` ```
And wait. It will load. If you'd like to watch it load, you can run And wait. It will load. If you'd like to watch it load, you can run
```bash ```bash
./cayley load --config=cayley.cfg.overview --triples=30kmoviedata.nt.gz --alsologtostderr ./cayley load --config=cayley.cfg.overview --triples=30kmoviedata.nq.gz --alsologtostderr
``` ```
And watch the log output go by. And watch the log output go by.

View file

@ -14,8 +14,7 @@
package graph package graph
// Define the general iterator interface, as well as the Base iterator which all // Define the general iterator interface.
// iterators can "inherit" from to get default iterator functionality.
import ( import (
"strings" "strings"
@ -24,18 +23,46 @@ import (
"github.com/barakmich/glog" "github.com/barakmich/glog"
) )
type Tagger struct {
tags []string
fixedTags map[string]Value
}
// Adds a tag to the iterator.
func (t *Tagger) Add(tag string) {
t.tags = append(t.tags, tag)
}
func (t *Tagger) AddFixed(tag string, value Value) {
if t.fixedTags == nil {
t.fixedTags = make(map[string]Value)
}
t.fixedTags[tag] = value
}
// Returns the tags. The returned value must not be mutated.
func (t *Tagger) Tags() []string {
return t.tags
}
// Returns the fixed tags. The returned value must not be mutated.
func (t *Tagger) Fixed() map[string]Value {
return t.fixedTags
}
func (t *Tagger) CopyFrom(src Iterator) {
for _, tag := range src.Tagger().Tags() {
t.Add(tag)
}
for k, v := range src.Tagger().Fixed() {
t.AddFixed(k, v)
}
}
type Iterator interface { type Iterator interface {
// Tags are the way we handle results. By adding a tag to an iterator, we can Tagger() *Tagger
// "name" it, in a sense, and at each step of iteration, get a named result.
// TagResults() is therefore the handy way of walking an iterator tree and
// getting the named results.
//
// Tag Accessors.
AddTag(string)
Tags() []string
AddFixedTag(string, Value)
FixedTags() map[string]Value
CopyTagsFrom(Iterator)
// Fills a tag-to-result-value map. // Fills a tag-to-result-value map.
TagResults(map[string]Value) TagResults(map[string]Value)
@ -58,19 +85,10 @@ type Iterator interface {
// All of them should set iterator.Last to be the last returned value, to // All of them should set iterator.Last to be the last returned value, to
// make results work. // make results work.
// //
// Next() advances the iterator and returns the next valid result. Returns
// (<value>, true) or (nil, false)
Next() (Value, bool)
// NextResult() advances iterators that may have more than one valid result, // NextResult() advances iterators that may have more than one valid result,
// from the bottom up. // from the bottom up.
NextResult() bool NextResult() bool
// Return whether this iterator is reliably nextable. Most iterators are.
// However, some iterators, like "not" are, by definition, the whole database
// except themselves. Next() on these is unproductive, if impossible.
CanNext() bool
// Check(), given a value, returns whether or not that value is within the set // Check(), given a value, returns whether or not that value is within the set
// held by this iterator. // held by this iterator.
Check(Value) bool Check(Value) bool
@ -117,6 +135,25 @@ type Iterator interface {
UID() uint64 UID() uint64
} }
type Nexter interface {
// Next() advances the iterator and returns the next valid result. Returns
// (<value>, true) or (nil, false)
Next() (Value, bool)
Iterator
}
// Next is a convenience function that conditionally calls the Next method
// of an Iterator if it is a Nexter. If the Iterator is not a Nexter, Next
// return a nil Value and false.
func Next(it Iterator) (Value, bool) {
if n, ok := it.(Nexter); ok {
return n.Next()
}
glog.Errorln("Nexting an un-nextable iterator")
return nil, false
}
// FixedIterator wraps iterators that are modifiable by addition of fixed value sets. // FixedIterator wraps iterators that are modifiable by addition of fixed value sets.
type FixedIterator interface { type FixedIterator interface {
Iterator Iterator

View file

@ -31,19 +31,25 @@ import (
// An All iterator across a range of int64 values, from `max` to `min`. // An All iterator across a range of int64 values, from `max` to `min`.
type Int64 struct { type Int64 struct {
Base uid uint64
tags graph.Tagger
max, min int64 max, min int64
at int64 at int64
result graph.Value
} }
// Creates a new Int64 with the given range. // Creates a new Int64 with the given range.
func NewInt64(min, max int64) *Int64 { func NewInt64(min, max int64) *Int64 {
var all Int64 return &Int64{
BaseInit(&all.Base) uid: NextUID(),
all.max = max min: min,
all.min = min max: max,
all.at = min at: min,
return &all }
}
func (it *Int64) UID() uint64 {
return it.uid
} }
// Start back at the beginning // Start back at the beginning
@ -55,13 +61,28 @@ func (it *Int64) Close() {}
func (it *Int64) Clone() graph.Iterator { func (it *Int64) Clone() graph.Iterator {
out := NewInt64(it.min, it.max) out := NewInt64(it.min, it.max)
out.CopyTagsFrom(it) out.tags.CopyFrom(it)
return out return out
} }
func (it *Int64) Tagger() *graph.Tagger {
return &it.tags
}
// Fill the map based on the tags assigned to this iterator.
func (it *Int64) TagResults(dst map[string]graph.Value) {
for _, tag := range it.tags.Tags() {
dst[tag] = it.Result()
}
for tag, value := range it.tags.Fixed() {
dst[tag] = value
}
}
// Prints the All iterator as just an "all". // Prints the All iterator as just an "all".
func (it *Int64) DebugString(indent int) string { func (it *Int64) DebugString(indent int) string {
return fmt.Sprintf("%s(%s tags: %v)", strings.Repeat(" ", indent), it.Type(), it.Tags()) return fmt.Sprintf("%s(%s tags: %v)", strings.Repeat(" ", indent), it.Type(), it.tags.Tags())
} }
// Next() on an Int64 all iterator is a simple incrementing counter. // Next() on an Int64 all iterator is a simple incrementing counter.
@ -76,10 +97,28 @@ func (it *Int64) Next() (graph.Value, bool) {
if it.at > it.max { if it.at > it.max {
it.at = -1 it.at = -1
} }
it.Last = val it.result = val
return graph.NextLogOut(it, val, true) return graph.NextLogOut(it, val, true)
} }
// DEPRECATED
func (it *Int64) ResultTree() *graph.ResultTree {
return graph.NewResultTree(it.Result())
}
func (it *Int64) Result() graph.Value {
return it.result
}
func (it *Int64) NextResult() bool {
return false
}
// No sub-iterators.
func (it *Int64) SubIterators() []graph.Iterator {
return nil
}
// The number of elements in an Int64 is the size of the range. // The number of elements in an Int64 is the size of the range.
// The size is exact. // The size is exact.
func (it *Int64) Size() (int64, bool) { func (it *Int64) Size() (int64, bool) {
@ -93,7 +132,7 @@ func (it *Int64) Check(tsv graph.Value) bool {
graph.CheckLogIn(it, tsv) graph.CheckLogIn(it, tsv)
v := tsv.(int64) v := tsv.(int64)
if it.min <= v && v <= it.max { if it.min <= v && v <= it.max {
it.Last = v it.result = v
return graph.CheckLogOut(it, v, true) return graph.CheckLogOut(it, v, true)
} }
return graph.CheckLogOut(it, v, false) return graph.CheckLogOut(it, v, false)

View file

@ -22,23 +22,28 @@ import (
"github.com/google/cayley/graph" "github.com/google/cayley/graph"
) )
// The And iterator. Consists of a Base and a number of subiterators, the primary of which will // The And iterator. Consists of a number of subiterators, the primary of which will
// be Next()ed if next is called. // be Next()ed if next is called.
type And struct { type And struct {
Base uid uint64
tags graph.Tagger
internalIterators []graph.Iterator internalIterators []graph.Iterator
itCount int itCount int
primaryIt graph.Iterator primaryIt graph.Iterator
checkList []graph.Iterator checkList []graph.Iterator
result graph.Value
} }
// Creates a new And iterator. // Creates a new And iterator.
func NewAnd() *And { func NewAnd() *And {
var and And return &And{
BaseInit(&and.Base) uid: NextUID(),
and.internalIterators = make([]graph.Iterator, 0, 20) internalIterators: make([]graph.Iterator, 0, 20),
and.checkList = nil }
return &and }
func (it *And) UID() uint64 {
return it.uid
} }
// Reset all internal iterators // Reset all internal iterators
@ -50,10 +55,33 @@ func (it *And) Reset() {
it.checkList = nil it.checkList = nil
} }
func (it *And) Tagger() *graph.Tagger {
return &it.tags
}
// An extended TagResults, as it needs to add it's own results and
// recurse down it's subiterators.
func (it *And) TagResults(dst map[string]graph.Value) {
for _, tag := range it.tags.Tags() {
dst[tag] = it.Result()
}
for tag, value := range it.tags.Fixed() {
dst[tag] = value
}
if it.primaryIt != nil {
it.primaryIt.TagResults(dst)
}
for _, sub := range it.internalIterators {
sub.TagResults(dst)
}
}
func (it *And) Clone() graph.Iterator { func (it *And) Clone() graph.Iterator {
and := NewAnd() and := NewAnd()
and.AddSubIterator(it.primaryIt.Clone()) and.AddSubIterator(it.primaryIt.Clone())
and.CopyTagsFrom(it) and.tags.CopyFrom(it)
for _, sub := range it.internalIterators { for _, sub := range it.internalIterators {
and.AddSubIterator(sub.Clone()) and.AddSubIterator(sub.Clone())
} }
@ -71,18 +99,6 @@ func (it *And) SubIterators() []graph.Iterator {
return iters return iters
} }
// Overrides Base TagResults, as it needs to add it's own results and
// recurse down it's subiterators.
func (it *And) TagResults(dst map[string]graph.Value) {
it.Base.TagResults(dst)
if it.primaryIt != nil {
it.primaryIt.TagResults(dst)
}
for _, sub := range it.internalIterators {
sub.TagResults(dst)
}
}
// DEPRECATED Returns the ResultTree for this iterator, recurses to it's subiterators. // DEPRECATED Returns the ResultTree for this iterator, recurses to it's subiterators.
func (it *And) ResultTree() *graph.ResultTree { func (it *And) ResultTree() *graph.ResultTree {
tree := graph.NewResultTree(it.Result()) tree := graph.NewResultTree(it.Result())
@ -101,7 +117,7 @@ func (it *And) DebugString(indent int) string {
total += fmt.Sprintf("%d:\n%s\n", i, sub.DebugString(indent+4)) total += fmt.Sprintf("%d:\n%s\n", i, sub.DebugString(indent+4))
} }
var tags string var tags string
for _, k := range it.Tags() { for _, k := range it.tags.Tags() {
tags += fmt.Sprintf("%s;", k) tags += fmt.Sprintf("%s;", k)
} }
spaces := strings.Repeat(" ", indent+2) spaces := strings.Repeat(" ", indent+2)
@ -144,16 +160,20 @@ func (it *And) Next() (graph.Value, bool) {
var curr graph.Value var curr graph.Value
var exists bool var exists bool
for { for {
curr, exists = it.primaryIt.Next() curr, exists = graph.Next(it.primaryIt)
if !exists { if !exists {
return graph.NextLogOut(it, nil, false) return graph.NextLogOut(it, nil, false)
} }
if it.checkSubIts(curr) { if it.checkSubIts(curr) {
it.Last = curr it.result = curr
return graph.NextLogOut(it, curr, true) return graph.NextLogOut(it, curr, true)
} }
} }
panic("Somehow broke out of Next() loop in And") panic("unreachable")
}
func (it *And) Result() graph.Value {
return it.result
} }
// Checks a value against the non-primary iterators, in order. // Checks a value against the non-primary iterators, in order.
@ -177,7 +197,7 @@ func (it *And) checkCheckList(val graph.Value) bool {
} }
} }
if ok { if ok {
it.Last = val it.result = val
} }
return graph.CheckLogOut(it, val, ok) return graph.CheckLogOut(it, val, ok)
} }
@ -196,7 +216,7 @@ func (it *And) Check(val graph.Value) bool {
if !othersGood { if !othersGood {
return graph.CheckLogOut(it, val, false) return graph.CheckLogOut(it, val, false)
} }
it.Last = val it.result = val
return graph.CheckLogOut(it, val, true) return graph.CheckLogOut(it, val, true)
} }

View file

@ -82,7 +82,7 @@ func (it *And) Optimize() (graph.Iterator, bool) {
} }
// Move the tags hanging on us (like any good replacement). // Move the tags hanging on us (like any good replacement).
newAnd.CopyTagsFrom(it) newAnd.tags.CopyFrom(it)
newAnd.optimizeCheck() newAnd.optimizeCheck()
@ -145,14 +145,14 @@ func optimizeOrder(its []graph.Iterator) []graph.Iterator {
// all of it's contents, and to Check() each of those against everyone // all of it's contents, and to Check() each of those against everyone
// else. // else.
for _, it := range its { for _, it := range its {
if !it.CanNext() { if _, canNext := it.(graph.Nexter); !canNext {
bad = append(bad, it) bad = append(bad, it)
continue continue
} }
rootStats := it.Stats() rootStats := it.Stats()
cost := rootStats.NextCost cost := rootStats.NextCost
for _, f := range its { for _, f := range its {
if !f.CanNext() { if _, canNext := it.(graph.Nexter); !canNext {
continue continue
} }
if f == it { if f == it {
@ -177,7 +177,7 @@ func optimizeOrder(its []graph.Iterator) []graph.Iterator {
// ... push everyone else after... // ... push everyone else after...
for _, it := range its { for _, it := range its {
if !it.CanNext() { if _, canNext := it.(graph.Nexter); !canNext {
continue continue
} }
if it != best { if it != best {
@ -213,11 +213,11 @@ func (it *And) optimizeCheck() {
func (it *And) getSubTags() map[string]struct{} { func (it *And) getSubTags() map[string]struct{} {
tags := make(map[string]struct{}) tags := make(map[string]struct{})
for _, sub := range it.SubIterators() { for _, sub := range it.SubIterators() {
for _, tag := range sub.Tags() { for _, tag := range sub.Tagger().Tags() {
tags[tag] = struct{}{} tags[tag] = struct{}{}
} }
} }
for _, tag := range it.Tags() { for _, tag := range it.tags.Tags() {
tags[tag] = struct{}{} tags[tag] = struct{}{}
} }
return tags return tags
@ -227,13 +227,14 @@ func (it *And) getSubTags() map[string]struct{} {
// src itself, and moves them to dst. // src itself, and moves them to dst.
func moveTagsTo(dst graph.Iterator, src *And) { func moveTagsTo(dst graph.Iterator, src *And) {
tags := src.getSubTags() tags := src.getSubTags()
for _, tag := range dst.Tags() { for _, tag := range dst.Tagger().Tags() {
if _, ok := tags[tag]; ok { if _, ok := tags[tag]; ok {
delete(tags, tag) delete(tags, tag)
} }
} }
dt := dst.Tagger()
for k := range tags { for k := range tags {
dst.AddTag(k) dt.Add(k)
} }
} }

View file

@ -32,9 +32,9 @@ func TestIteratorPromotion(t *testing.T) {
a := NewAnd() a := NewAnd()
a.AddSubIterator(all) a.AddSubIterator(all)
a.AddSubIterator(fixed) a.AddSubIterator(fixed)
all.AddTag("a") all.Tagger().Add("a")
fixed.AddTag("b") fixed.Tagger().Add("b")
a.AddTag("c") a.Tagger().Add("c")
newIt, changed := a.Optimize() newIt, changed := a.Optimize()
if !changed { if !changed {
t.Error("Iterator didn't optimize") t.Error("Iterator didn't optimize")
@ -43,7 +43,7 @@ func TestIteratorPromotion(t *testing.T) {
t.Error("Expected fixed iterator") t.Error("Expected fixed iterator")
} }
tagsExpected := []string{"a", "b", "c"} tagsExpected := []string{"a", "b", "c"}
tags := newIt.Tags() tags := newIt.Tagger().Tags()
sort.Strings(tags) sort.Strings(tags)
if !reflect.DeepEqual(tags, tagsExpected) { if !reflect.DeepEqual(tags, tagsExpected) {
t.Fatal("Tags don't match") t.Fatal("Tags don't match")
@ -67,9 +67,9 @@ func TestNullIteratorAnd(t *testing.T) {
func TestReorderWithTag(t *testing.T) { func TestReorderWithTag(t *testing.T) {
all := NewInt64(100, 300) all := NewInt64(100, 300)
all.AddTag("good") all.Tagger().Add("good")
all2 := NewInt64(1, 30000) all2 := NewInt64(1, 30000)
all2.AddTag("slow") all2.Tagger().Add("slow")
a := NewAnd() a := NewAnd()
// Make all2 the default iterator // Make all2 the default iterator
a.AddSubIterator(all2) a.AddSubIterator(all2)
@ -82,7 +82,7 @@ func TestReorderWithTag(t *testing.T) {
expectedTags := []string{"good", "slow"} expectedTags := []string{"good", "slow"}
tagsOut := make([]string, 0) tagsOut := make([]string, 0)
for _, sub := range newIt.SubIterators() { for _, sub := range newIt.SubIterators() {
for _, x := range sub.Tags() { for _, x := range sub.Tagger().Tags() {
tagsOut = append(tagsOut, x) tagsOut = append(tagsOut, x)
} }
} }
@ -93,9 +93,9 @@ func TestReorderWithTag(t *testing.T) {
func TestAndStatistics(t *testing.T) { func TestAndStatistics(t *testing.T) {
all := NewInt64(100, 300) all := NewInt64(100, 300)
all.AddTag("good") all.Tagger().Add("good")
all2 := NewInt64(1, 30000) all2 := NewInt64(1, 30000)
all2.AddTag("slow") all2.Tagger().Add("slow")
a := NewAnd() a := NewAnd()
// Make all2 the default iterator // Make all2 the default iterator
a.AddSubIterator(all2) a.AddSubIterator(all2)

View file

@ -24,11 +24,11 @@ import (
func TestTag(t *testing.T) { func TestTag(t *testing.T) {
fix1 := newFixed() fix1 := newFixed()
fix1.Add(234) fix1.Add(234)
fix1.AddTag("foo") fix1.Tagger().Add("foo")
and := NewAnd() and := NewAnd()
and.AddSubIterator(fix1) and.AddSubIterator(fix1)
and.AddTag("bar") and.Tagger().Add("bar")
out := fix1.Tags() out := fix1.Tagger().Tags()
if len(out) != 1 { if len(out) != 1 {
t.Errorf("Expected length 1, got %d", len(out)) t.Errorf("Expected length 1, got %d", len(out))
} }

View file

@ -30,10 +30,12 @@ import (
// A Fixed iterator consists of it's values, an index (where it is in the process of Next()ing) and // A Fixed iterator consists of it's values, an index (where it is in the process of Next()ing) and
// an equality function. // an equality function.
type Fixed struct { type Fixed struct {
Base uid uint64
tags graph.Tagger
values []graph.Value values []graph.Value
lastIndex int lastIndex int
cmp Equality cmp Equality
result graph.Value
} }
// Define the signature of an equality function. // Define the signature of an equality function.
@ -54,12 +56,15 @@ func newFixed() *Fixed {
// Creates a new Fixed iterator with a custom comparitor. // Creates a new Fixed iterator with a custom comparitor.
func NewFixedIteratorWithCompare(compareFn Equality) *Fixed { func NewFixedIteratorWithCompare(compareFn Equality) *Fixed {
var it Fixed return &Fixed{
BaseInit(&it.Base) uid: NextUID(),
it.values = make([]graph.Value, 0, 20) values: make([]graph.Value, 0, 20),
it.lastIndex = 0 cmp: compareFn,
it.cmp = compareFn }
return &it }
func (it *Fixed) UID() uint64 {
return it.uid
} }
func (it *Fixed) Reset() { func (it *Fixed) Reset() {
@ -68,12 +73,26 @@ func (it *Fixed) Reset() {
func (it *Fixed) Close() {} func (it *Fixed) Close() {}
func (it *Fixed) Tagger() *graph.Tagger {
return &it.tags
}
func (it *Fixed) TagResults(dst map[string]graph.Value) {
for _, tag := range it.tags.Tags() {
dst[tag] = it.Result()
}
for tag, value := range it.tags.Fixed() {
dst[tag] = value
}
}
func (it *Fixed) Clone() graph.Iterator { func (it *Fixed) Clone() graph.Iterator {
out := NewFixedIteratorWithCompare(it.cmp) out := NewFixedIteratorWithCompare(it.cmp)
for _, val := range it.values { for _, val := range it.values {
out.Add(val) out.Add(val)
} }
out.CopyTagsFrom(it) out.tags.CopyFrom(it)
return out return out
} }
@ -92,7 +111,7 @@ func (it *Fixed) DebugString(indent int) string {
return fmt.Sprintf("%s(%s tags: %s Size: %d id0: %d)", return fmt.Sprintf("%s(%s tags: %s Size: %d id0: %d)",
strings.Repeat(" ", indent), strings.Repeat(" ", indent),
it.Type(), it.Type(),
it.FixedTags(), it.tags.Fixed(),
len(it.values), len(it.values),
value, value,
) )
@ -109,7 +128,7 @@ func (it *Fixed) Check(v graph.Value) bool {
graph.CheckLogIn(it, v) graph.CheckLogIn(it, v)
for _, x := range it.values { for _, x := range it.values {
if it.cmp(x, v) { if it.cmp(x, v) {
it.Last = x it.result = x
return graph.CheckLogOut(it, v, true) return graph.CheckLogOut(it, v, true)
} }
} }
@ -123,11 +142,29 @@ func (it *Fixed) Next() (graph.Value, bool) {
return graph.NextLogOut(it, nil, false) return graph.NextLogOut(it, nil, false)
} }
out := it.values[it.lastIndex] out := it.values[it.lastIndex]
it.Last = out it.result = out
it.lastIndex++ it.lastIndex++
return graph.NextLogOut(it, out, true) return graph.NextLogOut(it, out, true)
} }
// DEPRECATED
func (it *Fixed) ResultTree() *graph.ResultTree {
return graph.NewResultTree(it.Result())
}
func (it *Fixed) Result() graph.Value {
return it.result
}
func (it *Fixed) NextResult() bool {
return false
}
// No sub-iterators.
func (it *Fixed) SubIterators() []graph.Iterator {
return nil
}
// Optimize() for a Fixed iterator is simple. Returns a Null iterator if it's empty // Optimize() for a Fixed iterator is simple. Returns a Null iterator if it's empty
// (so that other iterators upstream can treat this as null) or there is no // (so that other iterators upstream can treat this as null) or there is no
// optimization. // optimization.

View file

@ -40,28 +40,35 @@ import (
"github.com/barakmich/glog" "github.com/barakmich/glog"
"github.com/google/cayley/graph" "github.com/google/cayley/graph"
"github.com/google/cayley/quad"
) )
// A HasA consists of a reference back to the graph.TripleStore that it references, // A HasA consists of a reference back to the graph.TripleStore that it references,
// a primary subiterator, a direction in which the triples for that subiterator point, // a primary subiterator, a direction in which the triples for that subiterator point,
// and a temporary holder for the iterator generated on Check(). // and a temporary holder for the iterator generated on Check().
type HasA struct { type HasA struct {
Base uid uint64
tags graph.Tagger
ts graph.TripleStore ts graph.TripleStore
primaryIt graph.Iterator primaryIt graph.Iterator
dir graph.Direction dir quad.Direction
resultIt graph.Iterator resultIt graph.Iterator
result graph.Value
} }
// Construct a new HasA iterator, given the triple subiterator, and the triple // Construct a new HasA iterator, given the triple subiterator, and the triple
// direction for which it stands. // direction for which it stands.
func NewHasA(ts graph.TripleStore, subIt graph.Iterator, d graph.Direction) *HasA { func NewHasA(ts graph.TripleStore, subIt graph.Iterator, d quad.Direction) *HasA {
var hasa HasA return &HasA{
BaseInit(&hasa.Base) uid: NextUID(),
hasa.ts = ts ts: ts,
hasa.primaryIt = subIt primaryIt: subIt,
hasa.dir = d dir: d,
return &hasa }
}
func (it *HasA) UID() uint64 {
return it.uid
} }
// Return our sole subiterator. // Return our sole subiterator.
@ -76,14 +83,18 @@ func (it *HasA) Reset() {
} }
} }
func (it *HasA) Tagger() *graph.Tagger {
return &it.tags
}
func (it *HasA) Clone() graph.Iterator { func (it *HasA) Clone() graph.Iterator {
out := NewHasA(it.ts, it.primaryIt.Clone(), it.dir) out := NewHasA(it.ts, it.primaryIt.Clone(), it.dir)
out.CopyTagsFrom(it) out.tags.CopyFrom(it)
return out return out
} }
// Direction accessor. // Direction accessor.
func (it *HasA) Direction() graph.Direction { return it.dir } func (it *HasA) Direction() quad.Direction { return it.dir }
// Pass the Optimize() call along to the subiterator. If it becomes Null, // Pass the Optimize() call along to the subiterator. If it becomes Null,
// then the HasA becomes Null (there are no triples that have any directions). // then the HasA becomes Null (there are no triples that have any directions).
@ -100,7 +111,14 @@ func (it *HasA) Optimize() (graph.Iterator, bool) {
// Pass the TagResults down the chain. // Pass the TagResults down the chain.
func (it *HasA) TagResults(dst map[string]graph.Value) { func (it *HasA) TagResults(dst map[string]graph.Value) {
it.Base.TagResults(dst) for _, tag := range it.tags.Tags() {
dst[tag] = it.Result()
}
for tag, value := range it.tags.Fixed() {
dst[tag] = value
}
it.primaryIt.TagResults(dst) it.primaryIt.TagResults(dst)
} }
@ -114,7 +132,7 @@ func (it *HasA) ResultTree() *graph.ResultTree {
// Print some information about this iterator. // Print some information about this iterator.
func (it *HasA) DebugString(indent int) string { func (it *HasA) DebugString(indent int) string {
var tags string var tags string
for _, k := range it.Tags() { for _, k := range it.tags.Tags() {
tags += fmt.Sprintf("%s;", k) tags += fmt.Sprintf("%s;", k)
} }
return fmt.Sprintf("%s(%s %d tags:%s direction:%s\n%s)", strings.Repeat(" ", indent), it.Type(), it.UID(), tags, it.dir, it.primaryIt.DebugString(indent+4)) return fmt.Sprintf("%s(%s %d tags:%s direction:%s\n%s)", strings.Repeat(" ", indent), it.Type(), it.UID(), tags, it.dir, it.primaryIt.DebugString(indent+4))
@ -141,15 +159,15 @@ func (it *HasA) Check(val graph.Value) bool {
// another match is made. // another match is made.
func (it *HasA) GetCheckResult() bool { func (it *HasA) GetCheckResult() bool {
for { for {
linkVal, ok := it.resultIt.Next() linkVal, ok := graph.Next(it.resultIt)
if !ok { if !ok {
break break
} }
if glog.V(4) { if glog.V(4) {
glog.V(4).Infoln("Triple is", it.ts.Triple(linkVal)) glog.V(4).Infoln("Quad is", it.ts.Quad(linkVal))
} }
if it.primaryIt.Check(linkVal) { if it.primaryIt.Check(linkVal) {
it.Last = it.ts.TripleDirection(linkVal, it.dir) it.result = it.ts.TripleDirection(linkVal, it.dir)
return true return true
} }
} }
@ -180,16 +198,20 @@ func (it *HasA) Next() (graph.Value, bool) {
} }
it.resultIt = &Null{} it.resultIt = &Null{}
tID, ok := it.primaryIt.Next() tID, ok := graph.Next(it.primaryIt)
if !ok { if !ok {
return graph.NextLogOut(it, 0, false) return graph.NextLogOut(it, 0, false)
} }
name := it.ts.Triple(tID).Get(it.dir) name := it.ts.Quad(tID).Get(it.dir)
val := it.ts.ValueOf(name) val := it.ts.ValueOf(name)
it.Last = val it.result = val
return graph.NextLogOut(it, val, true) return graph.NextLogOut(it, val, true)
} }
func (it *HasA) Result() graph.Value {
return it.result
}
// GetStats() returns the statistics on the HasA iterator. This is curious. Next // GetStats() returns the statistics on the HasA iterator. This is curious. Next
// cost is easy, it's an extra call or so on top of the subiterator Next cost. // cost is easy, it's an extra call or so on top of the subiterator Next cost.
// CheckCost involves going to the graph.TripleStore, iterating out values, and hoping // CheckCost involves going to the graph.TripleStore, iterating out values, and hoping
@ -221,3 +243,7 @@ func (it *HasA) Close() {
// Register this iterator as a HasA. // Register this iterator as a HasA.
func (it *HasA) Type() graph.Type { return graph.HasA } func (it *HasA) Type() graph.Type { return graph.HasA }
func (it *HasA) Size() (int64, bool) {
return 0, true
}

View file

@ -14,16 +14,12 @@
package iterator package iterator
// Define the general iterator interface, as well as the Base which all // Define the general iterator interface.
// iterators can "inherit" from to get default iterator functionality.
import ( import (
"fmt"
"strings" "strings"
"sync/atomic" "sync/atomic"
"github.com/barakmich/glog"
"github.com/google/cayley/graph" "github.com/google/cayley/graph"
) )
@ -33,142 +29,40 @@ func NextUID() uint64 {
return atomic.AddUint64(&nextIteratorID, 1) - 1 return atomic.AddUint64(&nextIteratorID, 1) - 1
} }
// The Base iterator is the iterator other iterators inherit from to get some // Here we define the simplest iterator -- the Null iterator. It contains nothing.
// default functionality.
type Base struct {
Last graph.Value
tags []string
fixedTags map[string]graph.Value
canNext bool
uid uint64
}
// Called by subclases.
func BaseInit(it *Base) {
// Your basic iterator is nextable
it.canNext = true
if glog.V(2) {
it.uid = NextUID()
}
}
func (it *Base) UID() uint64 {
return it.uid
}
// Adds a tag to the iterator. Most iterators don't need to override.
func (it *Base) AddTag(tag string) {
if it.tags == nil {
it.tags = make([]string, 0)
}
it.tags = append(it.tags, tag)
}
func (it *Base) AddFixedTag(tag string, value graph.Value) {
if it.fixedTags == nil {
it.fixedTags = make(map[string]graph.Value)
}
it.fixedTags[tag] = value
}
// Returns the tags.
func (it *Base) Tags() []string {
return it.tags
}
func (it *Base) FixedTags() map[string]graph.Value {
return it.fixedTags
}
func (it *Base) CopyTagsFrom(other_it graph.Iterator) {
for _, tag := range other_it.Tags() {
it.AddTag(tag)
}
for k, v := range other_it.FixedTags() {
it.AddFixedTag(k, v)
}
}
// Prints a silly debug string. Most classes override.
func (it *Base) DebugString(indent int) string {
return fmt.Sprintf("%s(base)", strings.Repeat(" ", indent))
}
// Nothing in a base iterator.
func (it *Base) Check(v graph.Value) bool {
return false
}
// Base iterators should never appear in a tree if they are, select against
// them.
func (it *Base) Stats() graph.IteratorStats {
return graph.IteratorStats{100000, 100000, 100000}
}
// DEPRECATED
func (it *Base) ResultTree() *graph.ResultTree {
tree := graph.NewResultTree(it.Result())
return tree
}
// Nothing in a base iterator.
func (it *Base) Next() (graph.Value, bool) {
return nil, false
}
func (it *Base) NextResult() bool {
return false
}
// Returns the last result of an iterator.
func (it *Base) Result() graph.Value {
return it.Last
}
// If you're empty and you know it, clap your hands.
func (it *Base) Size() (int64, bool) {
return 0, true
}
// No subiterators. Only those with subiterators need to do anything here.
func (it *Base) SubIterators() []graph.Iterator {
return nil
}
// Accessor
func (it *Base) CanNext() bool { return it.canNext }
// Fill the map based on the tags assigned to this iterator. Default
// functionality works well for most iterators.
func (it *Base) TagResults(dst map[string]graph.Value) {
for _, tag := range it.Tags() {
dst[tag] = it.Result()
}
for tag, value := range it.FixedTags() {
dst[tag] = value
}
}
// Nothing to clean up.
// func (it *Base) Close() {}
func (it *Null) Close() {}
func (it *Base) Reset() {}
// Here we define the simplest base iterator -- the Null iterator. It contains nothing.
// It is the empty set. Often times, queries that contain one of these match nothing, // It is the empty set. Often times, queries that contain one of these match nothing,
// so it's important to give it a special iterator. // so it's important to give it a special iterator.
type Null struct { type Null struct {
Base uid uint64
tags graph.Tagger
} }
// Fairly useless New function. // Fairly useless New function.
func NewNull() *Null { func NewNull() *Null {
return &Null{} return &Null{uid: NextUID()}
}
func (it *Null) UID() uint64 {
return it.uid
}
func (it *Null) Tagger() *graph.Tagger {
return &it.tags
}
// Fill the map based on the tags assigned to this iterator.
func (it *Null) TagResults(dst map[string]graph.Value) {
for _, tag := range it.tags.Tags() {
dst[tag] = it.Result()
}
for tag, value := range it.tags.Fixed() {
dst[tag] = value
}
}
func (it *Null) Check(graph.Value) bool {
return false
} }
func (it *Null) Clone() graph.Iterator { return NewNull() } func (it *Null) Clone() graph.Iterator { return NewNull() }
@ -185,6 +79,34 @@ func (it *Null) DebugString(indent int) string {
return strings.Repeat(" ", indent) + "(null)" return strings.Repeat(" ", indent) + "(null)"
} }
func (it *Null) Next() (graph.Value, bool) {
return nil, false
}
func (it *Null) Result() graph.Value {
return nil
}
func (it *Null) ResultTree() *graph.ResultTree {
return graph.NewResultTree(it.Result())
}
func (it *Null) SubIterators() []graph.Iterator {
return nil
}
func (it *Null) NextResult() bool {
return false
}
func (it *Null) Size() (int64, bool) {
return 0, true
}
func (it *Null) Reset() {}
func (it *Null) Close() {}
// A null iterator costs nothing. Use it! // A null iterator costs nothing. Use it!
func (it *Null) Stats() graph.IteratorStats { func (it *Null) Stats() graph.IteratorStats {
return graph.IteratorStats{} return graph.IteratorStats{}

View file

@ -34,29 +34,36 @@ import (
"strings" "strings"
"github.com/google/cayley/graph" "github.com/google/cayley/graph"
"github.com/google/cayley/quad"
) )
// A LinksTo has a reference back to the graph.TripleStore (to create the iterators // A LinksTo has a reference back to the graph.TripleStore (to create the iterators
// for each node) the subiterator, and the direction the iterator comes from. // for each node) the subiterator, and the direction the iterator comes from.
// `next_it` is the tempoarary iterator held per result in `primary_it`. // `next_it` is the tempoarary iterator held per result in `primary_it`.
type LinksTo struct { type LinksTo struct {
Base uid uint64
tags graph.Tagger
ts graph.TripleStore ts graph.TripleStore
primaryIt graph.Iterator primaryIt graph.Iterator
dir graph.Direction dir quad.Direction
nextIt graph.Iterator nextIt graph.Iterator
result graph.Value
} }
// Construct a new LinksTo iterator around a direction and a subiterator of // Construct a new LinksTo iterator around a direction and a subiterator of
// nodes. // nodes.
func NewLinksTo(ts graph.TripleStore, it graph.Iterator, d graph.Direction) *LinksTo { func NewLinksTo(ts graph.TripleStore, it graph.Iterator, d quad.Direction) *LinksTo {
var lto LinksTo return &LinksTo{
BaseInit(&lto.Base) uid: NextUID(),
lto.ts = ts ts: ts,
lto.primaryIt = it primaryIt: it,
lto.dir = d dir: d,
lto.nextIt = &Null{} nextIt: &Null{},
return &lto }
}
func (it *LinksTo) UID() uint64 {
return it.uid
} }
func (it *LinksTo) Reset() { func (it *LinksTo) Reset() {
@ -67,18 +74,29 @@ func (it *LinksTo) Reset() {
it.nextIt = &Null{} it.nextIt = &Null{}
} }
func (it *LinksTo) Tagger() *graph.Tagger {
return &it.tags
}
func (it *LinksTo) Clone() graph.Iterator { func (it *LinksTo) Clone() graph.Iterator {
out := NewLinksTo(it.ts, it.primaryIt.Clone(), it.dir) out := NewLinksTo(it.ts, it.primaryIt.Clone(), it.dir)
out.CopyTagsFrom(it) out.tags.CopyFrom(it)
return out return out
} }
// Return the direction under consideration. // Return the direction under consideration.
func (it *LinksTo) Direction() graph.Direction { return it.dir } func (it *LinksTo) Direction() quad.Direction { return it.dir }
// Tag these results, and our subiterator's results. // Tag these results, and our subiterator's results.
func (it *LinksTo) TagResults(dst map[string]graph.Value) { func (it *LinksTo) TagResults(dst map[string]graph.Value) {
it.Base.TagResults(dst) for _, tag := range it.tags.Tags() {
dst[tag] = it.Result()
}
for tag, value := range it.tags.Fixed() {
dst[tag] = value
}
it.primaryIt.TagResults(dst) it.primaryIt.TagResults(dst)
} }
@ -102,7 +120,7 @@ func (it *LinksTo) Check(val graph.Value) bool {
graph.CheckLogIn(it, val) graph.CheckLogIn(it, val)
node := it.ts.TripleDirection(val, it.dir) node := it.ts.TripleDirection(val, it.dir)
if it.primaryIt.Check(node) { if it.primaryIt.Check(node) {
it.Last = val it.result = val
return graph.CheckLogOut(it, val, true) return graph.CheckLogOut(it, val, true)
} }
return graph.CheckLogOut(it, val, false) return graph.CheckLogOut(it, val, false)
@ -137,10 +155,10 @@ func (it *LinksTo) Optimize() (graph.Iterator, bool) {
// Next()ing a LinksTo operates as described above. // Next()ing a LinksTo operates as described above.
func (it *LinksTo) Next() (graph.Value, bool) { func (it *LinksTo) Next() (graph.Value, bool) {
graph.NextLogIn(it) graph.NextLogIn(it)
val, ok := it.nextIt.Next() val, ok := graph.Next(it.nextIt)
if !ok { if !ok {
// Subiterator is empty, get another one // Subiterator is empty, get another one
candidate, ok := it.primaryIt.Next() candidate, ok := graph.Next(it.primaryIt)
if !ok { if !ok {
// We're out of nodes in our subiterator, so we're done as well. // We're out of nodes in our subiterator, so we're done as well.
return graph.NextLogOut(it, 0, false) return graph.NextLogOut(it, 0, false)
@ -150,10 +168,14 @@ func (it *LinksTo) Next() (graph.Value, bool) {
// Recurse -- return the first in the next set. // Recurse -- return the first in the next set.
return it.Next() return it.Next()
} }
it.Last = val it.result = val
return graph.NextLogOut(it, val, ok) return graph.NextLogOut(it, val, ok)
} }
func (it *LinksTo) Result() graph.Value {
return it.result
}
// Close our subiterators. // Close our subiterators.
func (it *LinksTo) Close() { func (it *LinksTo) Close() {
it.nextIt.Close() it.nextIt.Close()
@ -181,3 +203,7 @@ func (it *LinksTo) Stats() graph.IteratorStats {
Size: fanoutFactor * subitStats.Size, Size: fanoutFactor * subitStats.Size,
} }
} }
func (it *LinksTo) Size() (int64, bool) {
return 0, true
}

View file

@ -17,7 +17,7 @@ package iterator
import ( import (
"testing" "testing"
"github.com/google/cayley/graph" "github.com/google/cayley/quad"
) )
func TestLinksTo(t *testing.T) { func TestLinksTo(t *testing.T) {
@ -32,12 +32,12 @@ func TestLinksTo(t *testing.T) {
t.Fatalf("Failed to return correct value, got:%v expect:1", val) t.Fatalf("Failed to return correct value, got:%v expect:1", val)
} }
fixed.Add(val) fixed.Add(val)
lto := NewLinksTo(ts, fixed, graph.Object) lto := NewLinksTo(ts, fixed, quad.Object)
val, ok := lto.Next() val, ok := lto.Next()
if !ok { if !ok {
t.Error("At least one triple matches the fixed object") t.Error("At least one triple matches the fixed object")
} }
if val != 2 { if val != 2 {
t.Errorf("Triple index 2, such as %s, should match %s", ts.Triple(2), ts.Triple(val)) t.Errorf("Quad index 2, such as %s, should match %s", ts.Quad(2), ts.Quad(val))
} }
} }

View file

@ -17,15 +17,18 @@ package iterator
// A quickly mocked version of the TripleStore interface, for use in tests. // A quickly mocked version of the TripleStore interface, for use in tests.
// Can better used Mock.Called but will fill in as needed. // Can better used Mock.Called but will fill in as needed.
import "github.com/google/cayley/graph" import (
"github.com/google/cayley/graph"
"github.com/google/cayley/quad"
)
type store struct { type store struct {
data []string data []string
iter graph.Iterator iter graph.Iterator
} }
func (ts *store) ValueOf(s string) graph.Value { func (qs *store) ValueOf(s string) graph.Value {
for i, v := range ts.data { for i, v := range qs.data {
if s == v { if s == v {
return i return i
} }
@ -33,42 +36,42 @@ func (ts *store) ValueOf(s string) graph.Value {
return nil return nil
} }
func (ts *store) AddTriple(*graph.Triple) {} func (qs *store) AddTriple(*quad.Quad) {}
func (ts *store) AddTripleSet([]*graph.Triple) {} func (qs *store) AddTripleSet([]*quad.Quad) {}
func (ts *store) Triple(graph.Value) *graph.Triple { return &graph.Triple{} } func (qs *store) Quad(graph.Value) *quad.Quad { return &quad.Quad{} }
func (ts *store) TripleIterator(d graph.Direction, i graph.Value) graph.Iterator { func (qs *store) TripleIterator(d quad.Direction, i graph.Value) graph.Iterator {
return ts.iter return qs.iter
} }
func (ts *store) NodesAllIterator() graph.Iterator { return &Null{} } func (qs *store) NodesAllIterator() graph.Iterator { return &Null{} }
func (ts *store) TriplesAllIterator() graph.Iterator { return &Null{} } func (qs *store) TriplesAllIterator() graph.Iterator { return &Null{} }
func (ts *store) NameOf(v graph.Value) string { func (qs *store) NameOf(v graph.Value) string {
i := v.(int) i := v.(int)
if i < 0 || i >= len(ts.data) { if i < 0 || i >= len(qs.data) {
return "" return ""
} }
return ts.data[i] return qs.data[i]
} }
func (ts *store) Size() int64 { return 0 } func (qs *store) Size() int64 { return 0 }
func (ts *store) DebugPrint() {} func (qs *store) DebugPrint() {}
func (ts *store) OptimizeIterator(it graph.Iterator) (graph.Iterator, bool) { func (qs *store) OptimizeIterator(it graph.Iterator) (graph.Iterator, bool) {
return &Null{}, false return &Null{}, false
} }
func (ts *store) FixedIterator() graph.FixedIterator { func (qs *store) FixedIterator() graph.FixedIterator {
return NewFixedIteratorWithCompare(BasicEquality) return NewFixedIteratorWithCompare(BasicEquality)
} }
func (ts *store) Close() {} func (qs *store) Close() {}
func (ts *store) TripleDirection(graph.Value, graph.Direction) graph.Value { return 0 } func (qs *store) TripleDirection(graph.Value, quad.Direction) graph.Value { return 0 }
func (ts *store) RemoveTriple(t *graph.Triple) {} func (qs *store) RemoveTriple(t *quad.Quad) {}

View file

@ -30,26 +30,31 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/barakmich/glog"
"github.com/google/cayley/graph" "github.com/google/cayley/graph"
) )
// An optional iterator has the subconstraint iterator we wish to be optional // An optional iterator has the sub-constraint iterator we wish to be optional
// and whether the last check we received was true or false. // and whether the last check we received was true or false.
type Optional struct { type Optional struct {
Base uid uint64
tags graph.Tagger
subIt graph.Iterator subIt graph.Iterator
lastCheck bool lastCheck bool
result graph.Value
} }
// Creates a new optional iterator. // Creates a new optional iterator.
func NewOptional(it graph.Iterator) *Optional { func NewOptional(it graph.Iterator) *Optional {
var o Optional return &Optional{
BaseInit(&o.Base) uid: NextUID(),
o.canNext = false subIt: it,
o.subIt = it }
return &o }
func (it *Optional) CanNext() bool { return false }
func (it *Optional) UID() uint64 {
return it.uid
} }
func (it *Optional) Reset() { func (it *Optional) Reset() {
@ -61,17 +66,23 @@ func (it *Optional) Close() {
it.subIt.Close() it.subIt.Close()
} }
func (it *Optional) Tagger() *graph.Tagger {
return &it.tags
}
func (it *Optional) Clone() graph.Iterator { func (it *Optional) Clone() graph.Iterator {
out := NewOptional(it.subIt.Clone()) out := NewOptional(it.subIt.Clone())
out.CopyTagsFrom(it) out.tags.CopyFrom(it)
return out return out
} }
// Nexting the iterator is unsupported -- error and return an empty set. // DEPRECATED
// (As above, a reasonable alternative would be to Next() an all iterator) func (it *Optional) ResultTree() *graph.ResultTree {
func (it *Optional) Next() (graph.Value, bool) { return graph.NewResultTree(it.Result())
glog.Errorln("Nexting an un-nextable iterator") }
return nil, false
func (it *Optional) Result() graph.Value {
return it.result
} }
// An optional iterator only has a next result if, (a) last time we checked // An optional iterator only has a next result if, (a) last time we checked
@ -84,13 +95,18 @@ func (it *Optional) NextResult() bool {
return false return false
} }
// No subiterators.
func (it *Optional) SubIterators() []graph.Iterator {
return nil
}
// Check() is the real hack of this iterator. It always returns true, regardless // Check() is the real hack of this iterator. It always returns true, regardless
// of whether the subiterator matched. But we keep track of whether the subiterator // of whether the subiterator matched. But we keep track of whether the subiterator
// matched for results purposes. // matched for results purposes.
func (it *Optional) Check(val graph.Value) bool { func (it *Optional) Check(val graph.Value) bool {
checked := it.subIt.Check(val) checked := it.subIt.Check(val)
it.lastCheck = checked it.lastCheck = checked
it.Last = val it.result = val
return true return true
} }
@ -111,7 +127,7 @@ func (it *Optional) DebugString(indent int) string {
return fmt.Sprintf("%s(%s tags:%s\n%s)", return fmt.Sprintf("%s(%s tags:%s\n%s)",
strings.Repeat(" ", indent), strings.Repeat(" ", indent),
it.Type(), it.Type(),
it.Tags(), it.tags.Tags(),
it.subIt.DebugString(indent+4)) it.subIt.DebugString(indent+4))
} }
@ -135,3 +151,8 @@ func (it *Optional) Stats() graph.IteratorStats {
Size: subStats.Size, Size: subStats.Size,
} }
} }
// If you're empty and you know it, clap your hands.
func (it *Optional) Size() (int64, bool) {
return 0, true
}

View file

@ -29,29 +29,34 @@ import (
) )
type Or struct { type Or struct {
Base uid uint64
tags graph.Tagger
isShortCircuiting bool isShortCircuiting bool
internalIterators []graph.Iterator internalIterators []graph.Iterator
itCount int itCount int
currentIterator int currentIterator int
result graph.Value
} }
func NewOr() *Or { func NewOr() *Or {
var or Or return &Or{
BaseInit(&or.Base) uid: NextUID(),
or.internalIterators = make([]graph.Iterator, 0, 20) internalIterators: make([]graph.Iterator, 0, 20),
or.isShortCircuiting = false currentIterator: -1,
or.currentIterator = -1 }
return &or
} }
func NewShortCircuitOr() *Or { func NewShortCircuitOr() *Or {
var or Or return &Or{
BaseInit(&or.Base) uid: NextUID(),
or.internalIterators = make([]graph.Iterator, 0, 20) internalIterators: make([]graph.Iterator, 0, 20),
or.isShortCircuiting = true isShortCircuiting: true,
or.currentIterator = -1 currentIterator: -1,
return &or }
}
func (it *Or) UID() uint64 {
return it.uid
} }
// Reset all internal iterators // Reset all internal iterators
@ -62,6 +67,10 @@ func (it *Or) Reset() {
it.currentIterator = -1 it.currentIterator = -1
} }
func (it *Or) Tagger() *graph.Tagger {
return &it.tags
}
func (it *Or) Clone() graph.Iterator { func (it *Or) Clone() graph.Iterator {
var or *Or var or *Or
if it.isShortCircuiting { if it.isShortCircuiting {
@ -72,7 +81,7 @@ func (it *Or) Clone() graph.Iterator {
for _, sub := range it.internalIterators { for _, sub := range it.internalIterators {
or.AddSubIterator(sub.Clone()) or.AddSubIterator(sub.Clone())
} }
or.CopyTagsFrom(it) or.tags.CopyFrom(it)
return or return or
} }
@ -84,7 +93,14 @@ func (it *Or) SubIterators() []graph.Iterator {
// Overrides BaseIterator TagResults, as it needs to add it's own results and // Overrides BaseIterator TagResults, as it needs to add it's own results and
// recurse down it's subiterators. // recurse down it's subiterators.
func (it *Or) TagResults(dst map[string]graph.Value) { func (it *Or) TagResults(dst map[string]graph.Value) {
it.Base.TagResults(dst) for _, tag := range it.tags.Tags() {
dst[tag] = it.Result()
}
for tag, value := range it.tags.Fixed() {
dst[tag] = value
}
it.internalIterators[it.currentIterator].TagResults(dst) it.internalIterators[it.currentIterator].TagResults(dst)
} }
@ -105,7 +121,7 @@ func (it *Or) DebugString(indent int) string {
total += fmt.Sprintf("%d:\n%s\n", i, sub.DebugString(indent+4)) total += fmt.Sprintf("%d:\n%s\n", i, sub.DebugString(indent+4))
} }
var tags string var tags string
for _, k := range it.Tags() { for _, k := range it.tags.Tags() {
tags += fmt.Sprintf("%s;", k) tags += fmt.Sprintf("%s;", k)
} }
spaces := strings.Repeat(" ", indent+2) spaces := strings.Repeat(" ", indent+2)
@ -139,7 +155,7 @@ func (it *Or) Next() (graph.Value, bool) {
firstTime = true firstTime = true
} }
curIt := it.internalIterators[it.currentIterator] curIt := it.internalIterators[it.currentIterator]
curr, exists = curIt.Next() curr, exists = graph.Next(curIt)
if !exists { if !exists {
if it.isShortCircuiting && !firstTime { if it.isShortCircuiting && !firstTime {
return graph.NextLogOut(it, nil, false) return graph.NextLogOut(it, nil, false)
@ -149,11 +165,15 @@ func (it *Or) Next() (graph.Value, bool) {
return graph.NextLogOut(it, nil, false) return graph.NextLogOut(it, nil, false)
} }
} else { } else {
it.Last = curr it.result = curr
return graph.NextLogOut(it, curr, true) return graph.NextLogOut(it, curr, true)
} }
} }
panic("Somehow broke out of Next() loop in Or") panic("unreachable")
}
func (it *Or) Result() graph.Value {
return it.result
} }
// Checks a value against the iterators, in order. // Checks a value against the iterators, in order.
@ -176,7 +196,7 @@ func (it *Or) Check(val graph.Value) bool {
if !anyGood { if !anyGood {
return graph.CheckLogOut(it, val, false) return graph.CheckLogOut(it, val, false)
} }
it.Last = val it.result = val
return graph.CheckLogOut(it, val, true) return graph.CheckLogOut(it, val, true)
} }
@ -247,7 +267,7 @@ func (it *Or) Optimize() (graph.Iterator, bool) {
} }
// Move the tags hanging on us (like any good replacement). // Move the tags hanging on us (like any good replacement).
newOr.CopyTagsFrom(it) newOr.tags.CopyFrom(it)
// And close ourselves but not our subiterators -- some may still be alive in // And close ourselves but not our subiterators -- some may still be alive in
// the new And (they were unchanged upon calling Optimize() on them, at the // the new And (they were unchanged upon calling Optimize() on them, at the

View file

@ -24,7 +24,7 @@ import (
func iterated(it graph.Iterator) []int { func iterated(it graph.Iterator) []int {
var res []int var res []int
for { for {
val, ok := it.Next() val, ok := graph.Next(it)
if !ok { if !ok {
break break
} }

View file

@ -16,6 +16,7 @@ package iterator
import ( import (
"github.com/google/cayley/graph" "github.com/google/cayley/graph"
"github.com/google/cayley/quad"
) )
type Node struct { type Node struct {
@ -39,7 +40,7 @@ type queryShape struct {
ts graph.TripleStore ts graph.TripleStore
nodeId int nodeId int
hasaIds []int hasaIds []int
hasaDirs []graph.Direction hasaDirs []quad.Direction
} }
func OutputQueryShapeForIterator(it graph.Iterator, ts graph.TripleStore, outputMap map[string]interface{}) { func OutputQueryShapeForIterator(it graph.Iterator, ts graph.TripleStore, outputMap map[string]interface{}) {
@ -62,11 +63,11 @@ func (qs *queryShape) AddLink(l *Link) {
qs.links = append(qs.links, *l) qs.links = append(qs.links, *l)
} }
func (qs *queryShape) LastHasa() (int, graph.Direction) { func (qs *queryShape) LastHasa() (int, quad.Direction) {
return qs.hasaIds[len(qs.hasaIds)-1], qs.hasaDirs[len(qs.hasaDirs)-1] return qs.hasaIds[len(qs.hasaIds)-1], qs.hasaDirs[len(qs.hasaDirs)-1]
} }
func (qs *queryShape) PushHasa(i int, d graph.Direction) { func (qs *queryShape) PushHasa(i int, d quad.Direction) {
qs.hasaIds = append(qs.hasaIds, i) qs.hasaIds = append(qs.hasaIds, i)
qs.hasaDirs = append(qs.hasaDirs, d) qs.hasaDirs = append(qs.hasaDirs, d)
} }
@ -107,10 +108,10 @@ func (qs *queryShape) StealNode(left *Node, right *Node) {
func (qs *queryShape) MakeNode(it graph.Iterator) *Node { func (qs *queryShape) MakeNode(it graph.Iterator) *Node {
n := Node{Id: qs.nodeId} n := Node{Id: qs.nodeId}
for _, tag := range it.Tags() { for _, tag := range it.Tagger().Tags() {
n.Tags = append(n.Tags, tag) n.Tags = append(n.Tags, tag)
} }
for k, _ := range it.FixedTags() { for k, _ := range it.Tagger().Fixed() {
n.Tags = append(n.Tags, k) n.Tags = append(n.Tags, k)
} }
@ -129,7 +130,7 @@ func (qs *queryShape) MakeNode(it graph.Iterator) *Node {
case graph.Fixed: case graph.Fixed:
n.IsFixed = true n.IsFixed = true
for { for {
val, more := it.Next() val, more := graph.Next(it)
if !more { if !more {
break break
} }
@ -159,10 +160,10 @@ func (qs *queryShape) MakeNode(it graph.Iterator) *Node {
qs.nodeId++ qs.nodeId++
newNode := qs.MakeNode(lto.primaryIt) newNode := qs.MakeNode(lto.primaryIt)
hasaID, hasaDir := qs.LastHasa() hasaID, hasaDir := qs.LastHasa()
if (hasaDir == graph.Subject && lto.dir == graph.Object) || if (hasaDir == quad.Subject && lto.dir == quad.Object) ||
(hasaDir == graph.Object && lto.dir == graph.Subject) { (hasaDir == quad.Object && lto.dir == quad.Subject) {
qs.AddNode(newNode) qs.AddNode(newNode)
if hasaDir == graph.Subject { if hasaDir == quad.Subject {
qs.AddLink(&Link{hasaID, newNode.Id, 0, n.Id}) qs.AddLink(&Link{hasaID, newNode.Id, 0, n.Id})
} else { } else {
qs.AddLink(&Link{newNode.Id, hasaID, 0, n.Id}) qs.AddLink(&Link{newNode.Id, hasaID, 0, n.Id})

View file

@ -19,6 +19,7 @@ import (
"testing" "testing"
"github.com/google/cayley/graph" "github.com/google/cayley/graph"
"github.com/google/cayley/quad"
) )
func hasaWithTag(ts graph.TripleStore, tag string, target string) *HasA { func hasaWithTag(ts graph.TripleStore, tag string, target string) *HasA {
@ -26,14 +27,14 @@ func hasaWithTag(ts graph.TripleStore, tag string, target string) *HasA {
obj := ts.FixedIterator() obj := ts.FixedIterator()
obj.Add(ts.ValueOf(target)) obj.Add(ts.ValueOf(target))
obj.AddTag(tag) obj.Tagger().Add(tag)
and.AddSubIterator(NewLinksTo(ts, obj, graph.Object)) and.AddSubIterator(NewLinksTo(ts, obj, quad.Object))
pred := ts.FixedIterator() pred := ts.FixedIterator()
pred.Add(ts.ValueOf("status")) pred.Add(ts.ValueOf("status"))
and.AddSubIterator(NewLinksTo(ts, pred, graph.Predicate)) and.AddSubIterator(NewLinksTo(ts, pred, quad.Predicate))
return NewHasA(ts, and, graph.Subject) return NewHasA(ts, and, quad.Subject)
} }
func TestQueryShape(t *testing.T) { func TestQueryShape(t *testing.T) {
@ -48,7 +49,7 @@ func TestQueryShape(t *testing.T) {
// Given a single linkage iterator's shape. // Given a single linkage iterator's shape.
hasa := hasaWithTag(ts, "tag", "cool") hasa := hasaWithTag(ts, "tag", "cool")
hasa.AddTag("top") hasa.Tagger().Add("top")
shape := make(map[string]interface{}) shape := make(map[string]interface{})
OutputQueryShapeForIterator(hasa, ts, shape) OutputQueryShapeForIterator(hasa, ts, shape)
@ -93,22 +94,22 @@ func TestQueryShape(t *testing.T) {
andInternal := NewAnd() andInternal := NewAnd()
hasa1 := hasaWithTag(ts, "tag1", "cool") hasa1 := hasaWithTag(ts, "tag1", "cool")
hasa1.AddTag("hasa1") hasa1.Tagger().Add("hasa1")
andInternal.AddSubIterator(hasa1) andInternal.AddSubIterator(hasa1)
hasa2 := hasaWithTag(ts, "tag2", "fun") hasa2 := hasaWithTag(ts, "tag2", "fun")
hasa2.AddTag("hasa2") hasa2.Tagger().Add("hasa2")
andInternal.AddSubIterator(hasa2) andInternal.AddSubIterator(hasa2)
pred := ts.FixedIterator() pred := ts.FixedIterator()
pred.Add(ts.ValueOf("name")) pred.Add(ts.ValueOf("name"))
and := NewAnd() and := NewAnd()
and.AddSubIterator(NewLinksTo(ts, andInternal, graph.Subject)) and.AddSubIterator(NewLinksTo(ts, andInternal, quad.Subject))
and.AddSubIterator(NewLinksTo(ts, pred, graph.Predicate)) and.AddSubIterator(NewLinksTo(ts, pred, quad.Predicate))
shape = make(map[string]interface{}) shape = make(map[string]interface{})
OutputQueryShapeForIterator(NewHasA(ts, and, graph.Object), ts, shape) OutputQueryShapeForIterator(NewHasA(ts, and, quad.Object), ts, shape)
links = shape["links"].([]Link) links = shape["links"].([]Link)
if len(links) != 3 { if len(links) != 3 {

View file

@ -17,7 +17,7 @@ package iterator
// "Value Comparison" is a unary operator -- a filter across the values in the // "Value Comparison" is a unary operator -- a filter across the values in the
// relevant subiterator. // relevant subiterator.
// //
// This is hugely useful for things like provenance, but value ranges in general // This is hugely useful for things like label, but value ranges in general
// come up from time to time. At *worst* we're as big as our underlying iterator. // come up from time to time. At *worst* we're as big as our underlying iterator.
// At best, we're the null iterator. // At best, we're the null iterator.
// //
@ -46,21 +46,27 @@ const (
) )
type Comparison struct { type Comparison struct {
Base uid uint64
subIt graph.Iterator tags graph.Tagger
op Operator subIt graph.Iterator
val interface{} op Operator
ts graph.TripleStore val interface{}
ts graph.TripleStore
result graph.Value
} }
func NewComparison(sub graph.Iterator, op Operator, val interface{}, ts graph.TripleStore) *Comparison { func NewComparison(sub graph.Iterator, op Operator, val interface{}, ts graph.TripleStore) *Comparison {
var vc Comparison return &Comparison{
BaseInit(&vc.Base) uid: NextUID(),
vc.subIt = sub subIt: sub,
vc.op = op op: op,
vc.val = val val: val,
vc.ts = ts ts: ts,
return &vc }
}
func (it *Comparison) UID() uint64 {
return it.uid
} }
// Here's the non-boilerplate part of the ValueComparison iterator. Given a value // Here's the non-boilerplate part of the ValueComparison iterator. Given a value
@ -111,9 +117,13 @@ func (it *Comparison) Reset() {
it.subIt.Reset() it.subIt.Reset()
} }
func (it *Comparison) Tagger() *graph.Tagger {
return &it.tags
}
func (it *Comparison) Clone() graph.Iterator { func (it *Comparison) Clone() graph.Iterator {
out := NewComparison(it.subIt.Clone(), it.op, it.val, it.ts) out := NewComparison(it.subIt.Clone(), it.op, it.val, it.ts)
out.CopyTagsFrom(it) out.tags.CopyFrom(it)
return out return out
} }
@ -121,7 +131,7 @@ func (it *Comparison) Next() (graph.Value, bool) {
var val graph.Value var val graph.Value
var ok bool var ok bool
for { for {
val, ok = it.subIt.Next() val, ok = graph.Next(it.subIt)
if !ok { if !ok {
return nil, false return nil, false
} }
@ -129,10 +139,19 @@ func (it *Comparison) Next() (graph.Value, bool) {
break break
} }
} }
it.Last = val it.result = val
return val, ok return val, ok
} }
// DEPRECATED
func (it *Comparison) ResultTree() *graph.ResultTree {
return graph.NewResultTree(it.Result())
}
func (it *Comparison) Result() graph.Value {
return it.result
}
func (it *Comparison) NextResult() bool { func (it *Comparison) NextResult() bool {
for { for {
hasNext := it.subIt.NextResult() hasNext := it.subIt.NextResult()
@ -143,10 +162,15 @@ func (it *Comparison) NextResult() bool {
return true return true
} }
} }
it.Last = it.subIt.Result() it.result = it.subIt.Result()
return true return true
} }
// No subiterators.
func (it *Comparison) SubIterators() []graph.Iterator {
return nil
}
func (it *Comparison) Check(val graph.Value) bool { func (it *Comparison) Check(val graph.Value) bool {
if !it.doComparison(val) { if !it.doComparison(val) {
return false return false
@ -157,7 +181,14 @@ func (it *Comparison) Check(val graph.Value) bool {
// If we failed the check, then the subiterator should not contribute to the result // If we failed the check, then the subiterator should not contribute to the result
// set. Otherwise, go ahead and tag it. // set. Otherwise, go ahead and tag it.
func (it *Comparison) TagResults(dst map[string]graph.Value) { func (it *Comparison) TagResults(dst map[string]graph.Value) {
it.Base.TagResults(dst) for _, tag := range it.tags.Tags() {
dst[tag] = it.Result()
}
for tag, value := range it.tags.Fixed() {
dst[tag] = value
}
it.subIt.TagResults(dst) it.subIt.TagResults(dst)
} }
@ -188,3 +219,7 @@ func (it *Comparison) Optimize() (graph.Iterator, bool) {
func (it *Comparison) Stats() graph.IteratorStats { func (it *Comparison) Stats() graph.IteratorStats {
return it.subIt.Stats() return it.subIt.Stats()
} }
func (it *Comparison) Size() (int64, bool) {
return 0, true
}

View file

@ -24,36 +24,51 @@ import (
"github.com/google/cayley/graph" "github.com/google/cayley/graph"
"github.com/google/cayley/graph/iterator" "github.com/google/cayley/graph/iterator"
"github.com/google/cayley/quad"
) )
type AllIterator struct { type AllIterator struct {
iterator.Base uid uint64
tags graph.Tagger
prefix []byte prefix []byte
dir graph.Direction dir quad.Direction
open bool open bool
iter ldbit.Iterator iter ldbit.Iterator
ts *TripleStore ts *TripleStore
ro *opt.ReadOptions ro *opt.ReadOptions
result graph.Value
} }
func NewAllIterator(prefix string, d graph.Direction, ts *TripleStore) *AllIterator { func NewAllIterator(prefix string, d quad.Direction, ts *TripleStore) *AllIterator {
var it AllIterator opts := &opt.ReadOptions{
iterator.BaseInit(&it.Base) DontFillCache: true,
it.ro = &opt.ReadOptions{} }
it.ro.DontFillCache = true
it.iter = ts.db.NewIterator(nil, it.ro) it := AllIterator{
it.prefix = []byte(prefix) uid: iterator.NextUID(),
it.dir = d ro: opts,
it.open = true iter: ts.db.NewIterator(nil, opts),
it.ts = ts prefix: []byte(prefix),
dir: d,
open: true,
ts: ts,
}
it.iter.Seek(it.prefix) it.iter.Seek(it.prefix)
if !it.iter.Valid() { if !it.iter.Valid() {
// FIXME(kortschak) What are the semantics here? Is this iterator usable?
// If not, we should return nil *Iterator and an error.
it.open = false it.open = false
it.iter.Release() it.iter.Release()
} }
return &it return &it
} }
func (it *AllIterator) UID() uint64 {
return it.uid
}
func (it *AllIterator) Reset() { func (it *AllIterator) Reset() {
if !it.open { if !it.open {
it.iter = it.ts.db.NewIterator(nil, it.ro) it.iter = it.ts.db.NewIterator(nil, it.ro)
@ -66,15 +81,29 @@ func (it *AllIterator) Reset() {
} }
} }
func (it *AllIterator) Tagger() *graph.Tagger {
return &it.tags
}
func (it *AllIterator) TagResults(dst map[string]graph.Value) {
for _, tag := range it.tags.Tags() {
dst[tag] = it.Result()
}
for tag, value := range it.tags.Fixed() {
dst[tag] = value
}
}
func (it *AllIterator) Clone() graph.Iterator { func (it *AllIterator) Clone() graph.Iterator {
out := NewAllIterator(string(it.prefix), it.dir, it.ts) out := NewAllIterator(string(it.prefix), it.dir, it.ts)
out.CopyTagsFrom(it) out.tags.CopyFrom(it)
return out return out
} }
func (it *AllIterator) Next() (graph.Value, bool) { func (it *AllIterator) Next() (graph.Value, bool) {
if !it.open { if !it.open {
it.Last = nil it.result = nil
return nil, false return nil, false
} }
var out []byte var out []byte
@ -88,12 +117,29 @@ func (it *AllIterator) Next() (graph.Value, bool) {
it.Close() it.Close()
return nil, false return nil, false
} }
it.Last = out it.result = out
return out, true return out, true
} }
func (it *AllIterator) ResultTree() *graph.ResultTree {
return graph.NewResultTree(it.Result())
}
func (it *AllIterator) Result() graph.Value {
return it.result
}
func (it *AllIterator) NextResult() bool {
return false
}
// No subiterators.
func (it *AllIterator) SubIterators() []graph.Iterator {
return nil
}
func (it *AllIterator) Check(v graph.Value) bool { func (it *AllIterator) Check(v graph.Value) bool {
it.Last = v it.result = v
return true return true
} }
@ -115,7 +161,7 @@ func (it *AllIterator) Size() (int64, bool) {
func (it *AllIterator) DebugString(indent int) string { func (it *AllIterator) DebugString(indent int) string {
size, _ := it.Size() size, _ := it.Size()
return fmt.Sprintf("%s(%s tags: %v leveldb size:%d %s %p)", strings.Repeat(" ", indent), it.Type(), it.Tags(), size, it.dir, it) return fmt.Sprintf("%s(%s tags: %v leveldb size:%d %s %p)", strings.Repeat(" ", indent), it.Type(), it.tags.Tags(), size, it.dir, it)
} }
func (it *AllIterator) Type() graph.Type { return graph.All } func (it *AllIterator) Type() graph.Type { return graph.All }

View file

@ -24,45 +24,63 @@ import (
"github.com/google/cayley/graph" "github.com/google/cayley/graph"
"github.com/google/cayley/graph/iterator" "github.com/google/cayley/graph/iterator"
"github.com/google/cayley/quad"
) )
type Iterator struct { type Iterator struct {
iterator.Base uid uint64
tags graph.Tagger
nextPrefix []byte nextPrefix []byte
checkId []byte checkId []byte
dir graph.Direction dir quad.Direction
open bool open bool
iter ldbit.Iterator iter ldbit.Iterator
ts *TripleStore qs *TripleStore
ro *opt.ReadOptions ro *opt.ReadOptions
originalPrefix string originalPrefix string
result graph.Value
} }
func NewIterator(prefix string, d graph.Direction, value graph.Value, ts *TripleStore) *Iterator { func NewIterator(prefix string, d quad.Direction, value graph.Value, qs *TripleStore) *Iterator {
var it Iterator vb := value.([]byte)
iterator.BaseInit(&it.Base) p := make([]byte, 0, 2+qs.hasher.Size())
it.checkId = value.([]byte) p = append(p, []byte(prefix)...)
it.dir = d p = append(p, []byte(vb[1:])...)
it.originalPrefix = prefix
it.nextPrefix = make([]byte, 0, 2+ts.hasher.Size()) opts := &opt.ReadOptions{
it.nextPrefix = append(it.nextPrefix, []byte(prefix)...) DontFillCache: true,
it.nextPrefix = append(it.nextPrefix, []byte(it.checkId[1:])...) }
it.ro = &opt.ReadOptions{}
it.ro.DontFillCache = true it := Iterator{
it.iter = ts.db.NewIterator(nil, it.ro) uid: iterator.NextUID(),
it.open = true nextPrefix: p,
it.ts = ts checkId: vb,
dir: d,
originalPrefix: prefix,
ro: opts,
iter: qs.db.NewIterator(nil, opts),
open: true,
qs: qs,
}
ok := it.iter.Seek(it.nextPrefix) ok := it.iter.Seek(it.nextPrefix)
if !ok { if !ok {
// FIXME(kortschak) What are the semantics here? Is this iterator usable?
// If not, we should return nil *Iterator and an error.
it.open = false it.open = false
it.iter.Release() it.iter.Release()
} }
return &it return &it
} }
func (it *Iterator) UID() uint64 {
return it.uid
}
func (it *Iterator) Reset() { func (it *Iterator) Reset() {
if !it.open { if !it.open {
it.iter = it.ts.db.NewIterator(nil, it.ro) it.iter = it.qs.db.NewIterator(nil, it.ro)
it.open = true it.open = true
} }
ok := it.iter.Seek(it.nextPrefix) ok := it.iter.Seek(it.nextPrefix)
@ -72,9 +90,23 @@ func (it *Iterator) Reset() {
} }
} }
func (it *Iterator) Tagger() *graph.Tagger {
return &it.tags
}
func (it *Iterator) TagResults(dst map[string]graph.Value) {
for _, tag := range it.tags.Tags() {
dst[tag] = it.Result()
}
for tag, value := range it.tags.Fixed() {
dst[tag] = value
}
}
func (it *Iterator) Clone() graph.Iterator { func (it *Iterator) Clone() graph.Iterator {
out := NewIterator(it.originalPrefix, it.dir, it.checkId, it.ts) out := NewIterator(it.originalPrefix, it.dir, it.checkId, it.qs)
out.CopyTagsFrom(it) out.tags.CopyFrom(it)
return out return out
} }
@ -87,22 +119,22 @@ func (it *Iterator) Close() {
func (it *Iterator) Next() (graph.Value, bool) { func (it *Iterator) Next() (graph.Value, bool) {
if it.iter == nil { if it.iter == nil {
it.Last = nil it.result = nil
return nil, false return nil, false
} }
if !it.open { if !it.open {
it.Last = nil it.result = nil
return nil, false return nil, false
} }
if !it.iter.Valid() { if !it.iter.Valid() {
it.Last = nil it.result = nil
it.Close() it.Close()
return nil, false return nil, false
} }
if bytes.HasPrefix(it.iter.Key(), it.nextPrefix) { if bytes.HasPrefix(it.iter.Key(), it.nextPrefix) {
out := make([]byte, len(it.iter.Key())) out := make([]byte, len(it.iter.Key()))
copy(out, it.iter.Key()) copy(out, it.iter.Key())
it.Last = out it.result = out
ok := it.iter.Next() ok := it.iter.Next()
if !ok { if !ok {
it.Close() it.Close()
@ -110,56 +142,73 @@ func (it *Iterator) Next() (graph.Value, bool) {
return out, true return out, true
} }
it.Close() it.Close()
it.Last = nil it.result = nil
return nil, false return nil, false
} }
func PositionOf(prefix []byte, d graph.Direction, ts *TripleStore) int { func (it *Iterator) ResultTree() *graph.ResultTree {
return graph.NewResultTree(it.Result())
}
func (it *Iterator) Result() graph.Value {
return it.result
}
func (it *Iterator) NextResult() bool {
return false
}
// No subiterators.
func (it *Iterator) SubIterators() []graph.Iterator {
return nil
}
func PositionOf(prefix []byte, d quad.Direction, qs *TripleStore) int {
if bytes.Equal(prefix, []byte("sp")) { if bytes.Equal(prefix, []byte("sp")) {
switch d { switch d {
case graph.Subject: case quad.Subject:
return 2 return 2
case graph.Predicate: case quad.Predicate:
return ts.hasher.Size() + 2 return qs.hasher.Size() + 2
case graph.Object: case quad.Object:
return 2*ts.hasher.Size() + 2 return 2*qs.hasher.Size() + 2
case graph.Provenance: case quad.Label:
return -1 return -1
} }
} }
if bytes.Equal(prefix, []byte("po")) { if bytes.Equal(prefix, []byte("po")) {
switch d { switch d {
case graph.Subject: case quad.Subject:
return 2*ts.hasher.Size() + 2 return 2*qs.hasher.Size() + 2
case graph.Predicate: case quad.Predicate:
return 2 return 2
case graph.Object: case quad.Object:
return ts.hasher.Size() + 2 return qs.hasher.Size() + 2
case graph.Provenance: case quad.Label:
return -1 return -1
} }
} }
if bytes.Equal(prefix, []byte("os")) { if bytes.Equal(prefix, []byte("os")) {
switch d { switch d {
case graph.Subject: case quad.Subject:
return ts.hasher.Size() + 2 return qs.hasher.Size() + 2
case graph.Predicate: case quad.Predicate:
return 2*ts.hasher.Size() + 2 return 2*qs.hasher.Size() + 2
case graph.Object: case quad.Object:
return 2 return 2
case graph.Provenance: case quad.Label:
return -1 return -1
} }
} }
if bytes.Equal(prefix, []byte("cp")) { if bytes.Equal(prefix, []byte("cp")) {
switch d { switch d {
case graph.Subject: case quad.Subject:
return 2*ts.hasher.Size() + 2 return 2*qs.hasher.Size() + 2
case graph.Predicate: case quad.Predicate:
return ts.hasher.Size() + 2 return qs.hasher.Size() + 2
case graph.Object: case quad.Object:
return 3*ts.hasher.Size() + 2 return 3*qs.hasher.Size() + 2
case graph.Provenance: case quad.Label:
return 2 return 2
} }
} }
@ -171,14 +220,14 @@ func (it *Iterator) Check(v graph.Value) bool {
if val[0] == 'z' { if val[0] == 'z' {
return false return false
} }
offset := PositionOf(val[0:2], it.dir, it.ts) offset := PositionOf(val[0:2], it.dir, it.qs)
if offset != -1 { if offset != -1 {
if bytes.HasPrefix(val[offset:], it.checkId[1:]) { if bytes.HasPrefix(val[offset:], it.checkId[1:]) {
return true return true
} }
} else { } else {
nameForDir := it.ts.Triple(v).Get(it.dir) nameForDir := it.qs.Quad(v).Get(it.dir)
hashForDir := it.ts.ValueOf(nameForDir).([]byte) hashForDir := it.qs.ValueOf(nameForDir).([]byte)
if bytes.Equal(hashForDir, it.checkId) { if bytes.Equal(hashForDir, it.checkId) {
return true return true
} }
@ -187,12 +236,12 @@ func (it *Iterator) Check(v graph.Value) bool {
} }
func (it *Iterator) Size() (int64, bool) { func (it *Iterator) Size() (int64, bool) {
return it.ts.SizeOf(it.checkId), true return it.qs.SizeOf(it.checkId), true
} }
func (it *Iterator) DebugString(indent int) string { func (it *Iterator) DebugString(indent int) string {
size, _ := it.Size() size, _ := it.Size()
return fmt.Sprintf("%s(%s %d tags: %v dir: %s size:%d %s)", strings.Repeat(" ", indent), it.Type(), it.UID(), it.Tags(), it.dir, size, it.ts.NameOf(it.checkId)) return fmt.Sprintf("%s(%s %d tags: %v dir: %s size:%d %s)", strings.Repeat(" ", indent), it.Type(), it.UID(), it.tags.Tags(), it.dir, size, it.qs.NameOf(it.checkId))
} }
var levelDBType graph.Type var levelDBType graph.Type

View file

@ -23,10 +23,11 @@ import (
"github.com/google/cayley/graph" "github.com/google/cayley/graph"
"github.com/google/cayley/graph/iterator" "github.com/google/cayley/graph/iterator"
"github.com/google/cayley/quad"
) )
func makeTripleSet() []*graph.Triple { func makeTripleSet() []*quad.Quad {
tripleSet := []*graph.Triple{ tripleSet := []*quad.Quad{
{"A", "follows", "B", ""}, {"A", "follows", "B", ""},
{"C", "follows", "B", ""}, {"C", "follows", "B", ""},
{"C", "follows", "D", ""}, {"C", "follows", "D", ""},
@ -42,20 +43,20 @@ func makeTripleSet() []*graph.Triple {
return tripleSet return tripleSet
} }
func iteratedTriples(ts graph.TripleStore, it graph.Iterator) []*graph.Triple { func iteratedTriples(qs graph.TripleStore, it graph.Iterator) []*quad.Quad {
var res ordered var res ordered
for { for {
val, ok := it.Next() val, ok := graph.Next(it)
if !ok { if !ok {
break break
} }
res = append(res, ts.Triple(val)) res = append(res, qs.Quad(val))
} }
sort.Sort(res) sort.Sort(res)
return res return res
} }
type ordered []*graph.Triple type ordered []*quad.Quad
func (o ordered) Len() int { return len(o) } func (o ordered) Len() int { return len(o) }
func (o ordered) Less(i, j int) bool { func (o ordered) Less(i, j int) bool {
@ -72,7 +73,7 @@ func (o ordered) Less(i, j int) bool {
o[i].Subject == o[j].Subject && o[i].Subject == o[j].Subject &&
o[i].Predicate == o[j].Predicate && o[i].Predicate == o[j].Predicate &&
o[i].Object == o[j].Object && o[i].Object == o[j].Object &&
o[i].Provenance < o[j].Provenance: o[i].Label < o[j].Label:
return true return true
@ -82,14 +83,14 @@ func (o ordered) Less(i, j int) bool {
} }
func (o ordered) Swap(i, j int) { o[i], o[j] = o[j], o[i] } func (o ordered) Swap(i, j int) { o[i], o[j] = o[j], o[i] }
func iteratedNames(ts graph.TripleStore, it graph.Iterator) []string { func iteratedNames(qs graph.TripleStore, it graph.Iterator) []string {
var res []string var res []string
for { for {
val, ok := it.Next() val, ok := graph.Next(it)
if !ok { if !ok {
break break
} }
res = append(res, ts.NameOf(val)) res = append(res, qs.NameOf(val))
} }
sort.Strings(res) sort.Strings(res)
return res return res
@ -107,14 +108,14 @@ func TestCreateDatabase(t *testing.T) {
t.Fatal("Failed to create LevelDB database.") t.Fatal("Failed to create LevelDB database.")
} }
ts, err := newTripleStore(tmpDir, nil) qs, err := newTripleStore(tmpDir, nil)
if ts == nil || err != nil { if qs == nil || err != nil {
t.Error("Failed to create leveldb TripleStore.") t.Error("Failed to create leveldb TripleStore.")
} }
if s := ts.Size(); s != 0 { if s := qs.Size(); s != 0 {
t.Errorf("Unexpected size, got:%d expected:0", s) t.Errorf("Unexpected size, got:%d expected:0", s)
} }
ts.Close() qs.Close()
err = createNewLevelDB("/dev/null/some terrible path", nil) err = createNewLevelDB("/dev/null/some terrible path", nil)
if err == nil { if err == nil {
@ -137,53 +138,53 @@ func TestLoadDatabase(t *testing.T) {
t.Fatal("Failed to create LevelDB database.") t.Fatal("Failed to create LevelDB database.")
} }
ts, err := newTripleStore(tmpDir, nil) qs, err := newTripleStore(tmpDir, nil)
if ts == nil || err != nil { if qs == nil || err != nil {
t.Error("Failed to create leveldb TripleStore.") t.Error("Failed to create leveldb TripleStore.")
} }
ts.AddTriple(&graph.Triple{"Something", "points_to", "Something Else", "context"}) qs.AddTriple(&quad.Quad{"Something", "points_to", "Something Else", "context"})
for _, pq := range []string{"Something", "points_to", "Something Else", "context"} { for _, pq := range []string{"Something", "points_to", "Something Else", "context"} {
if got := ts.NameOf(ts.ValueOf(pq)); got != pq { if got := qs.NameOf(qs.ValueOf(pq)); got != pq {
t.Errorf("Failed to roundtrip %q, got:%q expect:%q", pq, got, pq) t.Errorf("Failed to roundtrip %q, got:%q expect:%q", pq, got, pq)
} }
} }
if s := ts.Size(); s != 1 { if s := qs.Size(); s != 1 {
t.Errorf("Unexpected triplestore size, got:%d expect:1", s) t.Errorf("Unexpected triplestore size, got:%d expect:1", s)
} }
ts.Close() qs.Close()
err = createNewLevelDB(tmpDir, nil) err = createNewLevelDB(tmpDir, nil)
if err != nil { if err != nil {
t.Fatal("Failed to create LevelDB database.") t.Fatal("Failed to create LevelDB database.")
} }
ts, err = newTripleStore(tmpDir, nil) qs, err = newTripleStore(tmpDir, nil)
if ts == nil || err != nil { if qs == nil || err != nil {
t.Error("Failed to create leveldb TripleStore.") t.Error("Failed to create leveldb TripleStore.")
} }
ts2, didConvert := ts.(*TripleStore) ts2, didConvert := qs.(*TripleStore)
if !didConvert { if !didConvert {
t.Errorf("Could not convert from generic to LevelDB TripleStore") t.Errorf("Could not convert from generic to LevelDB TripleStore")
} }
ts.AddTripleSet(makeTripleSet()) qs.AddTripleSet(makeTripleSet())
if s := ts.Size(); s != 11 { if s := qs.Size(); s != 11 {
t.Errorf("Unexpected triplestore size, got:%d expect:11", s) t.Errorf("Unexpected triplestore size, got:%d expect:11", s)
} }
if s := ts2.SizeOf(ts.ValueOf("B")); s != 5 { if s := ts2.SizeOf(qs.ValueOf("B")); s != 5 {
t.Errorf("Unexpected triplestore size, got:%d expect:5", s) t.Errorf("Unexpected triplestore size, got:%d expect:5", s)
} }
ts.RemoveTriple(&graph.Triple{"A", "follows", "B", ""}) qs.RemoveTriple(&quad.Quad{"A", "follows", "B", ""})
if s := ts.Size(); s != 10 { if s := qs.Size(); s != 10 {
t.Errorf("Unexpected triplestore size after RemoveTriple, got:%d expect:10", s) t.Errorf("Unexpected triplestore size after RemoveTriple, got:%d expect:10", s)
} }
if s := ts2.SizeOf(ts.ValueOf("B")); s != 4 { if s := ts2.SizeOf(qs.ValueOf("B")); s != 4 {
t.Errorf("Unexpected triplestore size, got:%d expect:4", s) t.Errorf("Unexpected triplestore size, got:%d expect:4", s)
} }
ts.Close() qs.Close()
} }
func TestIterator(t *testing.T) { func TestIterator(t *testing.T) {
@ -199,14 +200,14 @@ func TestIterator(t *testing.T) {
t.Fatal("Failed to create LevelDB database.") t.Fatal("Failed to create LevelDB database.")
} }
ts, err := newTripleStore(tmpDir, nil) qs, err := newTripleStore(tmpDir, nil)
if ts == nil || err != nil { if qs == nil || err != nil {
t.Error("Failed to create leveldb TripleStore.") t.Error("Failed to create leveldb TripleStore.")
} }
ts.AddTripleSet(makeTripleSet()) qs.AddTripleSet(makeTripleSet())
var it graph.Iterator var it graph.Iterator
it = ts.NodesAllIterator() it = qs.NodesAllIterator()
if it == nil { if it == nil {
t.Fatal("Got nil iterator.") t.Fatal("Got nil iterator.")
} }
@ -241,7 +242,7 @@ func TestIterator(t *testing.T) {
} }
sort.Strings(expect) sort.Strings(expect)
for i := 0; i < 2; i++ { for i := 0; i < 2; i++ {
got := iteratedNames(ts, it) got := iteratedNames(qs, it)
sort.Strings(got) sort.Strings(got)
if !reflect.DeepEqual(got, expect) { if !reflect.DeepEqual(got, expect) {
t.Errorf("Unexpected iterated result on repeat %d, got:%v expect:%v", i, got, expect) t.Errorf("Unexpected iterated result on repeat %d, got:%v expect:%v", i, got, expect)
@ -250,23 +251,23 @@ func TestIterator(t *testing.T) {
} }
for _, pq := range expect { for _, pq := range expect {
if !it.Check(ts.ValueOf(pq)) { if !it.Check(qs.ValueOf(pq)) {
t.Errorf("Failed to find and check %q correctly", pq) t.Errorf("Failed to find and check %q correctly", pq)
} }
} }
// FIXME(kortschak) Why does this fail? // FIXME(kortschak) Why does this fail?
/* /*
for _, pq := range []string{"baller"} { for _, pq := range []string{"baller"} {
if it.Check(ts.ValueOf(pq)) { if it.Check(qs.ValueOf(pq)) {
t.Errorf("Failed to check %q correctly", pq) t.Errorf("Failed to check %q correctly", pq)
} }
} }
*/ */
it.Reset() it.Reset()
it = ts.TriplesAllIterator() it = qs.TriplesAllIterator()
edge, _ := it.Next() edge, _ := graph.Next(it)
triple := ts.Triple(edge) triple := qs.Quad(edge)
set := makeTripleSet() set := makeTripleSet()
var ok bool var ok bool
for _, t := range set { for _, t := range set {
@ -279,7 +280,7 @@ func TestIterator(t *testing.T) {
t.Errorf("Failed to find %q during iteration, got:%q", triple, set) t.Errorf("Failed to find %q during iteration, got:%q", triple, set)
} }
ts.Close() qs.Close()
} }
func TestSetIterator(t *testing.T) { func TestSetIterator(t *testing.T) {
@ -292,95 +293,95 @@ func TestSetIterator(t *testing.T) {
t.Fatalf("Failed to create working directory") t.Fatalf("Failed to create working directory")
} }
ts, err := newTripleStore(tmpDir, nil) qs, err := newTripleStore(tmpDir, nil)
if ts == nil || err != nil { if qs == nil || err != nil {
t.Error("Failed to create leveldb TripleStore.") t.Error("Failed to create leveldb TripleStore.")
} }
defer ts.Close() defer qs.Close()
ts.AddTripleSet(makeTripleSet()) qs.AddTripleSet(makeTripleSet())
expect := []*graph.Triple{ expect := []*quad.Quad{
{"C", "follows", "B", ""}, {"C", "follows", "B", ""},
{"C", "follows", "D", ""}, {"C", "follows", "D", ""},
} }
sort.Sort(ordered(expect)) sort.Sort(ordered(expect))
// Subject iterator. // Subject iterator.
it := ts.TripleIterator(graph.Subject, ts.ValueOf("C")) it := qs.TripleIterator(quad.Subject, qs.ValueOf("C"))
if got := iteratedTriples(ts, it); !reflect.DeepEqual(got, expect) { if got := iteratedTriples(qs, it); !reflect.DeepEqual(got, expect) {
t.Errorf("Failed to get expected results, got:%v expect:%v", got, expect) t.Errorf("Failed to get expected results, got:%v expect:%v", got, expect)
} }
it.Reset() it.Reset()
and := iterator.NewAnd() and := iterator.NewAnd()
and.AddSubIterator(ts.TriplesAllIterator()) and.AddSubIterator(qs.TriplesAllIterator())
and.AddSubIterator(it) and.AddSubIterator(it)
if got := iteratedTriples(ts, and); !reflect.DeepEqual(got, expect) { if got := iteratedTriples(qs, and); !reflect.DeepEqual(got, expect) {
t.Errorf("Failed to get confirm expected results, got:%v expect:%v", got, expect) t.Errorf("Failed to get confirm expected results, got:%v expect:%v", got, expect)
} }
// Object iterator. // Object iterator.
it = ts.TripleIterator(graph.Object, ts.ValueOf("F")) it = qs.TripleIterator(quad.Object, qs.ValueOf("F"))
expect = []*graph.Triple{ expect = []*quad.Quad{
{"B", "follows", "F", ""}, {"B", "follows", "F", ""},
{"E", "follows", "F", ""}, {"E", "follows", "F", ""},
} }
sort.Sort(ordered(expect)) sort.Sort(ordered(expect))
if got := iteratedTriples(ts, it); !reflect.DeepEqual(got, expect) { if got := iteratedTriples(qs, it); !reflect.DeepEqual(got, expect) {
t.Errorf("Failed to get expected results, got:%v expect:%v", got, expect) t.Errorf("Failed to get expected results, got:%v expect:%v", got, expect)
} }
and = iterator.NewAnd() and = iterator.NewAnd()
and.AddSubIterator(ts.TripleIterator(graph.Subject, ts.ValueOf("B"))) and.AddSubIterator(qs.TripleIterator(quad.Subject, qs.ValueOf("B")))
and.AddSubIterator(it) and.AddSubIterator(it)
expect = []*graph.Triple{ expect = []*quad.Quad{
{"B", "follows", "F", ""}, {"B", "follows", "F", ""},
} }
if got := iteratedTriples(ts, and); !reflect.DeepEqual(got, expect) { if got := iteratedTriples(qs, and); !reflect.DeepEqual(got, expect) {
t.Errorf("Failed to get confirm expected results, got:%v expect:%v", got, expect) t.Errorf("Failed to get confirm expected results, got:%v expect:%v", got, expect)
} }
// Predicate iterator. // Predicate iterator.
it = ts.TripleIterator(graph.Predicate, ts.ValueOf("status")) it = qs.TripleIterator(quad.Predicate, qs.ValueOf("status"))
expect = []*graph.Triple{ expect = []*quad.Quad{
{"B", "status", "cool", "status_graph"}, {"B", "status", "cool", "status_graph"},
{"D", "status", "cool", "status_graph"}, {"D", "status", "cool", "status_graph"},
{"G", "status", "cool", "status_graph"}, {"G", "status", "cool", "status_graph"},
} }
sort.Sort(ordered(expect)) sort.Sort(ordered(expect))
if got := iteratedTriples(ts, it); !reflect.DeepEqual(got, expect) { if got := iteratedTriples(qs, it); !reflect.DeepEqual(got, expect) {
t.Errorf("Failed to get expected results from predicate iterator, got:%v expect:%v", got, expect) t.Errorf("Failed to get expected results from predicate iterator, got:%v expect:%v", got, expect)
} }
// Provenance iterator. // Label iterator.
it = ts.TripleIterator(graph.Provenance, ts.ValueOf("status_graph")) it = qs.TripleIterator(quad.Label, qs.ValueOf("status_graph"))
expect = []*graph.Triple{ expect = []*quad.Quad{
{"B", "status", "cool", "status_graph"}, {"B", "status", "cool", "status_graph"},
{"D", "status", "cool", "status_graph"}, {"D", "status", "cool", "status_graph"},
{"G", "status", "cool", "status_graph"}, {"G", "status", "cool", "status_graph"},
} }
sort.Sort(ordered(expect)) sort.Sort(ordered(expect))
if got := iteratedTriples(ts, it); !reflect.DeepEqual(got, expect) { if got := iteratedTriples(qs, it); !reflect.DeepEqual(got, expect) {
t.Errorf("Failed to get expected results from predicate iterator, got:%v expect:%v", got, expect) t.Errorf("Failed to get expected results from predicate iterator, got:%v expect:%v", got, expect)
} }
it.Reset() it.Reset()
// Order is important // Order is important
and = iterator.NewAnd() and = iterator.NewAnd()
and.AddSubIterator(ts.TripleIterator(graph.Subject, ts.ValueOf("B"))) and.AddSubIterator(qs.TripleIterator(quad.Subject, qs.ValueOf("B")))
and.AddSubIterator(it) and.AddSubIterator(it)
expect = []*graph.Triple{ expect = []*quad.Quad{
{"B", "status", "cool", "status_graph"}, {"B", "status", "cool", "status_graph"},
} }
if got := iteratedTriples(ts, and); !reflect.DeepEqual(got, expect) { if got := iteratedTriples(qs, and); !reflect.DeepEqual(got, expect) {
t.Errorf("Failed to get confirm expected results, got:%v expect:%v", got, expect) t.Errorf("Failed to get confirm expected results, got:%v expect:%v", got, expect)
} }
it.Reset() it.Reset()
@ -388,12 +389,12 @@ func TestSetIterator(t *testing.T) {
// Order is important // Order is important
and = iterator.NewAnd() and = iterator.NewAnd()
and.AddSubIterator(it) and.AddSubIterator(it)
and.AddSubIterator(ts.TripleIterator(graph.Subject, ts.ValueOf("B"))) and.AddSubIterator(qs.TripleIterator(quad.Subject, qs.ValueOf("B")))
expect = []*graph.Triple{ expect = []*quad.Quad{
{"B", "status", "cool", "status_graph"}, {"B", "status", "cool", "status_graph"},
} }
if got := iteratedTriples(ts, and); !reflect.DeepEqual(got, expect) { if got := iteratedTriples(qs, and); !reflect.DeepEqual(got, expect) {
t.Errorf("Failed to get confirm expected results, got:%v expect:%v", got, expect) t.Errorf("Failed to get confirm expected results, got:%v expect:%v", got, expect)
} }
} }
@ -406,17 +407,17 @@ func TestOptimize(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Failed to create working directory") t.Fatalf("Failed to create working directory")
} }
ts, err := newTripleStore(tmpDir, nil) qs, err := newTripleStore(tmpDir, nil)
if ts == nil || err != nil { if qs == nil || err != nil {
t.Error("Failed to create leveldb TripleStore.") t.Error("Failed to create leveldb TripleStore.")
} }
ts.AddTripleSet(makeTripleSet()) qs.AddTripleSet(makeTripleSet())
// With an linksto-fixed pair // With an linksto-fixed pair
fixed := ts.FixedIterator() fixed := qs.FixedIterator()
fixed.Add(ts.ValueOf("F")) fixed.Add(qs.ValueOf("F"))
fixed.AddTag("internal") fixed.Tagger().Add("internal")
lto := iterator.NewLinksTo(ts, fixed, graph.Object) lto := iterator.NewLinksTo(qs, fixed, quad.Object)
oldIt := lto.Clone() oldIt := lto.Clone()
newIt, ok := lto.Optimize() newIt, ok := lto.Optimize()
@ -427,16 +428,16 @@ func TestOptimize(t *testing.T) {
t.Errorf("Optimized iterator type does not match original, got:%v expect:%v", newIt.Type(), Type()) t.Errorf("Optimized iterator type does not match original, got:%v expect:%v", newIt.Type(), Type())
} }
newTriples := iteratedTriples(ts, newIt) newTriples := iteratedTriples(qs, newIt)
oldTriples := iteratedTriples(ts, oldIt) oldTriples := iteratedTriples(qs, oldIt)
if !reflect.DeepEqual(newTriples, oldTriples) { if !reflect.DeepEqual(newTriples, oldTriples) {
t.Errorf("Optimized iteration does not match original") t.Errorf("Optimized iteration does not match original")
} }
oldIt.Next() graph.Next(oldIt)
oldResults := make(map[string]graph.Value) oldResults := make(map[string]graph.Value)
oldIt.TagResults(oldResults) oldIt.TagResults(oldResults)
newIt.Next() graph.Next(newIt)
newResults := make(map[string]graph.Value) newResults := make(map[string]graph.Value)
newIt.TagResults(newResults) newIt.TagResults(newResults)
if !reflect.DeepEqual(newResults, oldResults) { if !reflect.DeepEqual(newResults, oldResults) {

View file

@ -30,6 +30,7 @@ import (
"github.com/google/cayley/graph" "github.com/google/cayley/graph"
"github.com/google/cayley/graph/iterator" "github.com/google/cayley/graph/iterator"
"github.com/google/cayley/quad"
) )
const ( const (
@ -56,110 +57,110 @@ func createNewLevelDB(path string, _ graph.Options) error {
return err return err
} }
defer db.Close() defer db.Close()
ts := &TripleStore{} qs := &TripleStore{}
ts.db = db qs.db = db
ts.writeopts = &opt.WriteOptions{ qs.writeopts = &opt.WriteOptions{
Sync: true, Sync: true,
} }
ts.Close() qs.Close()
return nil return nil
} }
func newTripleStore(path string, options graph.Options) (graph.TripleStore, error) { func newTripleStore(path string, options graph.Options) (graph.TripleStore, error) {
var ts TripleStore var qs TripleStore
ts.path = path qs.path = path
cache_size := DefaultCacheSize cache_size := DefaultCacheSize
if val, ok := options.IntKey("cache_size_mb"); ok { if val, ok := options.IntKey("cache_size_mb"); ok {
cache_size = val cache_size = val
} }
ts.dbOpts = &opt.Options{ qs.dbOpts = &opt.Options{
BlockCache: cache.NewLRUCache(cache_size * opt.MiB), BlockCache: cache.NewLRUCache(cache_size * opt.MiB),
} }
ts.dbOpts.ErrorIfMissing = true qs.dbOpts.ErrorIfMissing = true
write_buffer_mb := DefaultWriteBufferSize write_buffer_mb := DefaultWriteBufferSize
if val, ok := options.IntKey("write_buffer_mb"); ok { if val, ok := options.IntKey("write_buffer_mb"); ok {
write_buffer_mb = val write_buffer_mb = val
} }
ts.dbOpts.WriteBuffer = write_buffer_mb * opt.MiB qs.dbOpts.WriteBuffer = write_buffer_mb * opt.MiB
ts.hasher = sha1.New() qs.hasher = sha1.New()
ts.writeopts = &opt.WriteOptions{ qs.writeopts = &opt.WriteOptions{
Sync: false, Sync: false,
} }
ts.readopts = &opt.ReadOptions{} qs.readopts = &opt.ReadOptions{}
db, err := leveldb.OpenFile(ts.path, ts.dbOpts) db, err := leveldb.OpenFile(qs.path, qs.dbOpts)
if err != nil { if err != nil {
panic("Error, couldn't open! " + err.Error()) panic("Error, couldn't open! " + err.Error())
} }
ts.db = db qs.db = db
glog.Infoln(ts.GetStats()) glog.Infoln(qs.GetStats())
ts.getSize() qs.getSize()
return &ts, nil return &qs, nil
} }
func (ts *TripleStore) GetStats() string { func (qs *TripleStore) GetStats() string {
out := "" out := ""
stats, err := ts.db.GetProperty("leveldb.stats") stats, err := qs.db.GetProperty("leveldb.stats")
if err == nil { if err == nil {
out += fmt.Sprintln("Stats: ", stats) out += fmt.Sprintln("Stats: ", stats)
} }
out += fmt.Sprintln("Size: ", ts.size) out += fmt.Sprintln("Size: ", qs.size)
return out return out
} }
func (ts *TripleStore) Size() int64 { func (qs *TripleStore) Size() int64 {
return ts.size return qs.size
} }
func (ts *TripleStore) createKeyFor(d [3]graph.Direction, triple *graph.Triple) []byte { func (qs *TripleStore) createKeyFor(d [3]quad.Direction, triple *quad.Quad) []byte {
key := make([]byte, 0, 2+(ts.hasher.Size()*3)) key := make([]byte, 0, 2+(qs.hasher.Size()*3))
// TODO(kortschak) Remove dependence on String() method. // TODO(kortschak) Remove dependence on String() method.
key = append(key, []byte{d[0].Prefix(), d[1].Prefix()}...) key = append(key, []byte{d[0].Prefix(), d[1].Prefix()}...)
key = append(key, ts.convertStringToByteHash(triple.Get(d[0]))...) key = append(key, qs.convertStringToByteHash(triple.Get(d[0]))...)
key = append(key, ts.convertStringToByteHash(triple.Get(d[1]))...) key = append(key, qs.convertStringToByteHash(triple.Get(d[1]))...)
key = append(key, ts.convertStringToByteHash(triple.Get(d[2]))...) key = append(key, qs.convertStringToByteHash(triple.Get(d[2]))...)
return key return key
} }
func (ts *TripleStore) createProvKeyFor(d [3]graph.Direction, triple *graph.Triple) []byte { func (qs *TripleStore) createProvKeyFor(d [3]quad.Direction, triple *quad.Quad) []byte {
key := make([]byte, 0, 2+(ts.hasher.Size()*4)) key := make([]byte, 0, 2+(qs.hasher.Size()*4))
// TODO(kortschak) Remove dependence on String() method. // TODO(kortschak) Remove dependence on String() method.
key = append(key, []byte{graph.Provenance.Prefix(), d[0].Prefix()}...) key = append(key, []byte{quad.Label.Prefix(), d[0].Prefix()}...)
key = append(key, ts.convertStringToByteHash(triple.Get(graph.Provenance))...) key = append(key, qs.convertStringToByteHash(triple.Get(quad.Label))...)
key = append(key, ts.convertStringToByteHash(triple.Get(d[0]))...) key = append(key, qs.convertStringToByteHash(triple.Get(d[0]))...)
key = append(key, ts.convertStringToByteHash(triple.Get(d[1]))...) key = append(key, qs.convertStringToByteHash(triple.Get(d[1]))...)
key = append(key, ts.convertStringToByteHash(triple.Get(d[2]))...) key = append(key, qs.convertStringToByteHash(triple.Get(d[2]))...)
return key return key
} }
func (ts *TripleStore) createValueKeyFor(s string) []byte { func (qs *TripleStore) createValueKeyFor(s string) []byte {
key := make([]byte, 0, 1+ts.hasher.Size()) key := make([]byte, 0, 1+qs.hasher.Size())
key = append(key, []byte("z")...) key = append(key, []byte("z")...)
key = append(key, ts.convertStringToByteHash(s)...) key = append(key, qs.convertStringToByteHash(s)...)
return key return key
} }
func (ts *TripleStore) AddTriple(t *graph.Triple) { func (qs *TripleStore) AddTriple(t *quad.Quad) {
batch := &leveldb.Batch{} batch := &leveldb.Batch{}
ts.buildWrite(batch, t) qs.buildWrite(batch, t)
err := ts.db.Write(batch, ts.writeopts) err := qs.db.Write(batch, qs.writeopts)
if err != nil { if err != nil {
glog.Errorf("Couldn't write to DB for triple %s", t) glog.Errorf("Couldn't write to DB for triple %s", t)
return return
} }
ts.size++ qs.size++
} }
// Short hand for direction permutations. // Short hand for direction permutations.
var ( var (
spo = [3]graph.Direction{graph.Subject, graph.Predicate, graph.Object} spo = [3]quad.Direction{quad.Subject, quad.Predicate, quad.Object}
osp = [3]graph.Direction{graph.Object, graph.Subject, graph.Predicate} osp = [3]quad.Direction{quad.Object, quad.Subject, quad.Predicate}
pos = [3]graph.Direction{graph.Predicate, graph.Object, graph.Subject} pos = [3]quad.Direction{quad.Predicate, quad.Object, quad.Subject}
pso = [3]graph.Direction{graph.Predicate, graph.Subject, graph.Object} pso = [3]quad.Direction{quad.Predicate, quad.Subject, quad.Object}
) )
func (ts *TripleStore) RemoveTriple(t *graph.Triple) { func (qs *TripleStore) RemoveTriple(t *quad.Quad) {
_, err := ts.db.Get(ts.createKeyFor(spo, t), ts.readopts) _, err := qs.db.Get(qs.createKeyFor(spo, t), qs.readopts)
if err != nil && err != leveldb.ErrNotFound { if err != nil && err != leveldb.ErrNotFound {
glog.Errorf("Couldn't access DB to confirm deletion") glog.Errorf("Couldn't access DB to confirm deletion")
return return
@ -169,45 +170,45 @@ func (ts *TripleStore) RemoveTriple(t *graph.Triple) {
return return
} }
batch := &leveldb.Batch{} batch := &leveldb.Batch{}
batch.Delete(ts.createKeyFor(spo, t)) batch.Delete(qs.createKeyFor(spo, t))
batch.Delete(ts.createKeyFor(osp, t)) batch.Delete(qs.createKeyFor(osp, t))
batch.Delete(ts.createKeyFor(pos, t)) batch.Delete(qs.createKeyFor(pos, t))
ts.UpdateValueKeyBy(t.Get(graph.Subject), -1, batch) qs.UpdateValueKeyBy(t.Get(quad.Subject), -1, batch)
ts.UpdateValueKeyBy(t.Get(graph.Predicate), -1, batch) qs.UpdateValueKeyBy(t.Get(quad.Predicate), -1, batch)
ts.UpdateValueKeyBy(t.Get(graph.Object), -1, batch) qs.UpdateValueKeyBy(t.Get(quad.Object), -1, batch)
if t.Get(graph.Provenance) != "" { if t.Get(quad.Label) != "" {
batch.Delete(ts.createProvKeyFor(pso, t)) batch.Delete(qs.createProvKeyFor(pso, t))
ts.UpdateValueKeyBy(t.Get(graph.Provenance), -1, batch) qs.UpdateValueKeyBy(t.Get(quad.Label), -1, batch)
} }
err = ts.db.Write(batch, nil) err = qs.db.Write(batch, nil)
if err != nil { if err != nil {
glog.Errorf("Couldn't delete triple %s", t) glog.Errorf("Couldn't delete triple %s", t)
return return
} }
ts.size-- qs.size--
} }
func (ts *TripleStore) buildTripleWrite(batch *leveldb.Batch, t *graph.Triple) { func (qs *TripleStore) buildTripleWrite(batch *leveldb.Batch, t *quad.Quad) {
bytes, err := json.Marshal(*t) bytes, err := json.Marshal(*t)
if err != nil { if err != nil {
glog.Errorf("Couldn't write to buffer for triple %s\n %s\n", t, err) glog.Errorf("Couldn't write to buffer for triple %s\n %s\n", t, err)
return return
} }
batch.Put(ts.createKeyFor(spo, t), bytes) batch.Put(qs.createKeyFor(spo, t), bytes)
batch.Put(ts.createKeyFor(osp, t), bytes) batch.Put(qs.createKeyFor(osp, t), bytes)
batch.Put(ts.createKeyFor(pos, t), bytes) batch.Put(qs.createKeyFor(pos, t), bytes)
if t.Get(graph.Provenance) != "" { if t.Get(quad.Label) != "" {
batch.Put(ts.createProvKeyFor(pso, t), bytes) batch.Put(qs.createProvKeyFor(pso, t), bytes)
} }
} }
func (ts *TripleStore) buildWrite(batch *leveldb.Batch, t *graph.Triple) { func (qs *TripleStore) buildWrite(batch *leveldb.Batch, t *quad.Quad) {
ts.buildTripleWrite(batch, t) qs.buildTripleWrite(batch, t)
ts.UpdateValueKeyBy(t.Get(graph.Subject), 1, nil) qs.UpdateValueKeyBy(t.Get(quad.Subject), 1, nil)
ts.UpdateValueKeyBy(t.Get(graph.Predicate), 1, nil) qs.UpdateValueKeyBy(t.Get(quad.Predicate), 1, nil)
ts.UpdateValueKeyBy(t.Get(graph.Object), 1, nil) qs.UpdateValueKeyBy(t.Get(quad.Object), 1, nil)
if t.Get(graph.Provenance) != "" { if t.Get(quad.Label) != "" {
ts.UpdateValueKeyBy(t.Get(graph.Provenance), 1, nil) qs.UpdateValueKeyBy(t.Get(quad.Label), 1, nil)
} }
} }
@ -216,10 +217,10 @@ type ValueData struct {
Size int64 Size int64
} }
func (ts *TripleStore) UpdateValueKeyBy(name string, amount int, batch *leveldb.Batch) { func (qs *TripleStore) UpdateValueKeyBy(name string, amount int, batch *leveldb.Batch) {
value := &ValueData{name, int64(amount)} value := &ValueData{name, int64(amount)}
key := ts.createValueKeyFor(name) key := qs.createValueKeyFor(name)
b, err := ts.db.Get(key, ts.readopts) b, err := qs.db.Get(key, qs.readopts)
// Error getting the node from the database. // Error getting the node from the database.
if err != nil && err != leveldb.ErrNotFound { if err != nil && err != leveldb.ErrNotFound {
@ -241,7 +242,7 @@ func (ts *TripleStore) UpdateValueKeyBy(name string, amount int, batch *leveldb.
if amount < 0 { if amount < 0 {
if value.Size <= 0 { if value.Size <= 0 {
if batch == nil { if batch == nil {
ts.db.Delete(key, ts.writeopts) qs.db.Delete(key, qs.writeopts)
} else { } else {
batch.Delete(key) batch.Delete(key)
} }
@ -256,88 +257,88 @@ func (ts *TripleStore) UpdateValueKeyBy(name string, amount int, batch *leveldb.
return return
} }
if batch == nil { if batch == nil {
ts.db.Put(key, bytes, ts.writeopts) qs.db.Put(key, bytes, qs.writeopts)
} else { } else {
batch.Put(key, bytes) batch.Put(key, bytes)
} }
} }
func (ts *TripleStore) AddTripleSet(t_s []*graph.Triple) { func (qs *TripleStore) AddTripleSet(t_s []*quad.Quad) {
batch := &leveldb.Batch{} batch := &leveldb.Batch{}
newTs := len(t_s) newTs := len(t_s)
resizeMap := make(map[string]int) resizeMap := make(map[string]int)
for _, t := range t_s { for _, t := range t_s {
ts.buildTripleWrite(batch, t) qs.buildTripleWrite(batch, t)
resizeMap[t.Subject]++ resizeMap[t.Subject]++
resizeMap[t.Predicate]++ resizeMap[t.Predicate]++
resizeMap[t.Object]++ resizeMap[t.Object]++
if t.Provenance != "" { if t.Label != "" {
resizeMap[t.Provenance]++ resizeMap[t.Label]++
} }
} }
for k, v := range resizeMap { for k, v := range resizeMap {
ts.UpdateValueKeyBy(k, v, batch) qs.UpdateValueKeyBy(k, v, batch)
} }
err := ts.db.Write(batch, ts.writeopts) err := qs.db.Write(batch, qs.writeopts)
if err != nil { if err != nil {
glog.Errorf("Couldn't write to DB for tripleset") glog.Errorf("Couldn't write to DB for tripleset")
return return
} }
ts.size += int64(newTs) qs.size += int64(newTs)
} }
func (ts *TripleStore) Close() { func (qs *TripleStore) Close() {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
err := binary.Write(buf, binary.LittleEndian, ts.size) err := binary.Write(buf, binary.LittleEndian, qs.size)
if err == nil { if err == nil {
werr := ts.db.Put([]byte("__size"), buf.Bytes(), ts.writeopts) werr := qs.db.Put([]byte("__size"), buf.Bytes(), qs.writeopts)
if werr != nil { if werr != nil {
glog.Errorf("Couldn't write size before closing!") glog.Errorf("Couldn't write size before closing!")
} }
} else { } else {
glog.Errorf("Couldn't convert size before closing!") glog.Errorf("Couldn't convert size before closing!")
} }
ts.db.Close() qs.db.Close()
ts.open = false qs.open = false
} }
func (ts *TripleStore) Triple(k graph.Value) *graph.Triple { func (qs *TripleStore) Quad(k graph.Value) *quad.Quad {
var triple graph.Triple var triple quad.Quad
b, err := ts.db.Get(k.([]byte), ts.readopts) b, err := qs.db.Get(k.([]byte), qs.readopts)
if err != nil && err != leveldb.ErrNotFound { if err != nil && err != leveldb.ErrNotFound {
glog.Errorln("Error: couldn't get triple from DB") glog.Errorln("Error: couldn't get triple from DB")
return &graph.Triple{} return &quad.Quad{}
} }
if err == leveldb.ErrNotFound { if err == leveldb.ErrNotFound {
// No harm, no foul. // No harm, no foul.
return &graph.Triple{} return &quad.Quad{}
} }
err = json.Unmarshal(b, &triple) err = json.Unmarshal(b, &triple)
if err != nil { if err != nil {
glog.Errorln("Error: couldn't reconstruct triple") glog.Errorln("Error: couldn't reconstruct triple")
return &graph.Triple{} return &quad.Quad{}
} }
return &triple return &triple
} }
func (ts *TripleStore) convertStringToByteHash(s string) []byte { func (qs *TripleStore) convertStringToByteHash(s string) []byte {
ts.hasher.Reset() qs.hasher.Reset()
key := make([]byte, 0, ts.hasher.Size()) key := make([]byte, 0, qs.hasher.Size())
ts.hasher.Write([]byte(s)) qs.hasher.Write([]byte(s))
key = ts.hasher.Sum(key) key = qs.hasher.Sum(key)
return key return key
} }
func (ts *TripleStore) ValueOf(s string) graph.Value { func (qs *TripleStore) ValueOf(s string) graph.Value {
return ts.createValueKeyFor(s) return qs.createValueKeyFor(s)
} }
func (ts *TripleStore) valueData(value_key []byte) ValueData { func (qs *TripleStore) valueData(value_key []byte) ValueData {
var out ValueData var out ValueData
if glog.V(3) { if glog.V(3) {
glog.V(3).Infof("%s %v\n", string(value_key[0]), value_key) glog.V(3).Infof("%s %v\n", string(value_key[0]), value_key)
} }
b, err := ts.db.Get(value_key, ts.readopts) b, err := qs.db.Get(value_key, qs.readopts)
if err != nil && err != leveldb.ErrNotFound { if err != nil && err != leveldb.ErrNotFound {
glog.Errorln("Error: couldn't get value from DB") glog.Errorln("Error: couldn't get value from DB")
return out return out
@ -352,30 +353,30 @@ func (ts *TripleStore) valueData(value_key []byte) ValueData {
return out return out
} }
func (ts *TripleStore) NameOf(k graph.Value) string { func (qs *TripleStore) NameOf(k graph.Value) string {
if k == nil { if k == nil {
glog.V(2).Infoln("k was nil") glog.V(2).Infoln("k was nil")
return "" return ""
} }
return ts.valueData(k.([]byte)).Name return qs.valueData(k.([]byte)).Name
} }
func (ts *TripleStore) SizeOf(k graph.Value) int64 { func (qs *TripleStore) SizeOf(k graph.Value) int64 {
if k == nil { if k == nil {
return 0 return 0
} }
return int64(ts.valueData(k.([]byte)).Size) return int64(qs.valueData(k.([]byte)).Size)
} }
func (ts *TripleStore) getSize() { func (qs *TripleStore) getSize() {
var size int64 var size int64
b, err := ts.db.Get([]byte("__size"), ts.readopts) b, err := qs.db.Get([]byte("__size"), qs.readopts)
if err != nil && err != leveldb.ErrNotFound { if err != nil && err != leveldb.ErrNotFound {
panic("Couldn't read size " + err.Error()) panic("Couldn't read size " + err.Error())
} }
if err == leveldb.ErrNotFound { if err == leveldb.ErrNotFound {
// Must be a new database. Cool // Must be a new database. Cool
ts.size = 0 qs.size = 0
return return
} }
buf := bytes.NewBuffer(b) buf := bytes.NewBuffer(b)
@ -383,10 +384,10 @@ func (ts *TripleStore) getSize() {
if err != nil { if err != nil {
glog.Errorln("Error: couldn't parse size") glog.Errorln("Error: couldn't parse size")
} }
ts.size = size qs.size = size
} }
func (ts *TripleStore) SizeOfPrefix(pre []byte) (int64, error) { func (qs *TripleStore) SizeOfPrefix(pre []byte) (int64, error) {
limit := make([]byte, len(pre)) limit := make([]byte, len(pre))
copy(limit, pre) copy(limit, pre)
end := len(limit) - 1 end := len(limit) - 1
@ -394,45 +395,45 @@ func (ts *TripleStore) SizeOfPrefix(pre []byte) (int64, error) {
ranges := make([]util.Range, 1) ranges := make([]util.Range, 1)
ranges[0].Start = pre ranges[0].Start = pre
ranges[0].Limit = limit ranges[0].Limit = limit
sizes, err := ts.db.SizeOf(ranges) sizes, err := qs.db.SizeOf(ranges)
if err == nil { if err == nil {
return (int64(sizes[0]) >> 6) + 1, nil return (int64(sizes[0]) >> 6) + 1, nil
} }
return 0, nil return 0, nil
} }
func (ts *TripleStore) TripleIterator(d graph.Direction, val graph.Value) graph.Iterator { func (qs *TripleStore) TripleIterator(d quad.Direction, val graph.Value) graph.Iterator {
var prefix string var prefix string
switch d { switch d {
case graph.Subject: case quad.Subject:
prefix = "sp" prefix = "sp"
case graph.Predicate: case quad.Predicate:
prefix = "po" prefix = "po"
case graph.Object: case quad.Object:
prefix = "os" prefix = "os"
case graph.Provenance: case quad.Label:
prefix = "cp" prefix = "cp"
default: default:
panic("unreachable " + d.String()) panic("unreachable " + d.String())
} }
return NewIterator(prefix, d, val, ts) return NewIterator(prefix, d, val, qs)
} }
func (ts *TripleStore) NodesAllIterator() graph.Iterator { func (qs *TripleStore) NodesAllIterator() graph.Iterator {
return NewAllIterator("z", graph.Any, ts) return NewAllIterator("z", quad.Any, qs)
} }
func (ts *TripleStore) TriplesAllIterator() graph.Iterator { func (qs *TripleStore) TriplesAllIterator() graph.Iterator {
return NewAllIterator("po", graph.Predicate, ts) return NewAllIterator("po", quad.Predicate, qs)
} }
func (ts *TripleStore) TripleDirection(val graph.Value, d graph.Direction) graph.Value { func (qs *TripleStore) TripleDirection(val graph.Value, d quad.Direction) graph.Value {
v := val.([]uint8) v := val.([]uint8)
offset := PositionOf(v[0:2], d, ts) offset := PositionOf(v[0:2], d, qs)
if offset != -1 { if offset != -1 {
return append([]byte("z"), v[offset:offset+ts.hasher.Size()]...) return append([]byte("z"), v[offset:offset+qs.hasher.Size()]...)
} else { } else {
return ts.Triple(val).Get(d) return qs.Quad(val).Get(d)
} }
} }
@ -440,7 +441,7 @@ func compareBytes(a, b graph.Value) bool {
return bytes.Equal(a.([]uint8), b.([]uint8)) return bytes.Equal(a.([]uint8), b.([]uint8))
} }
func (ts *TripleStore) FixedIterator() graph.FixedIterator { func (qs *TripleStore) FixedIterator() graph.FixedIterator {
return iterator.NewFixedIteratorWithCompare(compareBytes) return iterator.NewFixedIteratorWithCompare(compareBytes)
} }

View file

@ -37,14 +37,15 @@ func (ts *TripleStore) optimizeLinksTo(it *iterator.LinksTo) (graph.Iterator, bo
if primary.Type() == graph.Fixed { if primary.Type() == graph.Fixed {
size, _ := primary.Size() size, _ := primary.Size()
if size == 1 { if size == 1 {
val, ok := primary.Next() val, ok := graph.Next(primary)
if !ok { if !ok {
panic("Sizes lie") panic("Sizes lie")
} }
newIt := ts.TripleIterator(it.Direction(), val) newIt := ts.TripleIterator(it.Direction(), val)
newIt.CopyTagsFrom(it) nt := newIt.Tagger()
for _, tag := range primary.Tags() { nt.CopyFrom(it)
newIt.AddFixedTag(tag, val) for _, tag := range primary.Tagger().Tags() {
nt.AddFixed(tag, val)
} }
it.Close() it.Close()
return newIt, true return newIt, true

View file

@ -31,6 +31,11 @@ func NewMemstoreAllIterator(ts *TripleStore) *AllIterator {
return &out return &out
} }
// No subiterators.
func (it *AllIterator) SubIterators() []graph.Iterator {
return nil
}
func (it *AllIterator) Next() (graph.Value, bool) { func (it *AllIterator) Next() (graph.Value, bool) {
next, out := it.Int64.Next() next, out := it.Int64.Next()
if !out { if !out {
@ -41,6 +46,5 @@ func (it *AllIterator) Next() (graph.Value, bool) {
if !ok { if !ok {
return it.Next() return it.Next()
} }
it.Last = next
return next, out return next, out
} }

View file

@ -26,11 +26,13 @@ import (
) )
type Iterator struct { type Iterator struct {
iterator.Base uid uint64
tags graph.Tagger
tree *llrb.LLRB tree *llrb.LLRB
data string data string
isRunning bool isRunning bool
iterLast Int64 iterLast Int64
result graph.Value
} }
type Int64 int64 type Int64 int64
@ -53,34 +55,69 @@ func IterateOne(tree *llrb.LLRB, last Int64) Int64 {
} }
func NewLlrbIterator(tree *llrb.LLRB, data string) *Iterator { func NewLlrbIterator(tree *llrb.LLRB, data string) *Iterator {
var it Iterator return &Iterator{
iterator.BaseInit(&it.Base) uid: iterator.NextUID(),
it.tree = tree tree: tree,
it.iterLast = Int64(-1) iterLast: Int64(-1),
it.data = data data: data,
return &it }
}
func (it *Iterator) UID() uint64 {
return it.uid
} }
func (it *Iterator) Reset() { func (it *Iterator) Reset() {
it.iterLast = Int64(-1) it.iterLast = Int64(-1)
} }
func (it *Iterator) Tagger() *graph.Tagger {
return &it.tags
}
func (it *Iterator) TagResults(dst map[string]graph.Value) {
for _, tag := range it.tags.Tags() {
dst[tag] = it.Result()
}
for tag, value := range it.tags.Fixed() {
dst[tag] = value
}
}
func (it *Iterator) Clone() graph.Iterator { func (it *Iterator) Clone() graph.Iterator {
var new_it = NewLlrbIterator(it.tree, it.data) m := NewLlrbIterator(it.tree, it.data)
new_it.CopyTagsFrom(it) m.tags.CopyFrom(it)
return new_it return m
} }
func (it *Iterator) Close() {} func (it *Iterator) Close() {}
func (it *Iterator) Next() (graph.Value, bool) { func (it *Iterator) Next() (graph.Value, bool) {
graph.NextLogIn(it) graph.NextLogIn(it)
if it.tree.Max() == nil || it.Last == int64(it.tree.Max().(Int64)) { if it.tree.Max() == nil || it.result == int64(it.tree.Max().(Int64)) {
return graph.NextLogOut(it, nil, false) return graph.NextLogOut(it, nil, false)
} }
it.iterLast = IterateOne(it.tree, it.iterLast) it.iterLast = IterateOne(it.tree, it.iterLast)
it.Last = int64(it.iterLast) it.result = int64(it.iterLast)
return graph.NextLogOut(it, it.Last, true) return graph.NextLogOut(it, it.result, true)
}
func (it *Iterator) ResultTree() *graph.ResultTree {
return graph.NewResultTree(it.Result())
}
func (it *Iterator) Result() graph.Value {
return it.result
}
func (it *Iterator) NextResult() bool {
return false
}
// No subiterators.
func (it *Iterator) SubIterators() []graph.Iterator {
return nil
} }
func (it *Iterator) Size() (int64, bool) { func (it *Iterator) Size() (int64, bool) {
@ -90,7 +127,7 @@ func (it *Iterator) Size() (int64, bool) {
func (it *Iterator) Check(v graph.Value) bool { func (it *Iterator) Check(v graph.Value) bool {
graph.CheckLogIn(it, v) graph.CheckLogIn(it, v)
if it.tree.Has(Int64(v.(int64))) { if it.tree.Has(Int64(v.(int64))) {
it.Last = v it.result = v
return graph.CheckLogOut(it, v, true) return graph.CheckLogOut(it, v, true)
} }
return graph.CheckLogOut(it, v, false) return graph.CheckLogOut(it, v, false)
@ -98,7 +135,7 @@ func (it *Iterator) Check(v graph.Value) bool {
func (it *Iterator) DebugString(indent int) string { func (it *Iterator) DebugString(indent int) string {
size, _ := it.Size() size, _ := it.Size()
return fmt.Sprintf("%s(%s tags:%s size:%d %s)", strings.Repeat(" ", indent), it.Type(), it.Tags(), size, it.data) return fmt.Sprintf("%s(%s tags:%s size:%d %s)", strings.Repeat(" ", indent), it.Type(), it.tags.Tags(), size, it.data)
} }
var memType graph.Type var memType graph.Type

View file

@ -20,15 +20,16 @@ import (
"github.com/barakmich/glog" "github.com/barakmich/glog"
"github.com/google/cayley/graph" "github.com/google/cayley/graph"
"github.com/google/cayley/graph/iterator" "github.com/google/cayley/graph/iterator"
"github.com/google/cayley/quad"
"github.com/petar/GoLLRB/llrb" "github.com/petar/GoLLRB/llrb"
) )
type TripleDirectionIndex struct { type TripleDirectionIndex struct {
subject map[int64]*llrb.LLRB subject map[int64]*llrb.LLRB
predicate map[int64]*llrb.LLRB predicate map[int64]*llrb.LLRB
object map[int64]*llrb.LLRB object map[int64]*llrb.LLRB
provenance map[int64]*llrb.LLRB label map[int64]*llrb.LLRB
} }
func NewTripleDirectionIndex() *TripleDirectionIndex { func NewTripleDirectionIndex() *TripleDirectionIndex {
@ -36,25 +37,25 @@ func NewTripleDirectionIndex() *TripleDirectionIndex {
tdi.subject = make(map[int64]*llrb.LLRB) tdi.subject = make(map[int64]*llrb.LLRB)
tdi.predicate = make(map[int64]*llrb.LLRB) tdi.predicate = make(map[int64]*llrb.LLRB)
tdi.object = make(map[int64]*llrb.LLRB) tdi.object = make(map[int64]*llrb.LLRB)
tdi.provenance = make(map[int64]*llrb.LLRB) tdi.label = make(map[int64]*llrb.LLRB)
return &tdi return &tdi
} }
func (tdi *TripleDirectionIndex) GetForDir(d graph.Direction) map[int64]*llrb.LLRB { func (tdi *TripleDirectionIndex) GetForDir(d quad.Direction) map[int64]*llrb.LLRB {
switch d { switch d {
case graph.Subject: case quad.Subject:
return tdi.subject return tdi.subject
case graph.Object: case quad.Object:
return tdi.object return tdi.object
case graph.Predicate: case quad.Predicate:
return tdi.predicate return tdi.predicate
case graph.Provenance: case quad.Label:
return tdi.provenance return tdi.label
} }
panic("illegal direction") panic("illegal direction")
} }
func (tdi *TripleDirectionIndex) GetOrCreate(d graph.Direction, id int64) *llrb.LLRB { func (tdi *TripleDirectionIndex) GetOrCreate(d quad.Direction, id int64) *llrb.LLRB {
directionIndex := tdi.GetForDir(d) directionIndex := tdi.GetForDir(d)
if _, ok := directionIndex[id]; !ok { if _, ok := directionIndex[id]; !ok {
directionIndex[id] = llrb.New() directionIndex[id] = llrb.New()
@ -62,7 +63,7 @@ func (tdi *TripleDirectionIndex) GetOrCreate(d graph.Direction, id int64) *llrb.
return directionIndex[id] return directionIndex[id]
} }
func (tdi *TripleDirectionIndex) Get(d graph.Direction, id int64) (*llrb.LLRB, bool) { func (tdi *TripleDirectionIndex) Get(d quad.Direction, id int64) (*llrb.LLRB, bool) {
directionIndex := tdi.GetForDir(d) directionIndex := tdi.GetForDir(d)
tree, exists := directionIndex[id] tree, exists := directionIndex[id]
return tree, exists return tree, exists
@ -73,7 +74,7 @@ type TripleStore struct {
tripleIdCounter int64 tripleIdCounter int64
idMap map[string]int64 idMap map[string]int64
revIdMap map[int64]string revIdMap map[int64]string
triples []graph.Triple triples []quad.Quad
size int64 size int64
index TripleDirectionIndex index TripleDirectionIndex
// vip_index map[string]map[int64]map[string]map[int64]*llrb.Tree // vip_index map[string]map[int64]map[string]map[int64]*llrb.Tree
@ -83,10 +84,10 @@ func newTripleStore() *TripleStore {
var ts TripleStore var ts TripleStore
ts.idMap = make(map[string]int64) ts.idMap = make(map[string]int64)
ts.revIdMap = make(map[int64]string) ts.revIdMap = make(map[int64]string)
ts.triples = make([]graph.Triple, 1, 200) ts.triples = make([]quad.Quad, 1, 200)
// Sentinel null triple so triple indices start at 1 // Sentinel null triple so triple indices start at 1
ts.triples[0] = graph.Triple{} ts.triples[0] = quad.Quad{}
ts.size = 1 ts.size = 1
ts.index = *NewTripleDirectionIndex() ts.index = *NewTripleDirectionIndex()
ts.idCounter = 1 ts.idCounter = 1
@ -94,18 +95,18 @@ func newTripleStore() *TripleStore {
return &ts return &ts
} }
func (ts *TripleStore) AddTripleSet(triples []*graph.Triple) { func (ts *TripleStore) AddTripleSet(triples []*quad.Quad) {
for _, t := range triples { for _, t := range triples {
ts.AddTriple(t) ts.AddTriple(t)
} }
} }
func (ts *TripleStore) tripleExists(t *graph.Triple) (bool, int64) { func (ts *TripleStore) tripleExists(t *quad.Quad) (bool, int64) {
smallest := -1 smallest := -1
var smallest_tree *llrb.LLRB var smallest_tree *llrb.LLRB
for d := graph.Subject; d <= graph.Provenance; d++ { for d := quad.Subject; d <= quad.Label; d++ {
sid := t.Get(d) sid := t.Get(d)
if d == graph.Provenance && sid == "" { if d == quad.Label && sid == "" {
continue continue
} }
id, ok := ts.idMap[sid] id, ok := ts.idMap[sid]
@ -137,7 +138,7 @@ func (ts *TripleStore) tripleExists(t *graph.Triple) (bool, int64) {
return false, 0 return false, 0
} }
func (ts *TripleStore) AddTriple(t *graph.Triple) { func (ts *TripleStore) AddTriple(t *quad.Quad) {
if exists, _ := ts.tripleExists(t); exists { if exists, _ := ts.tripleExists(t); exists {
return return
} }
@ -147,9 +148,9 @@ func (ts *TripleStore) AddTriple(t *graph.Triple) {
ts.size++ ts.size++
ts.tripleIdCounter++ ts.tripleIdCounter++
for d := graph.Subject; d <= graph.Provenance; d++ { for d := quad.Subject; d <= quad.Label; d++ {
sid := t.Get(d) sid := t.Get(d)
if d == graph.Provenance && sid == "" { if d == quad.Label && sid == "" {
continue continue
} }
if _, ok := ts.idMap[sid]; !ok { if _, ok := ts.idMap[sid]; !ok {
@ -159,8 +160,8 @@ func (ts *TripleStore) AddTriple(t *graph.Triple) {
} }
} }
for d := graph.Subject; d <= graph.Provenance; d++ { for d := quad.Subject; d <= quad.Label; d++ {
if d == graph.Provenance && t.Get(d) == "" { if d == quad.Label && t.Get(d) == "" {
continue continue
} }
id := ts.idMap[t.Get(d)] id := ts.idMap[t.Get(d)]
@ -171,7 +172,7 @@ func (ts *TripleStore) AddTriple(t *graph.Triple) {
// TODO(barakmich): Add VIP indexing // TODO(barakmich): Add VIP indexing
} }
func (ts *TripleStore) RemoveTriple(t *graph.Triple) { func (ts *TripleStore) RemoveTriple(t *quad.Quad) {
var tripleID int64 var tripleID int64
var exists bool var exists bool
tripleID = 0 tripleID = 0
@ -179,11 +180,11 @@ func (ts *TripleStore) RemoveTriple(t *graph.Triple) {
return return
} }
ts.triples[tripleID] = graph.Triple{} ts.triples[tripleID] = quad.Quad{}
ts.size-- ts.size--
for d := graph.Subject; d <= graph.Provenance; d++ { for d := quad.Subject; d <= quad.Label; d++ {
if d == graph.Provenance && t.Get(d) == "" { if d == quad.Label && t.Get(d) == "" {
continue continue
} }
id := ts.idMap[t.Get(d)] id := ts.idMap[t.Get(d)]
@ -191,8 +192,8 @@ func (ts *TripleStore) RemoveTriple(t *graph.Triple) {
tree.Delete(Int64(tripleID)) tree.Delete(Int64(tripleID))
} }
for d := graph.Subject; d <= graph.Provenance; d++ { for d := quad.Subject; d <= quad.Label; d++ {
if d == graph.Provenance && t.Get(d) == "" { if d == quad.Label && t.Get(d) == "" {
continue continue
} }
id, ok := ts.idMap[t.Get(d)] id, ok := ts.idMap[t.Get(d)]
@ -200,8 +201,8 @@ func (ts *TripleStore) RemoveTriple(t *graph.Triple) {
continue continue
} }
stillExists := false stillExists := false
for d := graph.Subject; d <= graph.Provenance; d++ { for d := quad.Subject; d <= quad.Label; d++ {
if d == graph.Provenance && t.Get(d) == "" { if d == quad.Label && t.Get(d) == "" {
continue continue
} }
nodeTree := ts.index.GetOrCreate(d, id) nodeTree := ts.index.GetOrCreate(d, id)
@ -217,11 +218,11 @@ func (ts *TripleStore) RemoveTriple(t *graph.Triple) {
} }
} }
func (ts *TripleStore) Triple(index graph.Value) *graph.Triple { func (ts *TripleStore) Quad(index graph.Value) *quad.Quad {
return &ts.triples[index.(int64)] return &ts.triples[index.(int64)]
} }
func (ts *TripleStore) TripleIterator(d graph.Direction, value graph.Value) graph.Iterator { func (ts *TripleStore) TripleIterator(d quad.Direction, value graph.Value) graph.Iterator {
index, ok := ts.index.Get(d, value.(int64)) index, ok := ts.index.Get(d, value.(int64))
data := fmt.Sprintf("dir:%s val:%d", d, value.(int64)) data := fmt.Sprintf("dir:%s val:%d", d, value.(int64))
if ok { if ok {
@ -259,8 +260,8 @@ func (ts *TripleStore) FixedIterator() graph.FixedIterator {
return iterator.NewFixedIteratorWithCompare(iterator.BasicEquality) return iterator.NewFixedIteratorWithCompare(iterator.BasicEquality)
} }
func (ts *TripleStore) TripleDirection(val graph.Value, d graph.Direction) graph.Value { func (ts *TripleStore) TripleDirection(val graph.Value, d quad.Direction) graph.Value {
name := ts.Triple(val).Get(d) name := ts.Quad(val).Get(d)
return ts.ValueOf(name) return ts.ValueOf(name)
} }

View file

@ -37,14 +37,15 @@ func (ts *TripleStore) optimizeLinksTo(it *iterator.LinksTo) (graph.Iterator, bo
if primary.Type() == graph.Fixed { if primary.Type() == graph.Fixed {
size, _ := primary.Size() size, _ := primary.Size()
if size == 1 { if size == 1 {
val, ok := primary.Next() val, ok := graph.Next(primary)
if !ok { if !ok {
panic("Sizes lie") panic("Sizes lie")
} }
newIt := ts.TripleIterator(it.Direction(), val) newIt := ts.TripleIterator(it.Direction(), val)
newIt.CopyTagsFrom(it) nt := newIt.Tagger()
for _, tag := range primary.Tags() { nt.CopyFrom(it)
newIt.AddFixedTag(tag, val) for _, tag := range primary.Tagger().Tags() {
nt.AddFixed(tag, val)
} }
return newIt, true return newIt, true
} }

View file

@ -21,6 +21,7 @@ import (
"github.com/google/cayley/graph" "github.com/google/cayley/graph"
"github.com/google/cayley/graph/iterator" "github.com/google/cayley/graph/iterator"
"github.com/google/cayley/quad"
) )
// This is a simple test graph. // This is a simple test graph.
@ -36,7 +37,7 @@ import (
// \-->|#D#|------------->+---+ // \-->|#D#|------------->+---+
// +---+ // +---+
// //
var simpleGraph = []*graph.Triple{ var simpleGraph = []*quad.Quad{
{"A", "follows", "B", ""}, {"A", "follows", "B", ""},
{"C", "follows", "B", ""}, {"C", "follows", "B", ""},
{"C", "follows", "D", ""}, {"C", "follows", "D", ""},
@ -50,7 +51,7 @@ var simpleGraph = []*graph.Triple{
{"G", "status", "cool", "status_graph"}, {"G", "status", "cool", "status_graph"},
} }
func makeTestStore(data []*graph.Triple) (*TripleStore, []pair) { func makeTestStore(data []*quad.Quad) (*TripleStore, []pair) {
seen := make(map[string]struct{}) seen := make(map[string]struct{})
ts := newTripleStore() ts := newTripleStore()
var ( var (
@ -58,7 +59,7 @@ func makeTestStore(data []*graph.Triple) (*TripleStore, []pair) {
ind []pair ind []pair
) )
for _, t := range data { for _, t := range data {
for _, qp := range []string{t.Subject, t.Predicate, t.Object, t.Provenance} { for _, qp := range []string{t.Subject, t.Predicate, t.Object, t.Label} {
if _, ok := seen[qp]; !ok && qp != "" { if _, ok := seen[qp]; !ok && qp != "" {
val++ val++
ind = append(ind, pair{qp, val}) ind = append(ind, pair{qp, val})
@ -105,10 +106,10 @@ func TestIteratorsAndNextResultOrderA(t *testing.T) {
all := ts.NodesAllIterator() all := ts.NodesAllIterator()
innerAnd := iterator.NewAnd() innerAnd := iterator.NewAnd()
innerAnd.AddSubIterator(iterator.NewLinksTo(ts, fixed2, graph.Predicate)) innerAnd.AddSubIterator(iterator.NewLinksTo(ts, fixed2, quad.Predicate))
innerAnd.AddSubIterator(iterator.NewLinksTo(ts, all, graph.Object)) innerAnd.AddSubIterator(iterator.NewLinksTo(ts, all, quad.Object))
hasa := iterator.NewHasA(ts, innerAnd, graph.Subject) hasa := iterator.NewHasA(ts, innerAnd, quad.Subject)
outerAnd := iterator.NewAnd() outerAnd := iterator.NewAnd()
outerAnd.AddSubIterator(fixed) outerAnd.AddSubIterator(fixed)
outerAnd.AddSubIterator(hasa) outerAnd.AddSubIterator(hasa)
@ -149,8 +150,8 @@ func TestLinksToOptimization(t *testing.T) {
fixed := ts.FixedIterator() fixed := ts.FixedIterator()
fixed.Add(ts.ValueOf("cool")) fixed.Add(ts.ValueOf("cool"))
lto := iterator.NewLinksTo(ts, fixed, graph.Object) lto := iterator.NewLinksTo(ts, fixed, quad.Object)
lto.AddTag("foo") lto.Tagger().Add("foo")
newIt, changed := lto.Optimize() newIt, changed := lto.Optimize()
if !changed { if !changed {
@ -165,7 +166,8 @@ func TestLinksToOptimization(t *testing.T) {
if v_clone.DebugString(0) != v.DebugString(0) { if v_clone.DebugString(0) != v.DebugString(0) {
t.Fatal("Wrong iterator. Got ", v_clone.DebugString(0)) t.Fatal("Wrong iterator. Got ", v_clone.DebugString(0))
} }
if len(v_clone.Tags()) < 1 || v_clone.Tags()[0] != "foo" { vt := v_clone.Tagger()
if len(vt.Tags()) < 1 || vt.Tags()[0] != "foo" {
t.Fatal("Tag on LinksTo did not persist") t.Fatal("Tag on LinksTo did not persist")
} }
} }
@ -173,7 +175,7 @@ func TestLinksToOptimization(t *testing.T) {
func TestRemoveTriple(t *testing.T) { func TestRemoveTriple(t *testing.T) {
ts, _ := makeTestStore(simpleGraph) ts, _ := makeTestStore(simpleGraph)
ts.RemoveTriple(&graph.Triple{"E", "follows", "F", ""}) ts.RemoveTriple(&quad.Quad{"E", "follows", "F", ""})
fixed := ts.FixedIterator() fixed := ts.FixedIterator()
fixed.Add(ts.ValueOf("E")) fixed.Add(ts.ValueOf("E"))
@ -182,13 +184,13 @@ func TestRemoveTriple(t *testing.T) {
fixed2.Add(ts.ValueOf("follows")) fixed2.Add(ts.ValueOf("follows"))
innerAnd := iterator.NewAnd() innerAnd := iterator.NewAnd()
innerAnd.AddSubIterator(iterator.NewLinksTo(ts, fixed, graph.Subject)) innerAnd.AddSubIterator(iterator.NewLinksTo(ts, fixed, quad.Subject))
innerAnd.AddSubIterator(iterator.NewLinksTo(ts, fixed2, graph.Predicate)) innerAnd.AddSubIterator(iterator.NewLinksTo(ts, fixed2, quad.Predicate))
hasa := iterator.NewHasA(ts, innerAnd, graph.Object) hasa := iterator.NewHasA(ts, innerAnd, quad.Object)
newIt, _ := hasa.Optimize() newIt, _ := hasa.Optimize()
_, ok := newIt.Next() _, ok := graph.Next(newIt)
if ok { if ok {
t.Error("E should not have any followers.") t.Error("E should not have any followers.")
} }

View file

@ -24,12 +24,14 @@ import (
"github.com/google/cayley/graph" "github.com/google/cayley/graph"
"github.com/google/cayley/graph/iterator" "github.com/google/cayley/graph/iterator"
"github.com/google/cayley/quad"
) )
type Iterator struct { type Iterator struct {
iterator.Base uid uint64
ts *TripleStore tags graph.Tagger
dir graph.Direction qs *TripleStore
dir quad.Direction
iter *mgo.Iter iter *mgo.Iter
hash string hash string
name string name string
@ -37,60 +39,73 @@ type Iterator struct {
isAll bool isAll bool
constraint bson.M constraint bson.M
collection string collection string
result graph.Value
} }
func NewIterator(ts *TripleStore, collection string, d graph.Direction, val graph.Value) *Iterator { func NewIterator(qs *TripleStore, collection string, d quad.Direction, val graph.Value) *Iterator {
var m Iterator name := qs.NameOf(val)
iterator.BaseInit(&m.Base)
m.name = ts.NameOf(val) var constraint bson.M
m.collection = collection
switch d { switch d {
case graph.Subject: case quad.Subject:
m.constraint = bson.M{"Subject": m.name} constraint = bson.M{"Subject": name}
case graph.Predicate: case quad.Predicate:
m.constraint = bson.M{"Predicate": m.name} constraint = bson.M{"Predicate": name}
case graph.Object: case quad.Object:
m.constraint = bson.M{"Object": m.name} constraint = bson.M{"Object": name}
case graph.Provenance: case quad.Label:
m.constraint = bson.M{"Provenance": m.name} constraint = bson.M{"Label": name}
} }
m.ts = ts size, err := qs.db.C(collection).Find(constraint).Count()
m.dir = d
m.iter = ts.db.C(collection).Find(m.constraint).Iter()
size, err := ts.db.C(collection).Find(m.constraint).Count()
if err != nil { if err != nil {
// FIXME(kortschak) This should be passed back rather than just logging.
glog.Errorln("Trouble getting size for iterator! ", err) glog.Errorln("Trouble getting size for iterator! ", err)
return nil return nil
} }
m.size = int64(size)
m.hash = val.(string) return &Iterator{
m.isAll = false uid: iterator.NextUID(),
return &m name: name,
constraint: constraint,
collection: collection,
qs: qs,
dir: d,
iter: qs.db.C(collection).Find(constraint).Iter(),
size: int64(size),
hash: val.(string),
isAll: false,
}
} }
func NewAllIterator(ts *TripleStore, collection string) *Iterator { func NewAllIterator(qs *TripleStore, collection string) *Iterator {
var m Iterator size, err := qs.db.C(collection).Count()
m.ts = ts
m.dir = graph.Any
m.constraint = nil
m.collection = collection
m.iter = ts.db.C(collection).Find(nil).Iter()
size, err := ts.db.C(collection).Count()
if err != nil { if err != nil {
// FIXME(kortschak) This should be passed back rather than just logging.
glog.Errorln("Trouble getting size for iterator! ", err) glog.Errorln("Trouble getting size for iterator! ", err)
return nil return nil
} }
m.size = int64(size)
m.hash = "" return &Iterator{
m.isAll = true uid: iterator.NextUID(),
return &m qs: qs,
dir: quad.Any,
constraint: nil,
collection: collection,
iter: qs.db.C(collection).Find(nil).Iter(),
size: int64(size),
hash: "",
isAll: true,
}
}
func (it *Iterator) UID() uint64 {
return it.uid
} }
func (it *Iterator) Reset() { func (it *Iterator) Reset() {
it.iter.Close() it.iter.Close()
it.iter = it.ts.db.C(it.collection).Find(it.constraint).Iter() it.iter = it.qs.db.C(it.collection).Find(it.constraint).Iter()
} }
@ -98,15 +113,29 @@ func (it *Iterator) Close() {
it.iter.Close() it.iter.Close()
} }
func (it *Iterator) Clone() graph.Iterator { func (it *Iterator) Tagger() *graph.Tagger {
var newM graph.Iterator return &it.tags
if it.isAll { }
newM = NewAllIterator(it.ts, it.collection)
} else { func (it *Iterator) TagResults(dst map[string]graph.Value) {
newM = NewIterator(it.ts, it.collection, it.dir, it.hash) for _, tag := range it.tags.Tags() {
dst[tag] = it.Result()
} }
newM.CopyTagsFrom(it)
return newM for tag, value := range it.tags.Fixed() {
dst[tag] = value
}
}
func (it *Iterator) Clone() graph.Iterator {
var m *Iterator
if it.isAll {
m = NewAllIterator(it.qs, it.collection)
} else {
m = NewIterator(it.qs, it.collection, it.dir, it.hash)
}
m.tags.CopyFrom(it)
return m
} }
func (it *Iterator) Next() (graph.Value, bool) { func (it *Iterator) Next() (graph.Value, bool) {
@ -124,30 +153,47 @@ func (it *Iterator) Next() (graph.Value, bool) {
} }
return nil, false return nil, false
} }
it.Last = result.Id it.result = result.Id
return result.Id, true return result.Id, true
} }
func (it *Iterator) ResultTree() *graph.ResultTree {
return graph.NewResultTree(it.Result())
}
func (it *Iterator) Result() graph.Value {
return it.result
}
func (it *Iterator) NextResult() bool {
return false
}
// No subiterators.
func (it *Iterator) SubIterators() []graph.Iterator {
return nil
}
func (it *Iterator) Check(v graph.Value) bool { func (it *Iterator) Check(v graph.Value) bool {
graph.CheckLogIn(it, v) graph.CheckLogIn(it, v)
if it.isAll { if it.isAll {
it.Last = v it.result = v
return graph.CheckLogOut(it, v, true) return graph.CheckLogOut(it, v, true)
} }
var offset int var offset int
switch it.dir { switch it.dir {
case graph.Subject: case quad.Subject:
offset = 0 offset = 0
case graph.Predicate: case quad.Predicate:
offset = (it.ts.hasher.Size() * 2) offset = (it.qs.hasher.Size() * 2)
case graph.Object: case quad.Object:
offset = (it.ts.hasher.Size() * 2) * 2 offset = (it.qs.hasher.Size() * 2) * 2
case graph.Provenance: case quad.Label:
offset = (it.ts.hasher.Size() * 2) * 3 offset = (it.qs.hasher.Size() * 2) * 3
} }
val := v.(string)[offset : it.ts.hasher.Size()*2+offset] val := v.(string)[offset : it.qs.hasher.Size()*2+offset]
if val == it.hash { if val == it.hash {
it.Last = v it.result = v
return graph.CheckLogOut(it, v, true) return graph.CheckLogOut(it, v, true)
} }
return graph.CheckLogOut(it, v, false) return graph.CheckLogOut(it, v, false)

View file

@ -18,6 +18,7 @@ import (
"crypto/sha1" "crypto/sha1"
"encoding/hex" "encoding/hex"
"hash" "hash"
"io"
"log" "log"
"gopkg.in/mgo.v2" "gopkg.in/mgo.v2"
@ -26,8 +27,16 @@ import (
"github.com/barakmich/glog" "github.com/barakmich/glog"
"github.com/google/cayley/graph" "github.com/google/cayley/graph"
"github.com/google/cayley/graph/iterator" "github.com/google/cayley/graph/iterator"
"github.com/google/cayley/quad"
) )
func init() {
graph.RegisterTripleStore("mongo", newTripleStore, createNewMongoGraph)
}
// Guarantee we satisfy graph.Bulkloader.
var _ graph.BulkLoader = (*TripleStore)(nil)
const DefaultDBName = "cayley" const DefaultDBName = "cayley"
type TripleStore struct { type TripleStore struct {
@ -60,13 +69,13 @@ func createNewMongoGraph(addr string, options graph.Options) error {
db.C("triples").EnsureIndex(indexOpts) db.C("triples").EnsureIndex(indexOpts)
indexOpts.Key = []string{"Obj"} indexOpts.Key = []string{"Obj"}
db.C("triples").EnsureIndex(indexOpts) db.C("triples").EnsureIndex(indexOpts)
indexOpts.Key = []string{"Provenance"} indexOpts.Key = []string{"Label"}
db.C("triples").EnsureIndex(indexOpts) db.C("triples").EnsureIndex(indexOpts)
return nil return nil
} }
func newTripleStore(addr string, options graph.Options) (graph.TripleStore, error) { func newTripleStore(addr string, options graph.Options) (graph.TripleStore, error) {
var ts TripleStore var qs TripleStore
conn, err := mgo.Dial(addr) conn, err := mgo.Dial(addr)
if err != nil { if err != nil {
return nil, err return nil, err
@ -76,26 +85,26 @@ func newTripleStore(addr string, options graph.Options) (graph.TripleStore, erro
if val, ok := options.StringKey("database_name"); ok { if val, ok := options.StringKey("database_name"); ok {
dbName = val dbName = val
} }
ts.db = conn.DB(dbName) qs.db = conn.DB(dbName)
ts.session = conn qs.session = conn
ts.hasher = sha1.New() qs.hasher = sha1.New()
ts.idCache = NewIDLru(1 << 16) qs.idCache = NewIDLru(1 << 16)
return &ts, nil return &qs, nil
} }
func (ts *TripleStore) getIdForTriple(t *graph.Triple) string { func (qs *TripleStore) getIdForTriple(t *quad.Quad) string {
id := ts.ConvertStringToByteHash(t.Subject) id := qs.ConvertStringToByteHash(t.Subject)
id += ts.ConvertStringToByteHash(t.Predicate) id += qs.ConvertStringToByteHash(t.Predicate)
id += ts.ConvertStringToByteHash(t.Object) id += qs.ConvertStringToByteHash(t.Object)
id += ts.ConvertStringToByteHash(t.Provenance) id += qs.ConvertStringToByteHash(t.Label)
return id return id
} }
func (ts *TripleStore) ConvertStringToByteHash(s string) string { func (qs *TripleStore) ConvertStringToByteHash(s string) string {
ts.hasher.Reset() qs.hasher.Reset()
key := make([]byte, 0, ts.hasher.Size()) key := make([]byte, 0, qs.hasher.Size())
ts.hasher.Write([]byte(s)) qs.hasher.Write([]byte(s))
key = ts.hasher.Sum(key) key = qs.hasher.Sum(key)
return hex.EncodeToString(key) return hex.EncodeToString(key)
} }
@ -105,10 +114,10 @@ type MongoNode struct {
Size int "Size" Size int "Size"
} }
func (ts *TripleStore) updateNodeBy(node_name string, inc int) { func (qs *TripleStore) updateNodeBy(node_name string, inc int) {
var size MongoNode var size MongoNode
node := ts.ValueOf(node_name) node := qs.ValueOf(node_name)
err := ts.db.C("nodes").FindId(node).One(&size) err := qs.db.C("nodes").FindId(node).One(&size)
if err != nil { if err != nil {
if err.Error() == "not found" { if err.Error() == "not found" {
// Not found. Okay. // Not found. Okay.
@ -128,7 +137,7 @@ func (ts *TripleStore) updateNodeBy(node_name string, inc int) {
// Removing something... // Removing something...
if inc < 0 { if inc < 0 {
if size.Size <= 0 { if size.Size <= 0 {
err := ts.db.C("nodes").RemoveId(node) err := qs.db.C("nodes").RemoveId(node)
if err != nil { if err != nil {
glog.Error("Error: ", err, " while removing node ", node_name) glog.Error("Error: ", err, " while removing node ", node_name)
return return
@ -136,21 +145,21 @@ func (ts *TripleStore) updateNodeBy(node_name string, inc int) {
} }
} }
_, err2 := ts.db.C("nodes").UpsertId(node, size) _, err2 := qs.db.C("nodes").UpsertId(node, size)
if err2 != nil { if err2 != nil {
glog.Error("Error: ", err) glog.Error("Error: ", err)
} }
} }
func (ts *TripleStore) writeTriple(t *graph.Triple) bool { func (qs *TripleStore) writeTriple(t *quad.Quad) bool {
tripledoc := bson.M{ tripledoc := bson.M{
"_id": ts.getIdForTriple(t), "_id": qs.getIdForTriple(t),
"Subject": t.Subject, "Subject": t.Subject,
"Predicate": t.Predicate, "Predicate": t.Predicate,
"Object": t.Object, "Object": t.Object,
"Provenance": t.Provenance, "Label": t.Label,
} }
err := ts.db.C("triples").Insert(tripledoc) err := qs.db.C("triples").Insert(tripledoc)
if err != nil { if err != nil {
// Among the reasons I hate MongoDB. "Errors don't happen! Right guys?" // Among the reasons I hate MongoDB. "Errors don't happen! Right guys?"
if err.(*mgo.LastError).Code == 11000 { if err.(*mgo.LastError).Code == 11000 {
@ -162,98 +171,98 @@ func (ts *TripleStore) writeTriple(t *graph.Triple) bool {
return true return true
} }
func (ts *TripleStore) AddTriple(t *graph.Triple) { func (qs *TripleStore) AddTriple(t *quad.Quad) {
_ = ts.writeTriple(t) _ = qs.writeTriple(t)
ts.updateNodeBy(t.Subject, 1) qs.updateNodeBy(t.Subject, 1)
ts.updateNodeBy(t.Predicate, 1) qs.updateNodeBy(t.Predicate, 1)
ts.updateNodeBy(t.Object, 1) qs.updateNodeBy(t.Object, 1)
if t.Provenance != "" { if t.Label != "" {
ts.updateNodeBy(t.Provenance, 1) qs.updateNodeBy(t.Label, 1)
} }
} }
func (ts *TripleStore) AddTripleSet(in []*graph.Triple) { func (qs *TripleStore) AddTripleSet(in []*quad.Quad) {
ts.session.SetSafe(nil) qs.session.SetSafe(nil)
ids := make(map[string]int) ids := make(map[string]int)
for _, t := range in { for _, t := range in {
wrote := ts.writeTriple(t) wrote := qs.writeTriple(t)
if wrote { if wrote {
ids[t.Subject]++ ids[t.Subject]++
ids[t.Object]++ ids[t.Object]++
ids[t.Predicate]++ ids[t.Predicate]++
if t.Provenance != "" { if t.Label != "" {
ids[t.Provenance]++ ids[t.Label]++
} }
} }
} }
for k, v := range ids { for k, v := range ids {
ts.updateNodeBy(k, v) qs.updateNodeBy(k, v)
} }
ts.session.SetSafe(&mgo.Safe{}) qs.session.SetSafe(&mgo.Safe{})
} }
func (ts *TripleStore) RemoveTriple(t *graph.Triple) { func (qs *TripleStore) RemoveTriple(t *quad.Quad) {
err := ts.db.C("triples").RemoveId(ts.getIdForTriple(t)) err := qs.db.C("triples").RemoveId(qs.getIdForTriple(t))
if err == mgo.ErrNotFound { if err == mgo.ErrNotFound {
return return
} else if err != nil { } else if err != nil {
log.Println("Error: ", err, " while removing triple ", t) log.Println("Error: ", err, " while removing triple ", t)
return return
} }
ts.updateNodeBy(t.Subject, -1) qs.updateNodeBy(t.Subject, -1)
ts.updateNodeBy(t.Predicate, -1) qs.updateNodeBy(t.Predicate, -1)
ts.updateNodeBy(t.Object, -1) qs.updateNodeBy(t.Object, -1)
if t.Provenance != "" { if t.Label != "" {
ts.updateNodeBy(t.Provenance, -1) qs.updateNodeBy(t.Label, -1)
} }
} }
func (ts *TripleStore) Triple(val graph.Value) *graph.Triple { func (qs *TripleStore) Quad(val graph.Value) *quad.Quad {
var bsonDoc bson.M var bsonDoc bson.M
err := ts.db.C("triples").FindId(val.(string)).One(&bsonDoc) err := qs.db.C("triples").FindId(val.(string)).One(&bsonDoc)
if err != nil { if err != nil {
log.Println("Error: Couldn't retrieve triple", val.(string), err) log.Println("Error: Couldn't retrieve triple", val.(string), err)
} }
return &graph.Triple{ return &quad.Quad{
bsonDoc["Subject"].(string), bsonDoc["Subject"].(string),
bsonDoc["Predicate"].(string), bsonDoc["Predicate"].(string),
bsonDoc["Object"].(string), bsonDoc["Object"].(string),
bsonDoc["Provenance"].(string), bsonDoc["Label"].(string),
} }
} }
func (ts *TripleStore) TripleIterator(d graph.Direction, val graph.Value) graph.Iterator { func (qs *TripleStore) TripleIterator(d quad.Direction, val graph.Value) graph.Iterator {
return NewIterator(ts, "triples", d, val) return NewIterator(qs, "triples", d, val)
} }
func (ts *TripleStore) NodesAllIterator() graph.Iterator { func (qs *TripleStore) NodesAllIterator() graph.Iterator {
return NewAllIterator(ts, "nodes") return NewAllIterator(qs, "nodes")
} }
func (ts *TripleStore) TriplesAllIterator() graph.Iterator { func (qs *TripleStore) TriplesAllIterator() graph.Iterator {
return NewAllIterator(ts, "triples") return NewAllIterator(qs, "triples")
} }
func (ts *TripleStore) ValueOf(s string) graph.Value { func (qs *TripleStore) ValueOf(s string) graph.Value {
return ts.ConvertStringToByteHash(s) return qs.ConvertStringToByteHash(s)
} }
func (ts *TripleStore) NameOf(v graph.Value) string { func (qs *TripleStore) NameOf(v graph.Value) string {
val, ok := ts.idCache.Get(v.(string)) val, ok := qs.idCache.Get(v.(string))
if ok { if ok {
return val return val
} }
var node MongoNode var node MongoNode
err := ts.db.C("nodes").FindId(v.(string)).One(&node) err := qs.db.C("nodes").FindId(v.(string)).One(&node)
if err != nil { if err != nil {
log.Println("Error: Couldn't retrieve node", v.(string), err) log.Println("Error: Couldn't retrieve node", v.(string), err)
} }
ts.idCache.Put(v.(string), node.Name) qs.idCache.Put(v.(string), node.Name)
return node.Name return node.Name
} }
func (ts *TripleStore) Size() int64 { func (qs *TripleStore) Size() int64 {
count, err := ts.db.C("triples").Count() count, err := qs.db.C("triples").Count()
if err != nil { if err != nil {
glog.Error("Error: ", err) glog.Error("Error: ", err)
return 0 return 0
@ -265,40 +274,48 @@ func compareStrings(a, b graph.Value) bool {
return a.(string) == b.(string) return a.(string) == b.(string)
} }
func (ts *TripleStore) FixedIterator() graph.FixedIterator { func (qs *TripleStore) FixedIterator() graph.FixedIterator {
return iterator.NewFixedIteratorWithCompare(compareStrings) return iterator.NewFixedIteratorWithCompare(compareStrings)
} }
func (ts *TripleStore) Close() { func (qs *TripleStore) Close() {
ts.db.Session.Close() qs.db.Session.Close()
} }
func (ts *TripleStore) TripleDirection(in graph.Value, d graph.Direction) graph.Value { func (qs *TripleStore) TripleDirection(in graph.Value, d quad.Direction) graph.Value {
// Maybe do the trick here // Maybe do the trick here
var offset int var offset int
switch d { switch d {
case graph.Subject: case quad.Subject:
offset = 0 offset = 0
case graph.Predicate: case quad.Predicate:
offset = (ts.hasher.Size() * 2) offset = (qs.hasher.Size() * 2)
case graph.Object: case quad.Object:
offset = (ts.hasher.Size() * 2) * 2 offset = (qs.hasher.Size() * 2) * 2
case graph.Provenance: case quad.Label:
offset = (ts.hasher.Size() * 2) * 3 offset = (qs.hasher.Size() * 2) * 3
} }
val := in.(string)[offset : ts.hasher.Size()*2+offset] val := in.(string)[offset : qs.hasher.Size()*2+offset]
return val return val
} }
func (ts *TripleStore) BulkLoad(t_chan chan *graph.Triple) bool { func (qs *TripleStore) BulkLoad(dec quad.Unmarshaler) error {
if ts.Size() != 0 { if qs.Size() != 0 {
return false return graph.ErrCannotBulkLoad
} }
ts.session.SetSafe(nil) qs.session.SetSafe(nil)
for triple := range t_chan { for {
ts.writeTriple(triple) q, err := dec.Unmarshal()
if err != nil {
if err != io.EOF {
return err
}
break
}
qs.writeTriple(q)
} }
outputTo := bson.M{"replace": "nodes", "sharded": true} outputTo := bson.M{"replace": "nodes", "sharded": true}
glog.Infoln("Mapreducing") glog.Infoln("Mapreducing")
job := mgo.MapReduce{ job := mgo.MapReduce{
@ -311,8 +328,8 @@ func (ts *TripleStore) BulkLoad(t_chan chan *graph.Triple) bool {
emit(s_key, {"_id": s_key, "Name" : this.Subject, "Size" : 1}) emit(s_key, {"_id": s_key, "Name" : this.Subject, "Size" : 1})
emit(p_key, {"_id": p_key, "Name" : this.Predicate, "Size" : 1}) emit(p_key, {"_id": p_key, "Name" : this.Predicate, "Size" : 1})
emit(o_key, {"_id": o_key, "Name" : this.Object, "Size" : 1}) emit(o_key, {"_id": o_key, "Name" : this.Object, "Size" : 1})
if (this.Provenance != "") { if (this.Label != "") {
emit(c_key, {"_id": c_key, "Name" : this.Provenance, "Size" : 1}) emit(c_key, {"_id": c_key, "Name" : this.Label, "Size" : 1})
} }
} }
`, `,
@ -330,16 +347,13 @@ func (ts *TripleStore) BulkLoad(t_chan chan *graph.Triple) bool {
`, `,
Out: outputTo, Out: outputTo,
} }
ts.db.C("triples").Find(nil).MapReduce(&job, nil) qs.db.C("triples").Find(nil).MapReduce(&job, nil)
glog.Infoln("Fixing") glog.Infoln("Fixing")
ts.db.Run(bson.D{{"eval", `function() { db.nodes.find().forEach(function (result) { qs.db.Run(bson.D{{"eval", `function() { db.nodes.find().forEach(function (result) {
db.nodes.update({"_id": result._id}, result.value) db.nodes.update({"_id": result._id}, result.value)
}) }`}, {"args", bson.D{}}}, nil) }) }`}, {"args", bson.D{}}}, nil)
ts.session.SetSafe(&mgo.Safe{}) qs.session.SetSafe(&mgo.Safe{})
return true
}
func init() { return nil
graph.RegisterTripleStore("mongo", newTripleStore, createNewMongoGraph)
} }

View file

@ -37,14 +37,15 @@ func (ts *TripleStore) optimizeLinksTo(it *iterator.LinksTo) (graph.Iterator, bo
if primary.Type() == graph.Fixed { if primary.Type() == graph.Fixed {
size, _ := primary.Size() size, _ := primary.Size()
if size == 1 { if size == 1 {
val, ok := primary.Next() val, ok := graph.Next(primary)
if !ok { if !ok {
panic("Sizes lie") panic("Sizes lie")
} }
newIt := ts.TripleIterator(it.Direction(), val) newIt := ts.TripleIterator(it.Direction(), val)
newIt.CopyTagsFrom(it) nt := newIt.Tagger()
for _, tag := range primary.Tags() { nt.CopyFrom(it)
newIt.AddFixedTag(tag, val) for _, tag := range primary.Tagger().Tags() {
nt.AddFixed(tag, val)
} }
it.Close() it.Close()
return newIt, true return newIt, true

View file

@ -40,7 +40,7 @@ func (t *ResultTree) AddSubtree(sub *ResultTree) {
t.subtrees = append(t.subtrees, sub) t.subtrees = append(t.subtrees, sub)
} }
func StringResultTreeEvaluator(it Iterator) string { func StringResultTreeEvaluator(it Nexter) string {
ok := true ok := true
out := "" out := ""
for { for {
@ -59,6 +59,6 @@ func StringResultTreeEvaluator(it Iterator) string {
return out return out
} }
func PrintResultTreeEvaluator(it Iterator) { func PrintResultTreeEvaluator(it Nexter) {
fmt.Print(StringResultTreeEvaluator(it)) fmt.Print(StringResultTreeEvaluator(it))
} }

View file

@ -19,6 +19,7 @@ import (
"github.com/google/cayley/graph" "github.com/google/cayley/graph"
"github.com/google/cayley/graph/iterator" "github.com/google/cayley/graph/iterator"
"github.com/google/cayley/quad"
) )
func BuildIteratorTreeForQuery(ts graph.TripleStore, query string) graph.Iterator { func BuildIteratorTreeForQuery(ts graph.TripleStore, query string) graph.Iterator {
@ -189,7 +190,7 @@ func buildIteratorTree(tree *peg.ExpressionTree, ts graph.TripleStore) graph.Ite
nodeID := getIdentString(tree) nodeID := getIdentString(tree)
if tree.Children[0].Name == "Variable" { if tree.Children[0].Name == "Variable" {
allIt := ts.NodesAllIterator() allIt := ts.NodesAllIterator()
allIt.AddTag(nodeID) allIt.Tagger().Add(nodeID)
out = allIt out = allIt
} else { } else {
n := nodeID n := nodeID
@ -208,7 +209,7 @@ func buildIteratorTree(tree *peg.ExpressionTree, ts graph.TripleStore) graph.Ite
i++ i++
} }
it := buildIteratorTree(tree.Children[i], ts) it := buildIteratorTree(tree.Children[i], ts)
lto := iterator.NewLinksTo(ts, it, graph.Predicate) lto := iterator.NewLinksTo(ts, it, quad.Predicate)
return lto return lto
case "RootConstraint": case "RootConstraint":
constraintCount := 0 constraintCount := 0
@ -229,16 +230,16 @@ func buildIteratorTree(tree *peg.ExpressionTree, ts graph.TripleStore) graph.Ite
return and return and
case "Constraint": case "Constraint":
var hasa *iterator.HasA var hasa *iterator.HasA
topLevelDir := graph.Subject topLevelDir := quad.Subject
subItDir := graph.Object subItDir := quad.Object
subAnd := iterator.NewAnd() subAnd := iterator.NewAnd()
isOptional := false isOptional := false
for _, c := range tree.Children { for _, c := range tree.Children {
switch c.Name { switch c.Name {
case "PredIdentifier": case "PredIdentifier":
if c.Children[0].Name == "Reverse" { if c.Children[0].Name == "Reverse" {
topLevelDir = graph.Object topLevelDir = quad.Object
subItDir = graph.Subject subItDir = quad.Subject
} }
it := buildIteratorTree(c, ts) it := buildIteratorTree(c, ts)
subAnd.AddSubIterator(it) subAnd.AddSubIterator(it)

View file

@ -18,6 +18,8 @@ import (
"testing" "testing"
"github.com/google/cayley/graph" "github.com/google/cayley/graph"
"github.com/google/cayley/quad"
_ "github.com/google/cayley/graph/memstore" _ "github.com/google/cayley/graph/memstore"
) )
@ -30,21 +32,21 @@ func TestBadParse(t *testing.T) {
var testQueries = []struct { var testQueries = []struct {
message string message string
add *graph.Triple add *quad.Quad
query string query string
typ graph.Type typ graph.Type
expect string expect string
}{ }{
{ {
message: "get a single triple linkage", message: "get a single triple linkage",
add: &graph.Triple{"i", "can", "win", ""}, add: &quad.Quad{"i", "can", "win", ""},
query: "($a (:can \"win\"))", query: "($a (:can \"win\"))",
typ: graph.And, typ: graph.And,
expect: "i", expect: "i",
}, },
{ {
message: "get a single triple linkage", message: "get a single triple linkage",
add: &graph.Triple{"i", "can", "win", ""}, add: &quad.Quad{"i", "can", "win", ""},
query: "(\"i\" (:can $a))", query: "(\"i\" (:can $a))",
typ: graph.And, typ: graph.And,
expect: "i", expect: "i",
@ -65,7 +67,7 @@ func TestMemstoreBackedSexp(t *testing.T) {
if it.Type() != test.typ { if it.Type() != test.typ {
t.Errorf("Incorrect type for %s, got:%q expect %q", test.message, it.Type(), test.expect) t.Errorf("Incorrect type for %s, got:%q expect %q", test.message, it.Type(), test.expect)
} }
got, ok := it.Next() got, ok := graph.Next(it)
if !ok { if !ok {
t.Errorf("Failed to %s", test.message) t.Errorf("Failed to %s", test.message)
} }
@ -77,8 +79,8 @@ func TestMemstoreBackedSexp(t *testing.T) {
func TestTreeConstraintParse(t *testing.T) { func TestTreeConstraintParse(t *testing.T) {
ts, _ := graph.NewTripleStore("memstore", "", nil) ts, _ := graph.NewTripleStore("memstore", "", nil)
ts.AddTriple(&graph.Triple{"i", "like", "food", ""}) ts.AddTriple(&quad.Quad{"i", "like", "food", ""})
ts.AddTriple(&graph.Triple{"food", "is", "good", ""}) ts.AddTriple(&quad.Quad{"food", "is", "good", ""})
query := "(\"i\"\n" + query := "(\"i\"\n" +
"(:like\n" + "(:like\n" +
"($a (:is :good))))" "($a (:is :good))))"
@ -86,7 +88,7 @@ func TestTreeConstraintParse(t *testing.T) {
if it.Type() != graph.And { if it.Type() != graph.And {
t.Error("Odd iterator tree. Got: %s", it.DebugString(0)) t.Error("Odd iterator tree. Got: %s", it.DebugString(0))
} }
out, ok := it.Next() out, ok := graph.Next(it)
if !ok { if !ok {
t.Error("Got no results") t.Error("Got no results")
} }
@ -97,13 +99,13 @@ func TestTreeConstraintParse(t *testing.T) {
func TestTreeConstraintTagParse(t *testing.T) { func TestTreeConstraintTagParse(t *testing.T) {
ts, _ := graph.NewTripleStore("memstore", "", nil) ts, _ := graph.NewTripleStore("memstore", "", nil)
ts.AddTriple(&graph.Triple{"i", "like", "food", ""}) ts.AddTriple(&quad.Quad{"i", "like", "food", ""})
ts.AddTriple(&graph.Triple{"food", "is", "good", ""}) ts.AddTriple(&quad.Quad{"food", "is", "good", ""})
query := "(\"i\"\n" + query := "(\"i\"\n" +
"(:like\n" + "(:like\n" +
"($a (:is :good))))" "($a (:is :good))))"
it := BuildIteratorTreeForQuery(ts, query) it := BuildIteratorTreeForQuery(ts, query)
_, ok := it.Next() _, ok := graph.Next(it)
if !ok { if !ok {
t.Error("Got no results") t.Error("Got no results")
} }
@ -117,7 +119,7 @@ func TestTreeConstraintTagParse(t *testing.T) {
func TestMultipleConstraintParse(t *testing.T) { func TestMultipleConstraintParse(t *testing.T) {
ts, _ := graph.NewTripleStore("memstore", "", nil) ts, _ := graph.NewTripleStore("memstore", "", nil)
for _, tv := range []*graph.Triple{ for _, tv := range []*quad.Quad{
{"i", "like", "food", ""}, {"i", "like", "food", ""},
{"i", "like", "beer", ""}, {"i", "like", "beer", ""},
{"you", "like", "beer", ""}, {"you", "like", "beer", ""},
@ -133,14 +135,14 @@ func TestMultipleConstraintParse(t *testing.T) {
if it.Type() != graph.And { if it.Type() != graph.And {
t.Error("Odd iterator tree. Got: %s", it.DebugString(0)) t.Error("Odd iterator tree. Got: %s", it.DebugString(0))
} }
out, ok := it.Next() out, ok := graph.Next(it)
if !ok { if !ok {
t.Error("Got no results") t.Error("Got no results")
} }
if out != ts.ValueOf("i") { if out != ts.ValueOf("i") {
t.Errorf("Got %d, expected %d", out, ts.ValueOf("i")) t.Errorf("Got %d, expected %d", out, ts.ValueOf("i"))
} }
_, ok = it.Next() _, ok = graph.Next(it)
if ok { if ok {
t.Error("Too many results") t.Error("Too many results")
} }

View file

@ -77,7 +77,7 @@ func (s *Session) ExecInput(input string, out chan interface{}, limit int) {
} }
nResults := 0 nResults := 0
for { for {
_, ok := it.Next() _, ok := graph.Next(it)
if !ok { if !ok {
break break
} }

View file

@ -23,7 +23,9 @@ package graph
import ( import (
"errors" "errors"
"github.com/barakmich/glog" "github.com/barakmich/glog"
"github.com/google/cayley/quad"
) )
// Defines an opaque "triple store value" type. However the backend wishes to // Defines an opaque "triple store value" type. However the backend wishes to
@ -38,21 +40,21 @@ type Value interface{}
type TripleStore interface { type TripleStore interface {
// Add a triple to the store. // Add a triple to the store.
AddTriple(*Triple) AddTriple(*quad.Quad)
// Add a set of triples to the store, atomically if possible. // Add a set of triples to the store, atomically if possible.
AddTripleSet([]*Triple) AddTripleSet([]*quad.Quad)
// Removes a triple matching the given one from the database, // Removes a triple matching the given one from the database,
// if it exists. Does nothing otherwise. // if it exists. Does nothing otherwise.
RemoveTriple(*Triple) RemoveTriple(*quad.Quad)
// Given an opaque token, returns the triple for that token from the store. // Given an opaque token, returns the triple for that token from the store.
Triple(Value) *Triple Quad(Value) *quad.Quad
// Given a direction and a token, creates an iterator of links which have // Given a direction and a token, creates an iterator of links which have
// that node token in that directional field. // that node token in that directional field.
TripleIterator(Direction, Value) Iterator TripleIterator(quad.Direction, Value) Iterator
// Returns an iterator enumerating all nodes in the graph. // Returns an iterator enumerating all nodes in the graph.
NodesAllIterator() Iterator NodesAllIterator() Iterator
@ -89,8 +91,8 @@ type TripleStore interface {
// gives the TripleStore the opportunity to make this optimization. // gives the TripleStore the opportunity to make this optimization.
// //
// Iterators will call this. At worst, a valid implementation is // Iterators will call this. At worst, a valid implementation is
// ts.IdFor(ts.Triple(triple_id).Get(dir)) // ts.IdFor(ts.quad.Quad(id).Get(dir))
TripleDirection(triple_id Value, d Direction) Value TripleDirection(id Value, d quad.Direction) Value
} }
type Options map[string]interface{} type Options map[string]interface{}
@ -122,14 +124,10 @@ func (d Options) StringKey(key string) (string, bool) {
var ErrCannotBulkLoad = errors.New("triplestore: cannot bulk load") var ErrCannotBulkLoad = errors.New("triplestore: cannot bulk load")
type BulkLoader interface { type BulkLoader interface {
// BulkLoad loads Triples from a TripleUnmarshaler in bulk to the TripleStore. // BulkLoad loads Quads from a quad.Unmarshaler in bulk to the TripleStore.
// It returns ErrCannotBulkLoad if bulk loading is not possible. For example if // It returns ErrCannotBulkLoad if bulk loading is not possible. For example if
// you cannot load in bulk to a non-empty database, and the db is non-empty. // you cannot load in bulk to a non-empty database, and the db is non-empty.
BulkLoad(TripleUnmarshaler) error BulkLoad(quad.Unmarshaler) error
}
type TripleUnmarshaler interface {
Unmarshal() (*Triple, error)
} }
type NewStoreFunc func(string, Options) (TripleStore, error) type NewStoreFunc func(string, Options) (TripleStore, error)

View file

@ -19,22 +19,22 @@ import (
"reflect" "reflect"
"testing" "testing"
"github.com/google/cayley/graph" "github.com/google/cayley/quad"
) )
var parseTests = []struct { var parseTests = []struct {
message string message string
input string input string
expect []*graph.Triple expect []*quad.Quad
err error err error
}{ }{
{ {
message: "parse correct JSON", message: "parse correct JSON",
input: `[ input: `[
{"subject": "foo", "predicate": "bar", "object": "baz"}, {"subject": "foo", "predicate": "bar", "object": "baz"},
{"subject": "foo", "predicate": "bar", "object": "baz", "provenance": "graph"} {"subject": "foo", "predicate": "bar", "object": "baz", "label": "graph"}
]`, ]`,
expect: []*graph.Triple{ expect: []*quad.Quad{
{"foo", "bar", "baz", ""}, {"foo", "bar", "baz", ""},
{"foo", "bar", "baz", "graph"}, {"foo", "bar", "baz", "graph"},
}, },
@ -45,7 +45,7 @@ var parseTests = []struct {
input: `[ input: `[
{"subject": "foo", "predicate": "bar", "object": "foo", "something_else": "extra data"} {"subject": "foo", "predicate": "bar", "object": "foo", "something_else": "extra data"}
]`, ]`,
expect: []*graph.Triple{ expect: []*quad.Quad{
{"foo", "bar", "foo", ""}, {"foo", "bar", "foo", ""},
}, },
err: nil, err: nil,
@ -56,7 +56,7 @@ var parseTests = []struct {
{"subject": "foo", "predicate": "bar"} {"subject": "foo", "predicate": "bar"}
]`, ]`,
expect: nil, expect: nil,
err: fmt.Errorf("Invalid triple at index %d. %v", 0, &graph.Triple{"foo", "bar", "", ""}), err: fmt.Errorf("Invalid triple at index %d. %v", 0, &quad.Quad{"foo", "bar", "", ""}),
}, },
} }

View file

@ -25,12 +25,12 @@ import (
"github.com/barakmich/glog" "github.com/barakmich/glog"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
"github.com/google/cayley/graph" "github.com/google/cayley/quad"
"github.com/google/cayley/nquads" "github.com/google/cayley/quad/nquads"
) )
func ParseJsonToTripleList(jsonBody []byte) ([]*graph.Triple, error) { func ParseJsonToTripleList(jsonBody []byte) ([]*quad.Quad, error) {
var tripleList []*graph.Triple var tripleList []*quad.Quad
err := json.Unmarshal(jsonBody, &tripleList) err := json.Unmarshal(jsonBody, &tripleList)
if err != nil { if err != nil {
return nil, err return nil, err
@ -83,7 +83,7 @@ func (api *Api) ServeV1WriteNQuad(w http.ResponseWriter, r *http.Request, params
var ( var (
n int n int
block = make([]*graph.Triple, 0, blockSize) block = make([]*quad.Quad, 0, blockSize)
) )
for { for {
t, err := dec.Unmarshal() t, err := dec.Unmarshal()

View file

@ -1,138 +0,0 @@
// Copyright 2014 The Cayley Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package nquads
import (
"reflect"
"testing"
"github.com/google/cayley/graph"
)
var testNTriples = []struct {
message string
input string
expect *graph.Triple
err error
}{
// NTriple tests.
{
message: "not parse invalid triples",
input: "invalid",
expect: nil,
err: ErrAbsentPredicate,
},
{
message: "invalid internal quote",
input: `":103032" "/film/performance/character" "Walter "Teacher" Cole" .`,
expect: nil,
err: ErrUnterminated,
},
{
message: "not parse comments",
input: "# nominally valid triple .",
expect: nil,
err: nil,
},
{
message: "parse simple triples",
input: "this is valid .",
expect: &graph.Triple{"this", "is", "valid", ""},
},
{
message: "parse quoted triples",
input: `this is "valid too" .`,
expect: &graph.Triple{"this", "is", "valid too", ""},
},
{
message: "parse escaped quoted triples",
input: `he said "\"That's all folks\"" .`,
expect: &graph.Triple{"he", "said", `"That's all folks"`, ""},
},
{
message: "parse an example real triple",
input: `":/guid/9202a8c04000641f80000000010c843c" "name" "George Morris" .`,
expect: &graph.Triple{":/guid/9202a8c04000641f80000000010c843c", "name", "George Morris", ""},
},
{
message: "parse a pathologically spaced triple",
input: "foo is \"\\tA big tough\\r\\nDeal\\\\\" .",
expect: &graph.Triple{"foo", "is", "\tA big tough\r\nDeal\\", ""},
},
// NQuad tests.
{
message: "parse a simple quad",
input: "this is valid quad .",
expect: &graph.Triple{"this", "is", "valid", "quad"},
},
{
message: "parse a quoted quad",
input: `this is valid "quad thing" .`,
expect: &graph.Triple{"this", "is", "valid", "quad thing"},
},
{
message: "parse crazy escaped quads",
input: `"\"this" "\"is" "\"valid" "\"quad thing".`,
expect: &graph.Triple{`"this`, `"is`, `"valid`, `"quad thing`},
},
// NTriple official tests.
{
message: "handle simple case with comments",
input: "<http://example/s> <http://example/p> <http://example/o> . # comment",
expect: &graph.Triple{"http://example/s", "http://example/p", "http://example/o", ""},
},
{
message: "handle simple case with comments",
input: "<http://example/s> <http://example/p> _:o . # comment",
expect: &graph.Triple{"http://example/s", "http://example/p", "_:o", ""},
},
{
message: "handle simple case with comments",
input: "<http://example/s> <http://example/p> \"o\" . # comment",
expect: &graph.Triple{"http://example/s", "http://example/p", "o", ""},
},
{
message: "handle simple case with comments",
input: "<http://example/s> <http://example/p> \"o\"^^<http://example/dt> . # comment",
expect: &graph.Triple{"http://example/s", "http://example/p", "o", ""},
},
{
message: "handle simple case with comments",
input: "<http://example/s> <http://example/p> \"o\"@en . # comment",
expect: &graph.Triple{"http://example/s", "http://example/p", "o", ""},
},
}
func TestParse(t *testing.T) {
for _, test := range testNTriples {
got, err := Parse(test.input)
if err != test.err {
t.Errorf("Unexpected error when %s: got:%v expect:%v", test.message, err, test.err)
}
if !reflect.DeepEqual(got, test.expect) {
t.Errorf("Failed to %s, %q, got:%q expect:%q", test.message, test.input, got, test.expect)
}
}
}
var result *graph.Triple
func BenchmarkParser(b *testing.B) {
for n := 0; n < b.N; n++ {
result, _ = Parse("<http://example/s> <http://example/p> \"object of some real\\tlength\"@en . # comment")
}
}

95
quad/cquads/actions.rl Normal file
View file

@ -0,0 +1,95 @@
// Copyright 2014 The Cayley Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
%%{
machine quads;
action Escape {
isEscaped = true
}
action Quote {
isQuoted = true
}
action StartSubject {
subject = p
}
action StartPredicate {
predicate = p
}
action StartObject {
object = p
}
action StartLabel {
label = p
}
action SetSubject {
if subject < 0 {
panic("unexpected parser state: subject start not set")
}
q.Subject = unEscape(data[subject:p], isQuoted, isEscaped)
isEscaped = false
isQuoted = false
}
action SetPredicate {
if predicate < 0 {
panic("unexpected parser state: predicate start not set")
}
q.Predicate = unEscape(data[predicate:p], isQuoted, isEscaped)
isEscaped = false
isQuoted = false
}
action SetObject {
if object < 0 {
panic("unexpected parser state: object start not set")
}
q.Object = unEscape(data[object:p], isQuoted, isEscaped)
isEscaped = false
isQuoted = false
}
action SetLabel {
if label < 0 {
panic("unexpected parser state: label start not set")
}
q.Label = unEscape(data[label:p], isQuoted, isEscaped)
isEscaped = false
isQuoted = false
}
action Return {
return q, nil
}
action Comment {
}
action Error {
if p < len(data) {
if r := data[p]; r < unicode.MaxASCII {
return q, fmt.Errorf("%v: unexpected rune %q at %d", quad.ErrInvalid, data[p], p)
} else {
return q, fmt.Errorf("%v: unexpected rune %q (\\u%04x) at %d", quad.ErrInvalid, data[p], data[p], p)
}
}
return q, quad.ErrIncomplete
}
}%%

144
quad/cquads/cquads.go Normal file
View file

@ -0,0 +1,144 @@
// Copyright 2014 The Cayley Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package cquads implements parsing N-Quads like line-based syntax
// for RDF datasets.
//
// N-Quad parsing is performed as based on a simplified grammar derived from
// the N-Quads grammar defined by http://www.w3.org/TR/n-quads/.
//
// For a complete definition of the grammar, see cquads.rl.
package cquads
import (
"bufio"
"bytes"
"fmt"
"io"
"strconv"
"github.com/google/cayley/quad"
)
// Parse returns a valid quad.Quad or a non-nil error. Parse does
// handle comments except where the comment placement does not prevent
// a complete valid quad.Quad from being defined.
func Parse(str string) (*quad.Quad, error) {
q, err := parse([]rune(str))
return &q, err
}
// Decoder implements simplified N-Quad document parsing.
type Decoder struct {
r *bufio.Reader
line []byte
}
// NewDecoder returns an N-Quad decoder that takes its input from the
// provided io.Reader.
func NewDecoder(r io.Reader) *Decoder {
return &Decoder{r: bufio.NewReader(r)}
}
// Unmarshal returns the next valid N-Quad as a quad.Quad, or an error.
func (dec *Decoder) Unmarshal() (*quad.Quad, error) {
dec.line = dec.line[:0]
var line []byte
for {
for {
l, pre, err := dec.r.ReadLine()
if err != nil {
return nil, err
}
dec.line = append(dec.line, l...)
if !pre {
break
}
}
if line = bytes.TrimSpace(dec.line); len(line) != 0 && line[0] != '#' {
break
}
dec.line = dec.line[:0]
}
triple, err := Parse(string(line))
if err != nil {
return nil, fmt.Errorf("failed to parse %q: %v", dec.line, err)
}
if triple == nil {
return dec.Unmarshal()
}
return triple, nil
}
func unEscape(r []rune, isQuoted, isEscaped bool) string {
if isQuoted {
r = r[1 : len(r)-1]
}
if len(r) >= 2 && r[0] == '<' && r[len(r)-1] == '>' {
return string(r[1 : len(r)-1])
}
if !isEscaped {
return string(r)
}
buf := bytes.NewBuffer(make([]byte, 0, len(r)))
for i := 0; i < len(r); {
switch r[i] {
case '\\':
i++
var c byte
switch r[i] {
case 't':
c = '\t'
case 'b':
c = '\b'
case 'n':
c = '\n'
case 'r':
c = '\r'
case 'f':
c = '\f'
case '"':
c = '"'
case '\'':
c = '\''
case '\\':
c = '\\'
case 'u':
rc, err := strconv.ParseInt(string(r[i+1:i+5]), 16, 32)
if err != nil {
panic(fmt.Errorf("internal parser error: %v", err))
}
buf.WriteRune(rune(rc))
i += 5
continue
case 'U':
rc, err := strconv.ParseInt(string(r[i+1:i+9]), 16, 32)
if err != nil {
panic(fmt.Errorf("internal parser error: %v", err))
}
buf.WriteRune(rune(rc))
i += 9
continue
}
buf.WriteByte(c)
default:
buf.WriteRune(r[i])
}
i++
}
return buf.String()
}

106
quad/cquads/cquads.rl Normal file
View file

@ -0,0 +1,106 @@
// Copyright 2014 The Cayley Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Ragel gramar definition derived from http://www.w3.org/TR/n-quads/#sec-grammar.
%%{
machine quads;
alphtype rune;
PN_CHARS_BASE = [A-Za-z]
| 0x00c0 .. 0x00d6
| 0x00d8 .. 0x00f6
| 0x00f8 .. 0x02ff
| 0x0370 .. 0x037d
| 0x037f .. 0x1fff
| 0x200c .. 0x200d
| 0x2070 .. 0x218f
| 0x2c00 .. 0x2fef
| 0x3001 .. 0xd7ff
| 0xf900 .. 0xfdcf
| 0xfdf0 .. 0xfffd
| 0x10000 .. 0xeffff
;
PN_CHARS_U = PN_CHARS_BASE | '_' | ':' ;
PN_CHARS = PN_CHARS_U
| '-'
| [0-9]
| 0xb7
| 0x0300 .. 0x036f
| 0x203f .. 0x2040
;
ECHAR = ('\\' [tbnrf"'\\]) %Escape ;
UCHAR = ('\\u' xdigit {4}
| '\\U' xdigit {8}) %Escape
;
BLANK_NODE_LABEL = '_:' (PN_CHARS_U | [0-9]) ((PN_CHARS | '.')* PN_CHARS)? ;
STRING_LITERAL = (
'!'
| '#' .. '['
| ']' .. 0x7e
| 0x80 .. 0x10ffff
| ECHAR
| UCHAR)+ - ('_:' | any* '.' | '#' any*)
;
STRING_LITERAL_QUOTE = '"' (
0x00 .. 0x09
| 0x0b .. 0x0c
| 0x0e .. '!'
| '#' .. '['
| ']' .. 0x10ffff
| ECHAR
| UCHAR)*
'"'
;
IRIREF = '<' (
'!' .. ';'
| '='
| '?' .. '['
| ']'
| '_'
| 'a' .. 'z'
| '~'
| 0x80 .. 0x10ffff
| UCHAR)*
'>'
;
LANGTAG = '@' [a-zA-Z]+ ('-' [a-zA-Z0-9]+)* ;
whitespace = [ \t] ;
literal = STRING_LITERAL | STRING_LITERAL_QUOTE % Quote | STRING_LITERAL_QUOTE ('^^' IRIREF | LANGTAG) ;
subject = (literal | BLANK_NODE_LABEL) ;
predicate = literal ;
object = (literal | BLANK_NODE_LABEL) ;
graphLabel = (literal | BLANK_NODE_LABEL) ;
statement := (
whitespace* subject >StartSubject %SetSubject
whitespace+ predicate >StartPredicate %SetPredicate
whitespace+ object >StartObject %SetObject
(whitespace+ graphLabel >StartLabel %SetLabel)?
whitespace* '.' whitespace* ('#' any*)? >Comment
) %Return @!Error ;
}%%

782
quad/cquads/cquads_test.go Normal file

File diff suppressed because it is too large Load diff

6692
quad/cquads/parse.go Normal file

File diff suppressed because it is too large Load diff

58
quad/cquads/parse.rl Normal file
View file

@ -0,0 +1,58 @@
// GO SOURCE FILE MACHINE GENERATED BY RAGEL; DO NOT EDIT
// Copyright 2014 The Cayley Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cquads
import (
"fmt"
"unicode"
"github.com/google/cayley/quad"
)
%%{
machine quads;
include "actions.rl";
include "cquads.rl";
write data;
}%%
func parse(data []rune) (quad.Quad, error) {
var (
cs, p int
pe = len(data)
eof = pe
subject = -1
predicate = -1
object = -1
label = -1
isEscaped bool
isQuoted bool
q quad.Quad
)
%%write init;
%%write exec;
return quad.Quad{}, quad.ErrInvalid
}

BIN
quad/nquad_tests.tar.gz Normal file

Binary file not shown.

87
quad/nquads/actions.rl Normal file
View file

@ -0,0 +1,87 @@
// Copyright 2014 The Cayley Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
%%{
machine quads;
action Escape {
isEscaped = true
}
action StartSubject {
subject = p
}
action StartPredicate {
predicate = p
}
action StartObject {
object = p
}
action StartLabel {
label = p
}
action SetSubject {
if subject < 0 {
panic("unexpected parser state: subject start not set")
}
q.Subject = unEscape(data[subject:p], isEscaped)
isEscaped = false
}
action SetPredicate {
if predicate < 0 {
panic("unexpected parser state: predicate start not set")
}
q.Predicate = unEscape(data[predicate:p], isEscaped)
isEscaped = false
}
action SetObject {
if object < 0 {
panic("unexpected parser state: object start not set")
}
q.Object = unEscape(data[object:p], isEscaped)
isEscaped = false
}
action SetLabel {
if label < 0 {
panic("unexpected parser state: label start not set")
}
q.Label = unEscape(data[label:p], isEscaped)
isEscaped = false
}
action Return {
return q, nil
}
action Comment {
}
action Error {
if p < len(data) {
if r := data[p]; r < unicode.MaxASCII {
return q, fmt.Errorf("%v: unexpected rune %q at %d", quad.ErrInvalid, data[p], p)
} else {
return q, fmt.Errorf("%v: unexpected rune %q (\\u%04x) at %d", quad.ErrInvalid, data[p], data[p], p)
}
}
return q, quad.ErrIncomplete
}
}%%

138
quad/nquads/nquads.go Normal file
View file

@ -0,0 +1,138 @@
// Copyright 2014 The Cayley Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package nquads implements parsing the RDF 1.1 N-Quads line-based syntax
// for RDF datasets.
//
// N-Quad parsing is performed as defined by http://www.w3.org/TR/n-quads/
// with the exception that the nquads package will allow relative IRI values,
// which are prohibited by the N-Quads quad-Quads specifications.
package nquads
import (
"bufio"
"bytes"
"fmt"
"io"
"strconv"
"github.com/google/cayley/quad"
)
// Parse returns a valid quad.Quad or a non-nil error. Parse does
// handle comments except where the comment placement does not prevent
// a complete valid quad.Quad from being defined.
func Parse(str string) (*quad.Quad, error) {
q, err := parse([]rune(str))
return &q, err
}
// Decoder implements N-Quad document parsing according to the RDF
// 1.1 N-Quads specification.
type Decoder struct {
r *bufio.Reader
line []byte
}
// NewDecoder returns an N-Quad decoder that takes its input from the
// provided io.Reader.
func NewDecoder(r io.Reader) *Decoder {
return &Decoder{r: bufio.NewReader(r)}
}
// Unmarshal returns the next valid N-Quad as a quad.Quad, or an error.
func (dec *Decoder) Unmarshal() (*quad.Quad, error) {
dec.line = dec.line[:0]
var line []byte
for {
for {
l, pre, err := dec.r.ReadLine()
if err != nil {
return nil, err
}
dec.line = append(dec.line, l...)
if !pre {
break
}
}
if line = bytes.TrimSpace(dec.line); len(line) != 0 && line[0] != '#' {
break
}
dec.line = dec.line[:0]
}
triple, err := Parse(string(line))
if err != nil {
return nil, fmt.Errorf("failed to parse %q: %v", dec.line, err)
}
if triple == nil {
return dec.Unmarshal()
}
return triple, nil
}
func unEscape(r []rune, isEscaped bool) string {
if !isEscaped {
return string(r)
}
buf := bytes.NewBuffer(make([]byte, 0, len(r)))
for i := 0; i < len(r); {
switch r[i] {
case '\\':
i++
var c byte
switch r[i] {
case 't':
c = '\t'
case 'b':
c = '\b'
case 'n':
c = '\n'
case 'r':
c = '\r'
case 'f':
c = '\f'
case '"':
c = '"'
case '\'':
c = '\''
case '\\':
c = '\\'
case 'u':
rc, err := strconv.ParseInt(string(r[i+1:i+5]), 16, 32)
if err != nil {
panic(fmt.Errorf("internal parser error: %v", err))
}
buf.WriteRune(rune(rc))
i += 5
continue
case 'U':
rc, err := strconv.ParseInt(string(r[i+1:i+9]), 16, 32)
if err != nil {
panic(fmt.Errorf("internal parser error: %v", err))
}
buf.WriteRune(rune(rc))
i += 9
continue
}
buf.WriteByte(c)
default:
buf.WriteRune(r[i])
}
i++
}
return buf.String()
}

97
quad/nquads/nquads.rl Normal file
View file

@ -0,0 +1,97 @@
// Copyright 2014 The Cayley Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Ragel gramar definition derived from http://www.w3.org/TR/n-quads/#sec-grammar.
%%{
machine quads;
alphtype rune;
PN_CHARS_BASE = [A-Za-z]
| 0x00c0 .. 0x00d6
| 0x00d8 .. 0x00f6
| 0x00f8 .. 0x02ff
| 0x0370 .. 0x037d
| 0x037f .. 0x1fff
| 0x200c .. 0x200d
| 0x2070 .. 0x218f
| 0x2c00 .. 0x2fef
| 0x3001 .. 0xd7ff
| 0xf900 .. 0xfdcf
| 0xfdf0 .. 0xfffd
| 0x10000 .. 0xeffff
;
PN_CHARS_U = PN_CHARS_BASE | '_' | ':' ;
PN_CHARS = PN_CHARS_U
| '-'
| [0-9]
| 0xb7
| 0x0300 .. 0x036f
| 0x203f .. 0x2040
;
ECHAR = ('\\' [tbnrf"'\\]) %Escape ;
UCHAR = ('\\u' xdigit {4}
| '\\U' xdigit {8}) %Escape
;
BLANK_NODE_LABEL = '_:' (PN_CHARS_U | [0-9]) ((PN_CHARS | '.')* PN_CHARS)? ;
STRING_LITERAL_QUOTE = '"' (
0x00 .. 0x09
| 0x0b .. 0x0c
| 0x0e .. '!'
| '#' .. '['
| ']' .. 0x10ffff
| ECHAR
| UCHAR)*
'"'
;
IRIREF = '<' (
'!' .. ';'
| '='
| '?' .. '['
| ']'
| '_'
| 'a' .. 'z'
| '~'
| 0x80 .. 0x10ffff
| UCHAR)*
'>'
;
LANGTAG = '@' [a-zA-Z]+ ('-' [a-zA-Z0-9]+)* ;
whitespace = [ \t] ;
literal = STRING_LITERAL_QUOTE ('^^' IRIREF | LANGTAG)? ;
subject = IRIREF | BLANK_NODE_LABEL ;
predicate = IRIREF ;
object = IRIREF | BLANK_NODE_LABEL | literal ;
graphLabel = IRIREF | BLANK_NODE_LABEL ;
statement := (
whitespace* subject >StartSubject %SetSubject
whitespace* predicate >StartPredicate %SetPredicate
whitespace* object >StartObject %SetObject
(whitespace* graphLabel >StartLabel %SetLabel)?
whitespace* '.' whitespace* ('#' any*)? >Comment
) %Return @!Error ;
}%%

589
quad/nquads/nquads_test.go Normal file

File diff suppressed because it is too large Load diff

3652
quad/nquads/parse.go Normal file

File diff suppressed because it is too large Load diff

57
quad/nquads/parse.rl Normal file
View file

@ -0,0 +1,57 @@
// GO SOURCE FILE MACHINE GENERATED BY RAGEL; DO NOT EDIT
// Copyright 2014 The Cayley Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package nquads
import (
"fmt"
"unicode"
"github.com/google/cayley/quad"
)
%%{
machine quads;
include "actions.rl";
include "nquads.rl";
write data;
}%%
func parse(data []rune) (quad.Quad, error) {
var (
cs, p int
pe = len(data)
eof = pe
subject = -1
predicate = -1
object = -1
label = -1
isEscaped bool
q quad.Quad
)
%%write init;
%%write exec;
return quad.Quad{}, quad.ErrInvalid
}

View file

@ -1,29 +1,52 @@
// Copyright 2014 The Cayley Authors. All rights reserved. // +build ignore
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package nquads package main
import ( import (
"bufio" "bufio"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"log"
"os"
"strings" "strings"
"github.com/google/cayley/graph" "github.com/google/cayley/graph"
) )
func main() {
dec := NewDecoder(os.Stdin)
for {
t, err := dec.Unmarshal()
if err != nil {
if err == io.EOF {
return
}
log.Println(err)
}
if t.Subject[0] == ':' && t.Subject[1] == '/' {
t.Subject = "<" + t.Subject[1:] + ">"
} else {
t.Subject = "_" + t.Subject
}
if t.Object[0] == ':' {
if t.Object[1] == '/' {
t.Object = "<" + t.Object[1:] + ">"
} else {
t.Object = "_" + t.Object
}
} else if t.Object[0] == '/' {
t.Object = "<" + t.Object + ">"
} else {
t.Object = fmt.Sprintf(`%q`, t.Object)
}
fmt.Printf("%s <%s> %s .\n", t.Subject, t.Predicate, t.Object)
}
}
// Historical N-Quads parser code.
// -------------------------------
var ( var (
ErrAbsentSubject = errors.New("nqauds: absent subject") ErrAbsentSubject = errors.New("nqauds: absent subject")
ErrAbsentPredicate = errors.New("nqauds: absent predicate") ErrAbsentPredicate = errors.New("nqauds: absent predicate")

BIN
quad/ntriple_tests.tar.gz Normal file

Binary file not shown.

View file

@ -12,7 +12,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package graph // Package quad defines quad and triple handling.
package quad
// Defines the struct which makes the TripleStore possible -- the triple. // Defines the struct which makes the TripleStore possible -- the triple.
// //
@ -25,7 +26,7 @@ package graph
// list of triples. The rest is just indexing for speed. // list of triples. The rest is just indexing for speed.
// //
// Adding fields to the triple is not to be taken lightly. You'll see I mention // Adding fields to the triple is not to be taken lightly. You'll see I mention
// provenance, but don't as yet use it in any backing store. In general, there // label, but don't as yet use it in any backing store. In general, there
// can be features that can be turned on or off for any store, but I haven't // can be features that can be turned on or off for any store, but I haven't
// decided how to allow/disallow them yet. Another such example would be to add // decided how to allow/disallow them yet. Another such example would be to add
// a forward and reverse index field -- forward being "order the list of // a forward and reverse index field -- forward being "order the list of
@ -35,17 +36,22 @@ package graph
// There will never be that much in this file except for the definition, but // There will never be that much in this file except for the definition, but
// the consequences are not to be taken lightly. But do suggest cool features! // the consequences are not to be taken lightly. But do suggest cool features!
import "fmt" import (
"errors"
"fmt"
)
// TODO(kortschak) Consider providing MashalJSON and UnmarshalJSON var (
// instead of using struct tags. ErrInvalid = errors.New("invalid N-Quad")
ErrIncomplete = errors.New("incomplete N-Quad")
)
// Our triple struct, used throughout. // Our triple struct, used throughout.
type Triple struct { type Quad struct {
Subject string `json:"subject"` Subject string `json:"subject"`
Predicate string `json:"predicate"` Predicate string `json:"predicate"`
Object string `json:"object"` Object string `json:"object"`
Provenance string `json:"provenance,omitempty"` Label string `json:"label,omitempty"`
} }
// Direction specifies an edge's type. // Direction specifies an edge's type.
@ -57,7 +63,7 @@ const (
Subject Subject
Predicate Predicate
Object Object
Provenance Label
) )
func (d Direction) Prefix() byte { func (d Direction) Prefix() byte {
@ -68,7 +74,7 @@ func (d Direction) Prefix() byte {
return 's' return 's'
case Predicate: case Predicate:
return 'p' return 'p'
case Provenance: case Label:
return 'c' return 'c'
case Object: case Object:
return 'o' return 'o'
@ -85,8 +91,8 @@ func (d Direction) String() string {
return "subject" return "subject"
case Predicate: case Predicate:
return "predicate" return "predicate"
case Provenance: case Label:
return "provenance" return "label"
case Object: case Object:
return "object" return "object"
default: default:
@ -98,45 +104,49 @@ func (d Direction) String() string {
// instead of the pointer. This needs benchmarking to make the decision. // instead of the pointer. This needs benchmarking to make the decision.
// Per-field accessor for triples // Per-field accessor for triples
func (t *Triple) Get(d Direction) string { func (q *Quad) Get(d Direction) string {
switch d { switch d {
case Subject: case Subject:
return t.Subject return q.Subject
case Predicate: case Predicate:
return t.Predicate return q.Predicate
case Provenance: case Label:
return t.Provenance return q.Label
case Object: case Object:
return t.Object return q.Object
default: default:
panic(d.String()) panic(d.String())
} }
} }
func (t *Triple) Equals(o *Triple) bool { func (q *Quad) Equals(o *Quad) bool {
return *t == *o return *q == *o
} }
// Pretty-prints a triple. // Pretty-prints a triple.
func (t *Triple) String() string { func (q *Quad) String() string {
// TODO(kortschak) String methods should generally not terminate in '\n'. // TODO(kortschak) String methods should generally not terminate in '\n'.
return fmt.Sprintf("%s -- %s -> %s\n", t.Subject, t.Predicate, t.Object) return fmt.Sprintf("%s -- %s -> %s\n", q.Subject, q.Predicate, q.Object)
} }
func (t *Triple) IsValid() bool { func (q *Quad) IsValid() bool {
return t.Subject != "" && t.Predicate != "" && t.Object != "" return q.Subject != "" && q.Predicate != "" && q.Object != ""
} }
// TODO(kortschak) NTriple looks like a good candidate for conversion // TODO(kortschak) NTriple looks like a good candidate for conversion
// to MarshalText() (text []byte, err error) and then move parsing code // to MarshalText() (text []byte, err error) and then move parsing code
// from nquads to here to provide UnmarshalText(text []byte) error. // from nquads to here to provide UnmarshalText(text []byte) error.
// Prints a triple in N-Triple format. // Prints a triple in N-Quad format.
func (t *Triple) NTriple() string { func (q *Quad) NTriple() string {
if t.Provenance == "" { if q.Label == "" {
//TODO(barakmich): Proper escaping. //TODO(barakmich): Proper escaping.
return fmt.Sprintf("%s %s %s .", t.Subject, t.Predicate, t.Object) return fmt.Sprintf("%s %s %s .", q.Subject, q.Predicate, q.Object)
} else { } else {
return fmt.Sprintf("%s %s %s %s .", t.Subject, t.Predicate, t.Object, t.Provenance) return fmt.Sprintf("%s %s %s %s .", q.Subject, q.Predicate, q.Object, q.Label)
} }
} }
type Unmarshaler interface {
Unmarshal() (*Quad, error)
}

View file

@ -22,6 +22,7 @@ import (
"github.com/google/cayley/graph" "github.com/google/cayley/graph"
"github.com/google/cayley/graph/iterator" "github.com/google/cayley/graph/iterator"
"github.com/google/cayley/quad"
) )
func getStrings(obj *otto.Object, field string) []string { func getStrings(obj *otto.Object, field string) []string {
@ -135,17 +136,17 @@ func buildInOutIterator(obj *otto.Object, ts graph.TripleStore, base graph.Itera
tags = makeListOfStringsFromArrayValue(one.Object()) tags = makeListOfStringsFromArrayValue(one.Object())
} }
for _, tag := range tags { for _, tag := range tags {
predicateNodeIterator.AddTag(tag) predicateNodeIterator.Tagger().Add(tag)
} }
} }
in, out := graph.Subject, graph.Object in, out := quad.Subject, quad.Object
if isReverse { if isReverse {
in, out = out, in in, out = out, in
} }
lto := iterator.NewLinksTo(ts, base, in) lto := iterator.NewLinksTo(ts, base, in)
and := iterator.NewAnd() and := iterator.NewAnd()
and.AddSubIterator(iterator.NewLinksTo(ts, predicateNodeIterator, graph.Predicate)) and.AddSubIterator(iterator.NewLinksTo(ts, predicateNodeIterator, quad.Predicate))
and.AddSubIterator(lto) and.AddSubIterator(lto)
return iterator.NewHasA(ts, and, out) return iterator.NewHasA(ts, and, out)
} }
@ -179,7 +180,7 @@ func buildIteratorTreeHelper(obj *otto.Object, ts graph.TripleStore, base graph.
case "tag": case "tag":
it = subIt it = subIt
for _, tag := range stringArgs { for _, tag := range stringArgs {
it.AddTag(tag) it.Tagger().Add(tag)
} }
case "save": case "save":
all := ts.NodesAllIterator() all := ts.NodesAllIterator()
@ -187,16 +188,16 @@ func buildIteratorTreeHelper(obj *otto.Object, ts graph.TripleStore, base graph.
return iterator.NewNull() return iterator.NewNull()
} }
if len(stringArgs) == 2 { if len(stringArgs) == 2 {
all.AddTag(stringArgs[1]) all.Tagger().Add(stringArgs[1])
} else { } else {
all.AddTag(stringArgs[0]) all.Tagger().Add(stringArgs[0])
} }
predFixed := ts.FixedIterator() predFixed := ts.FixedIterator()
predFixed.Add(ts.ValueOf(stringArgs[0])) predFixed.Add(ts.ValueOf(stringArgs[0]))
subAnd := iterator.NewAnd() subAnd := iterator.NewAnd()
subAnd.AddSubIterator(iterator.NewLinksTo(ts, predFixed, graph.Predicate)) subAnd.AddSubIterator(iterator.NewLinksTo(ts, predFixed, quad.Predicate))
subAnd.AddSubIterator(iterator.NewLinksTo(ts, all, graph.Object)) subAnd.AddSubIterator(iterator.NewLinksTo(ts, all, quad.Object))
hasa := iterator.NewHasA(ts, subAnd, graph.Subject) hasa := iterator.NewHasA(ts, subAnd, quad.Subject)
and := iterator.NewAnd() and := iterator.NewAnd()
and.AddSubIterator(hasa) and.AddSubIterator(hasa)
and.AddSubIterator(subIt) and.AddSubIterator(subIt)
@ -207,16 +208,16 @@ func buildIteratorTreeHelper(obj *otto.Object, ts graph.TripleStore, base graph.
return iterator.NewNull() return iterator.NewNull()
} }
if len(stringArgs) == 2 { if len(stringArgs) == 2 {
all.AddTag(stringArgs[1]) all.Tagger().Add(stringArgs[1])
} else { } else {
all.AddTag(stringArgs[0]) all.Tagger().Add(stringArgs[0])
} }
predFixed := ts.FixedIterator() predFixed := ts.FixedIterator()
predFixed.Add(ts.ValueOf(stringArgs[0])) predFixed.Add(ts.ValueOf(stringArgs[0]))
subAnd := iterator.NewAnd() subAnd := iterator.NewAnd()
subAnd.AddSubIterator(iterator.NewLinksTo(ts, predFixed, graph.Predicate)) subAnd.AddSubIterator(iterator.NewLinksTo(ts, predFixed, quad.Predicate))
subAnd.AddSubIterator(iterator.NewLinksTo(ts, all, graph.Subject)) subAnd.AddSubIterator(iterator.NewLinksTo(ts, all, quad.Subject))
hasa := iterator.NewHasA(ts, subAnd, graph.Object) hasa := iterator.NewHasA(ts, subAnd, quad.Object)
and := iterator.NewAnd() and := iterator.NewAnd()
and.AddSubIterator(hasa) and.AddSubIterator(hasa)
and.AddSubIterator(subIt) and.AddSubIterator(subIt)
@ -232,9 +233,9 @@ func buildIteratorTreeHelper(obj *otto.Object, ts graph.TripleStore, base graph.
predFixed := ts.FixedIterator() predFixed := ts.FixedIterator()
predFixed.Add(ts.ValueOf(stringArgs[0])) predFixed.Add(ts.ValueOf(stringArgs[0]))
subAnd := iterator.NewAnd() subAnd := iterator.NewAnd()
subAnd.AddSubIterator(iterator.NewLinksTo(ts, predFixed, graph.Predicate)) subAnd.AddSubIterator(iterator.NewLinksTo(ts, predFixed, quad.Predicate))
subAnd.AddSubIterator(iterator.NewLinksTo(ts, fixed, graph.Object)) subAnd.AddSubIterator(iterator.NewLinksTo(ts, fixed, quad.Object))
hasa := iterator.NewHasA(ts, subAnd, graph.Subject) hasa := iterator.NewHasA(ts, subAnd, quad.Subject)
and := iterator.NewAnd() and := iterator.NewAnd()
and.AddSubIterator(hasa) and.AddSubIterator(hasa)
and.AddSubIterator(subIt) and.AddSubIterator(subIt)

View file

@ -38,7 +38,7 @@ func embedFinals(env *otto.Otto, ses *Session, obj *otto.Object) {
func allFunc(env *otto.Otto, ses *Session, obj *otto.Object) func(otto.FunctionCall) otto.Value { func allFunc(env *otto.Otto, ses *Session, obj *otto.Object) func(otto.FunctionCall) otto.Value {
return func(call otto.FunctionCall) otto.Value { return func(call otto.FunctionCall) otto.Value {
it := buildIteratorTree(obj, ses.ts) it := buildIteratorTree(obj, ses.ts)
it.AddTag(TopResultTag) it.Tagger().Add(TopResultTag)
ses.limit = -1 ses.limit = -1
ses.count = 0 ses.count = 0
runIteratorOnSession(it, ses) runIteratorOnSession(it, ses)
@ -51,7 +51,7 @@ func limitFunc(env *otto.Otto, ses *Session, obj *otto.Object) func(otto.Functio
if len(call.ArgumentList) > 0 { if len(call.ArgumentList) > 0 {
limitVal, _ := call.Argument(0).ToInteger() limitVal, _ := call.Argument(0).ToInteger()
it := buildIteratorTree(obj, ses.ts) it := buildIteratorTree(obj, ses.ts)
it.AddTag(TopResultTag) it.Tagger().Add(TopResultTag)
ses.limit = int(limitVal) ses.limit = int(limitVal)
ses.count = 0 ses.count = 0
runIteratorOnSession(it, ses) runIteratorOnSession(it, ses)
@ -63,7 +63,7 @@ func limitFunc(env *otto.Otto, ses *Session, obj *otto.Object) func(otto.Functio
func toArrayFunc(env *otto.Otto, ses *Session, obj *otto.Object, withTags bool) func(otto.FunctionCall) otto.Value { func toArrayFunc(env *otto.Otto, ses *Session, obj *otto.Object, withTags bool) func(otto.FunctionCall) otto.Value {
return func(call otto.FunctionCall) otto.Value { return func(call otto.FunctionCall) otto.Value {
it := buildIteratorTree(obj, ses.ts) it := buildIteratorTree(obj, ses.ts)
it.AddTag(TopResultTag) it.Tagger().Add(TopResultTag)
limit := -1 limit := -1
if len(call.ArgumentList) > 0 { if len(call.ArgumentList) > 0 {
limitParsed, _ := call.Argument(0).ToInteger() limitParsed, _ := call.Argument(0).ToInteger()
@ -90,7 +90,7 @@ func toArrayFunc(env *otto.Otto, ses *Session, obj *otto.Object, withTags bool)
func toValueFunc(env *otto.Otto, ses *Session, obj *otto.Object, withTags bool) func(otto.FunctionCall) otto.Value { func toValueFunc(env *otto.Otto, ses *Session, obj *otto.Object, withTags bool) func(otto.FunctionCall) otto.Value {
return func(call otto.FunctionCall) otto.Value { return func(call otto.FunctionCall) otto.Value {
it := buildIteratorTree(obj, ses.ts) it := buildIteratorTree(obj, ses.ts)
it.AddTag(TopResultTag) it.Tagger().Add(TopResultTag)
limit := 1 limit := 1
var val otto.Value var val otto.Value
var err error var err error
@ -120,7 +120,7 @@ func toValueFunc(env *otto.Otto, ses *Session, obj *otto.Object, withTags bool)
func mapFunc(env *otto.Otto, ses *Session, obj *otto.Object) func(otto.FunctionCall) otto.Value { func mapFunc(env *otto.Otto, ses *Session, obj *otto.Object) func(otto.FunctionCall) otto.Value {
return func(call otto.FunctionCall) otto.Value { return func(call otto.FunctionCall) otto.Value {
it := buildIteratorTree(obj, ses.ts) it := buildIteratorTree(obj, ses.ts)
it.AddTag(TopResultTag) it.Tagger().Add(TopResultTag)
limit := -1 limit := -1
if len(call.ArgumentList) == 0 { if len(call.ArgumentList) == 0 {
return otto.NullValue() return otto.NullValue()
@ -151,7 +151,7 @@ func runIteratorToArray(it graph.Iterator, ses *Session, limit int) []map[string
if ses.doHalt { if ses.doHalt {
return nil return nil
} }
_, ok := it.Next() _, ok := graph.Next(it)
if !ok { if !ok {
break break
} }
@ -187,7 +187,7 @@ func runIteratorToArrayNoTags(it graph.Iterator, ses *Session, limit int) []stri
if ses.doHalt { if ses.doHalt {
return nil return nil
} }
val, ok := it.Next() val, ok := graph.Next(it)
if !ok { if !ok {
break break
} }
@ -208,7 +208,7 @@ func runIteratorWithCallback(it graph.Iterator, ses *Session, callback otto.Valu
if ses.doHalt { if ses.doHalt {
return return
} }
_, ok := it.Next() _, ok := graph.Next(it)
if !ok { if !ok {
break break
} }
@ -249,7 +249,7 @@ func runIteratorOnSession(it graph.Iterator, ses *Session) {
if ses.doHalt { if ses.doHalt {
return return
} }
_, ok := it.Next() _, ok := graph.Next(it)
if !ok { if !ok {
break break
} }

View file

@ -21,6 +21,7 @@ import (
"github.com/google/cayley/graph" "github.com/google/cayley/graph"
_ "github.com/google/cayley/graph/memstore" _ "github.com/google/cayley/graph/memstore"
"github.com/google/cayley/quad"
) )
// This is a simple test graph. // This is a simple test graph.
@ -36,7 +37,7 @@ import (
// \-->|#D#|------------->+---+ // \-->|#D#|------------->+---+
// +---+ // +---+
// //
var simpleGraph = []*graph.Triple{ var simpleGraph = []*quad.Quad{
{"A", "follows", "B", ""}, {"A", "follows", "B", ""},
{"C", "follows", "B", ""}, {"C", "follows", "B", ""},
{"C", "follows", "D", ""}, {"C", "follows", "D", ""},
@ -50,7 +51,7 @@ var simpleGraph = []*graph.Triple{
{"G", "status", "cool", "status_graph"}, {"G", "status", "cool", "status_graph"},
} }
func makeTestSession(data []*graph.Triple) *Session { func makeTestSession(data []*quad.Quad) *Session {
ts, _ := graph.NewTripleStore("memstore", "", nil) ts, _ := graph.NewTripleStore("memstore", "", nil)
for _, t := range data { for _, t := range data {
ts.AddTriple(t) ts.AddTriple(t)
@ -244,7 +245,7 @@ var testQueries = []struct {
}, },
} }
func runQueryGetTag(g []*graph.Triple, query string, tag string) []string { func runQueryGetTag(g []*quad.Quad, query string, tag string) []string {
js := makeTestSession(g) js := makeTestSession(g)
c := make(chan interface{}, 5) c := make(chan interface{}, 5)
js.ExecInput(query, c, -1) js.ExecInput(query, c, -1)

View file

@ -23,6 +23,7 @@ import (
"github.com/google/cayley/graph" "github.com/google/cayley/graph"
"github.com/google/cayley/graph/iterator" "github.com/google/cayley/graph/iterator"
"github.com/google/cayley/quad"
) )
func (q *Query) buildFixed(s string) graph.Iterator { func (q *Query) buildFixed(s string) graph.Iterator {
@ -33,7 +34,7 @@ func (q *Query) buildFixed(s string) graph.Iterator {
func (q *Query) buildResultIterator(path Path) graph.Iterator { func (q *Query) buildResultIterator(path Path) graph.Iterator {
all := q.ses.ts.NodesAllIterator() all := q.ses.ts.NodesAllIterator()
all.AddTag(string(path)) all.Tagger().Add(string(path))
return all return all
} }
@ -97,7 +98,7 @@ func (q *Query) buildIteratorTreeInternal(query interface{}, path Path) (it grap
if err != nil { if err != nil {
return nil, false, err return nil, false, err
} }
it.AddTag(string(path)) it.Tagger().Add(string(path))
return it, optional, nil return it, optional, nil
} }
@ -139,16 +140,16 @@ func (q *Query) buildIteratorTreeMapInternal(query map[string]interface{}, path
subAnd := iterator.NewAnd() subAnd := iterator.NewAnd()
predFixed := q.ses.ts.FixedIterator() predFixed := q.ses.ts.FixedIterator()
predFixed.Add(q.ses.ts.ValueOf(pred)) predFixed.Add(q.ses.ts.ValueOf(pred))
subAnd.AddSubIterator(iterator.NewLinksTo(q.ses.ts, predFixed, graph.Predicate)) subAnd.AddSubIterator(iterator.NewLinksTo(q.ses.ts, predFixed, quad.Predicate))
if reverse { if reverse {
lto := iterator.NewLinksTo(q.ses.ts, builtIt, graph.Subject) lto := iterator.NewLinksTo(q.ses.ts, builtIt, quad.Subject)
subAnd.AddSubIterator(lto) subAnd.AddSubIterator(lto)
hasa := iterator.NewHasA(q.ses.ts, subAnd, graph.Object) hasa := iterator.NewHasA(q.ses.ts, subAnd, quad.Object)
subit = hasa subit = hasa
} else { } else {
lto := iterator.NewLinksTo(q.ses.ts, builtIt, graph.Object) lto := iterator.NewLinksTo(q.ses.ts, builtIt, quad.Object)
subAnd.AddSubIterator(lto) subAnd.AddSubIterator(lto)
hasa := iterator.NewHasA(q.ses.ts, subAnd, graph.Subject) hasa := iterator.NewHasA(q.ses.ts, subAnd, quad.Subject)
subit = hasa subit = hasa
} }
} }

View file

@ -21,6 +21,7 @@ import (
"github.com/google/cayley/graph" "github.com/google/cayley/graph"
_ "github.com/google/cayley/graph/memstore" _ "github.com/google/cayley/graph/memstore"
"github.com/google/cayley/quad"
) )
// This is a simple test graph. // This is a simple test graph.
@ -36,7 +37,7 @@ import (
// \-->|#D#|------------->+---+ // \-->|#D#|------------->+---+
// +---+ // +---+
// //
var simpleGraph = []*graph.Triple{ var simpleGraph = []*quad.Quad{
{"A", "follows", "B", ""}, {"A", "follows", "B", ""},
{"C", "follows", "B", ""}, {"C", "follows", "B", ""},
{"C", "follows", "D", ""}, {"C", "follows", "D", ""},
@ -50,7 +51,7 @@ var simpleGraph = []*graph.Triple{
{"G", "status", "cool", "status_graph"}, {"G", "status", "cool", "status_graph"},
} }
func makeTestSession(data []*graph.Triple) *Session { func makeTestSession(data []*quad.Quad) *Session {
ts, _ := graph.NewTripleStore("memstore", "", nil) ts, _ := graph.NewTripleStore("memstore", "", nil)
for _, t := range data { for _, t := range data {
ts.AddTriple(t) ts.AddTriple(t)
@ -164,7 +165,7 @@ var testQueries = []struct {
}, },
} }
func runQuery(g []*graph.Triple, query string) interface{} { func runQuery(g []*quad.Quad, query string) interface{} {
s := makeTestSession(g) s := makeTestSession(g)
c := make(chan interface{}, 5) c := make(chan interface{}, 5)
go s.ExecInput(query, c, -1) go s.ExecInput(query, c, -1)

View file

@ -88,7 +88,7 @@ func (s *Session) ExecInput(input string, c chan interface{}, limit int) {
glog.V(2).Infoln(it.DebugString(0)) glog.V(2).Infoln(it.DebugString(0))
} }
for { for {
_, ok := it.Next() _, ok := graph.Next(it)
if !ok { if !ok {
break break
} }

View file

@ -48,7 +48,7 @@ $(function() {
subject: $("#subject").val(), subject: $("#subject").val(),
predicate: $("#predicate").val(), predicate: $("#predicate").val(),
object: $("#object").val(), object: $("#object").val(),
provenance: $("#provenance").val() label: $("#label").val()
} }
if (!checkTriple(triple)) { if (!checkTriple(triple)) {
return return
@ -68,7 +68,7 @@ $(function() {
subject: $("#rsubject").val(), subject: $("#rsubject").val(),
predicate: $("#rpredicate").val(), predicate: $("#rpredicate").val(),
object: $("#robject").val(), object: $("#robject").val(),
provenance: $("#rprovenance").val() label: $("#rlabel").val()
} }
if (!checkTriple(triple)) { if (!checkTriple(triple)) {
return return

View file

@ -45,7 +45,7 @@
<input id="subject" type="text" placeholder="Subject"></input> <input id="subject" type="text" placeholder="Subject"></input>
<input id="predicate" type="text" placeholder="Predicate"></input> <input id="predicate" type="text" placeholder="Predicate"></input>
<input id="object" type="text" placeholder="Object"></input> <input id="object" type="text" placeholder="Object"></input>
<input id="provenance" type="text" placeholder="Provenance"></input> <input id="label" type="text" placeholder="Label"></input>
</div> </div>
</div> </div>
<div class="row button-row"> <div class="row button-row">
@ -59,7 +59,7 @@
<input id="rsubject" type="text" placeholder="Subject"></input> <input id="rsubject" type="text" placeholder="Subject"></input>
<input id="rpredicate" type="text" placeholder="Predicate"></input> <input id="rpredicate" type="text" placeholder="Predicate"></input>
<input id="robject" type="text" placeholder="Object"></input> <input id="robject" type="text" placeholder="Object"></input>
<input id="rprovenance" type="text" placeholder="Provenance"></input> <input id="rlabel" type="text" placeholder="Label"></input>
</div> </div>
</div><!-- /.col-xs-12 main --> </div><!-- /.col-xs-12 main -->
<div class="row button-row"> <div class="row button-row">

9
testdata.nq Normal file
View file

@ -0,0 +1,9 @@
<alice> <follows> <bob> .
<bob> <follows> <alice> .
<charlie> <follows> <bob> .
<dani> <follows> <charlie> .
<dani> <follows> <alice> .
<alice> <is> "cool" .
<bob> <is> "not cool" .
<charlie> <is> "cool" .
<dani> <is> "not cool" .

View file

@ -1,9 +0,0 @@
alice follows bob .
bob follows alice .
charlie follows bob .
dani follows charlie .
dani follows alice .
alice is cool .
bob is "not cool" .
charlie is cool .
dani is "not cool" .