github.com/cayleygraph/cayley@v0.7.7/graph/nosql/quadstore.go (about)

     1  // Copyright 2014 The Cayley Authors. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package nosql
    16  
    17  import (
    18  	"context"
    19  	"encoding/base64"
    20  	"fmt"
    21  	"time"
    22  
    23  	"github.com/cayleygraph/cayley/clog"
    24  	"github.com/cayleygraph/cayley/graph"
    25  	"github.com/cayleygraph/cayley/graph/iterator"
    26  	"github.com/cayleygraph/cayley/internal/lru"
    27  	"github.com/cayleygraph/quad"
    28  	"github.com/cayleygraph/quad/pquads"
    29  	"github.com/hidal-go/hidalgo/legacy/nosql"
    30  )
    31  
    32  const DefaultDBName = "cayley"
    33  
    34  type Registration struct {
    35  	NewFunc      NewFunc
    36  	InitFunc     InitFunc
    37  	IsPersistent bool
    38  	Traits
    39  }
    40  
    41  type Traits = nosql.Traits
    42  
    43  func init() {
    44  	for _, reg := range nosql.List() {
    45  		Register(reg.Name, Registration{
    46  			NewFunc: func(addr string, options graph.Options) (nosql.Database, error) {
    47  				return reg.Open(addr, DefaultDBName, nosql.Options(options))
    48  			},
    49  			InitFunc: func(addr string, options graph.Options) (nosql.Database, error) {
    50  				return reg.New(addr, DefaultDBName, nosql.Options(options))
    51  			},
    52  			IsPersistent: !reg.Volatile, Traits: reg.Traits,
    53  		})
    54  	}
    55  }
    56  
    57  type InitFunc func(string, graph.Options) (nosql.Database, error)
    58  type NewFunc func(string, graph.Options) (nosql.Database, error)
    59  
    60  func Register(name string, r Registration) {
    61  	graph.RegisterQuadStore(name, graph.QuadStoreRegistration{
    62  		InitFunc: func(addr string, opt graph.Options) error {
    63  			if !r.IsPersistent {
    64  				return nil
    65  			}
    66  			db, err := r.InitFunc(addr, opt)
    67  			if err != nil {
    68  				return err
    69  			}
    70  			defer db.Close()
    71  			if err = Init(db, opt); err != nil {
    72  				return err
    73  			}
    74  			return db.Close()
    75  		},
    76  		NewFunc: func(addr string, opt graph.Options) (graph.QuadStore, error) {
    77  			db, err := r.NewFunc(addr, opt)
    78  			if err != nil {
    79  				return nil, err
    80  			}
    81  			if !r.IsPersistent {
    82  				if err = Init(db, opt); err != nil {
    83  					db.Close()
    84  					return nil, err
    85  				}
    86  			}
    87  			nopt := r.Traits
    88  			qs, err := NewQuadStore(db, &nopt, opt)
    89  			if err != nil {
    90  				return nil, err
    91  			}
    92  			return qs, nil
    93  		},
    94  		IsPersistent: r.IsPersistent,
    95  	})
    96  }
    97  
    98  func Init(db nosql.Database, opt graph.Options) error {
    99  	return ensureIndexes(context.TODO(), db)
   100  }
   101  
   102  func NewQuadStore(db nosql.Database, nopt *Traits, opt graph.Options) (*QuadStore, error) {
   103  	if err := ensureIndexes(context.TODO(), db); err != nil {
   104  		return nil, err
   105  	}
   106  	qs := &QuadStore{
   107  		db:    db,
   108  		ids:   lru.New(1 << 16),
   109  		sizes: lru.New(1 << 16),
   110  	}
   111  	if nopt != nil {
   112  		qs.opt = *nopt
   113  	}
   114  	return qs, nil
   115  }
   116  
   117  type NodeHash string
   118  
   119  func (NodeHash) IsNode() bool       { return false }
   120  func (v NodeHash) Key() interface{} { return v }
   121  func (v NodeHash) key() nosql.Key   { return nosql.Key{string(v)} }
   122  
   123  type QuadHash [4]string
   124  
   125  func (QuadHash) IsNode() bool       { return false }
   126  func (v QuadHash) Key() interface{} { return v }
   127  
   128  func (h QuadHash) Get(d quad.Direction) string {
   129  	var ind int
   130  	switch d {
   131  	case quad.Subject:
   132  		ind = 0
   133  	case quad.Predicate:
   134  		ind = 1
   135  	case quad.Object:
   136  		ind = 2
   137  	case quad.Label:
   138  		ind = 3
   139  	}
   140  	return h[ind]
   141  }
   142  
   143  const (
   144  	colLog   = "log"
   145  	colNodes = "nodes"
   146  	colQuads = "quads"
   147  
   148  	fldLogID = "id"
   149  
   150  	fldSubject     = "subject"
   151  	fldPredicate   = "predicate"
   152  	fldObject      = "object"
   153  	fldLabel       = "label"
   154  	fldQuadAdded   = "added"
   155  	fldQuadDeleted = "deleted"
   156  
   157  	fldHash  = "hash"
   158  	fldValue = "value"
   159  	fldSize  = "refs"
   160  
   161  	fldValData   = "str"
   162  	fldIRI       = "iri"
   163  	fldBNode     = "bnode"
   164  	fldType      = "type"
   165  	fldLang      = "lang"
   166  	fldValInt    = "int"
   167  	fldValStrInt = "int_str"
   168  	fldValFloat  = "float"
   169  	fldValBool   = "bool"
   170  	fldValTime   = "ts"
   171  	fldValPb     = "pb"
   172  )
   173  
   174  type QuadStore struct {
   175  	db    nosql.Database
   176  	ids   *lru.Cache
   177  	sizes *lru.Cache
   178  	opt   Traits
   179  }
   180  
   181  func ensureIndexes(ctx context.Context, db nosql.Database) error {
   182  	err := db.EnsureIndex(ctx, colLog, nosql.Index{
   183  		Fields: []string{fldLogID},
   184  		Type:   nosql.StringExact,
   185  	}, nil)
   186  	if err != nil {
   187  		return err
   188  	}
   189  	err = db.EnsureIndex(ctx, colNodes, nosql.Index{
   190  		Fields: []string{fldHash},
   191  		Type:   nosql.StringExact,
   192  	}, nil)
   193  	if err != nil {
   194  		return err
   195  	}
   196  	err = db.EnsureIndex(ctx, colQuads, nosql.Index{
   197  		Fields: []string{
   198  			fldSubject,
   199  			fldPredicate,
   200  			fldObject,
   201  			fldLabel,
   202  		},
   203  		Type: nosql.StringExact,
   204  	}, []nosql.Index{
   205  		{Fields: []string{fldSubject}, Type: nosql.StringExact},
   206  		{Fields: []string{fldPredicate}, Type: nosql.StringExact},
   207  		{Fields: []string{fldObject}, Type: nosql.StringExact},
   208  		{Fields: []string{fldLabel}, Type: nosql.StringExact},
   209  	})
   210  	if err != nil {
   211  		return err
   212  	}
   213  	return nil
   214  }
   215  
   216  func getKeyForQuad(t quad.Quad) nosql.Key {
   217  	return nosql.Key{
   218  		hashOf(t.Subject),
   219  		hashOf(t.Predicate),
   220  		hashOf(t.Object),
   221  		hashOf(t.Label),
   222  	}
   223  }
   224  
   225  func hashOf(s quad.Value) string {
   226  	if s == nil {
   227  		return ""
   228  	}
   229  	h := quad.HashOf(s)
   230  	return base64.StdEncoding.EncodeToString(h)
   231  }
   232  
   233  func (qs *QuadStore) nameToKey(name quad.Value) nosql.Key {
   234  	node := qs.hashOf(name)
   235  	return node.key()
   236  }
   237  
   238  func (qs *QuadStore) updateNodeBy(ctx context.Context, key nosql.Key, name quad.Value, inc int) error {
   239  	if inc == 0 {
   240  		return nil
   241  	}
   242  	d := toDocumentValue(&qs.opt, name)
   243  	err := qs.db.Update(colNodes, key).Upsert(d).Inc(fldSize, inc).Do(ctx)
   244  	if err != nil {
   245  		return fmt.Errorf("error updating node: %v", err)
   246  	}
   247  	return nil
   248  }
   249  
   250  func (qs *QuadStore) cleanupNodes(ctx context.Context, keys []nosql.Key) error {
   251  	err := qs.db.Delete(colNodes).Keys(keys...).WithFields(nosql.FieldFilter{
   252  		Path:   []string{fldSize},
   253  		Filter: nosql.Equal,
   254  		Value:  nosql.Int(0),
   255  	}).Do(ctx)
   256  	if err != nil {
   257  		err = fmt.Errorf("error cleaning up nodes: %v", err)
   258  	}
   259  	return err
   260  }
   261  
   262  func (qs *QuadStore) updateQuad(ctx context.Context, q quad.Quad, proc graph.Procedure) error {
   263  	var setname string
   264  	if proc == graph.Add {
   265  		setname = fldQuadAdded
   266  	} else if proc == graph.Delete {
   267  		setname = fldQuadDeleted
   268  	}
   269  	doc := nosql.Document{
   270  		fldSubject:   nosql.String(hashOf(q.Subject)),
   271  		fldPredicate: nosql.String(hashOf(q.Predicate)),
   272  		fldObject:    nosql.String(hashOf(q.Object)),
   273  	}
   274  	if l := hashOf(q.Label); l != "" {
   275  		doc[fldLabel] = nosql.String(l)
   276  	}
   277  	err := qs.db.Update(colQuads, getKeyForQuad(q)).Upsert(doc).
   278  		Inc(setname, 1).Do(ctx)
   279  	if err != nil {
   280  		err = fmt.Errorf("quad update failed: %v", err)
   281  	}
   282  	return err
   283  }
   284  
   285  func checkQuadValid(q nosql.Document) bool {
   286  	added, _ := asInt(q[fldQuadAdded])
   287  	deleted, _ := asInt(q[fldQuadDeleted])
   288  	return added > deleted
   289  }
   290  
   291  func (qs *QuadStore) checkValidQuad(ctx context.Context, key nosql.Key) (bool, error) {
   292  	q, err := qs.db.FindByKey(ctx, colQuads, key)
   293  	if err == nosql.ErrNotFound {
   294  		return false, nil
   295  	}
   296  	if err != nil {
   297  		err = fmt.Errorf("error checking quad validity: %v", err)
   298  		return false, err
   299  	}
   300  	return checkQuadValid(q), nil
   301  }
   302  
   303  func (qs *QuadStore) batchInsert(col string) nosql.DocWriter {
   304  	return nosql.BatchInsert(qs.db, col)
   305  }
   306  
   307  func (qs *QuadStore) appendLog(ctx context.Context, deltas []graph.Delta) ([]nosql.Key, error) {
   308  	w := qs.batchInsert(colLog)
   309  	defer w.Close()
   310  	for _, d := range deltas {
   311  		data, err := pquads.MakeQuad(d.Quad).Marshal()
   312  		if err != nil {
   313  			return w.Keys(), err
   314  		}
   315  		var action string
   316  		if d.Action == graph.Add {
   317  			action = "AddQuadPQ"
   318  		} else {
   319  			action = "DeleteQuadPQ"
   320  		}
   321  		err = w.WriteDoc(ctx, nil, nosql.Document{
   322  			"op":   nosql.String(action),
   323  			"data": nosql.Bytes(data),
   324  			"ts":   nosql.Time(time.Now().UTC()),
   325  		})
   326  		if err != nil {
   327  			return w.Keys(), err
   328  		}
   329  	}
   330  	err := w.Flush(ctx)
   331  	return w.Keys(), err
   332  }
   333  
   334  func (qs *QuadStore) NewQuadWriter() (quad.WriteCloser, error) {
   335  	return &quadWriter{qs: qs}, nil
   336  }
   337  
   338  type quadWriter struct {
   339  	qs     *QuadStore
   340  	deltas []graph.Delta
   341  }
   342  
   343  func (w *quadWriter) WriteQuad(q quad.Quad) error {
   344  	_, err := w.WriteQuads([]quad.Quad{q})
   345  	return err
   346  }
   347  
   348  func (w *quadWriter) WriteQuads(buf []quad.Quad) (int, error) {
   349  	// TODO(dennwc): write an optimized implementation
   350  	w.deltas = w.deltas[:0]
   351  	if cap(w.deltas) < len(buf) {
   352  		w.deltas = make([]graph.Delta, 0, len(buf))
   353  	}
   354  	for _, q := range buf {
   355  		w.deltas = append(w.deltas, graph.Delta{
   356  			Quad: q, Action: graph.Add,
   357  		})
   358  	}
   359  	err := w.qs.ApplyDeltas(w.deltas, graph.IgnoreOpts{
   360  		IgnoreDup: true,
   361  	})
   362  	w.deltas = w.deltas[:0]
   363  	if err != nil {
   364  		return 0, err
   365  	}
   366  	return len(buf), nil
   367  }
   368  
   369  func (w *quadWriter) Close() error {
   370  	w.deltas = nil
   371  	return nil
   372  }
   373  
   374  func (qs *QuadStore) ApplyDeltas(deltas []graph.Delta, ignoreOpts graph.IgnoreOpts) error {
   375  	ctx := context.TODO()
   376  	ids := make(map[quad.Value]int)
   377  
   378  	var validDeltas []graph.Delta
   379  	if ignoreOpts.IgnoreDup || ignoreOpts.IgnoreMissing {
   380  		validDeltas = make([]graph.Delta, 0, len(deltas))
   381  	}
   382  	// Pre-check the existence condition.
   383  	for _, d := range deltas {
   384  		if d.Action != graph.Add && d.Action != graph.Delete {
   385  			return &graph.DeltaError{Delta: d, Err: graph.ErrInvalidAction}
   386  		}
   387  		valid, err := qs.checkValidQuad(ctx, getKeyForQuad(d.Quad))
   388  		if err != nil {
   389  			return &graph.DeltaError{Delta: d, Err: err}
   390  		}
   391  		switch d.Action {
   392  		case graph.Add:
   393  			if valid {
   394  				if ignoreOpts.IgnoreDup {
   395  					continue
   396  				} else {
   397  					return &graph.DeltaError{Delta: d, Err: graph.ErrQuadExists}
   398  				}
   399  			}
   400  		case graph.Delete:
   401  			if !valid {
   402  				if ignoreOpts.IgnoreMissing {
   403  					continue
   404  				} else {
   405  					return &graph.DeltaError{Delta: d, Err: graph.ErrQuadNotExist}
   406  				}
   407  			}
   408  		}
   409  		if validDeltas != nil {
   410  			validDeltas = append(validDeltas, d)
   411  		}
   412  		var dn int
   413  		if d.Action == graph.Add {
   414  			dn = 1
   415  		} else {
   416  			dn = -1
   417  		}
   418  		ids[d.Quad.Subject] += dn
   419  		ids[d.Quad.Object] += dn
   420  		ids[d.Quad.Predicate] += dn
   421  		if d.Quad.Label != nil {
   422  			ids[d.Quad.Label] += dn
   423  		}
   424  	}
   425  	if validDeltas != nil {
   426  		deltas = validDeltas
   427  	}
   428  	if oids, err := qs.appendLog(ctx, deltas); err != nil {
   429  		if i := len(oids); i < len(deltas) {
   430  			return &graph.DeltaError{Delta: deltas[i], Err: err}
   431  		}
   432  		return &graph.DeltaError{Err: err}
   433  	}
   434  	// make sure to create all nodes before writing any quads
   435  	// concurrent reads may observe broken quads in other case
   436  	var gc []nosql.Key
   437  	for name, dn := range ids {
   438  		key := qs.nameToKey(name)
   439  		err := qs.updateNodeBy(ctx, key, name, dn)
   440  		if err != nil {
   441  			return err
   442  		}
   443  		if dn < 0 {
   444  			gc = append(gc, key)
   445  		}
   446  	}
   447  	// gc nodes that has negative ref counter
   448  	if err := qs.cleanupNodes(ctx, gc); err != nil {
   449  		return err
   450  	}
   451  	for _, d := range deltas {
   452  		err := qs.updateQuad(ctx, d.Quad, d.Action)
   453  		if err != nil {
   454  			return &graph.DeltaError{Delta: d, Err: err}
   455  		}
   456  	}
   457  	return nil
   458  }
   459  
   460  func toDocumentValue(opt *Traits, v quad.Value) nosql.Document {
   461  	if v == nil {
   462  		return nil
   463  	}
   464  	var doc nosql.Document
   465  	encPb := func() {
   466  		qv := pquads.MakeValue(v)
   467  		data, err := qv.Marshal()
   468  		if err != nil {
   469  			panic(err)
   470  		}
   471  		doc[fldValPb] = nosql.Bytes(data)
   472  	}
   473  	switch d := v.(type) {
   474  	case quad.String:
   475  		doc = nosql.Document{fldValData: nosql.String(d)}
   476  	case quad.IRI:
   477  		doc = nosql.Document{fldValData: nosql.String(d), fldIRI: nosql.Bool(true)}
   478  	case quad.BNode:
   479  		doc = nosql.Document{fldValData: nosql.String(d), fldBNode: nosql.Bool(true)}
   480  	case quad.TypedString:
   481  		doc = nosql.Document{fldValData: nosql.String(d.Value), fldType: nosql.String(d.Type)}
   482  	case quad.LangString:
   483  		doc = nosql.Document{fldValData: nosql.String(d.Value), fldLang: nosql.String(d.Lang)}
   484  	case quad.Int:
   485  		doc = nosql.Document{fldValInt: nosql.Int(d)}
   486  		if opt.Number32 {
   487  			// store sortable string representation for range queries
   488  			doc[fldValStrInt] = nosql.String(itos(int64(d)))
   489  			encPb()
   490  		}
   491  	case quad.Float:
   492  		doc = nosql.Document{fldValFloat: nosql.Float(d)}
   493  		if opt.Number32 {
   494  			encPb()
   495  		}
   496  	case quad.Bool:
   497  		doc = nosql.Document{fldValBool: nosql.Bool(d)}
   498  	case quad.Time:
   499  		doc = nosql.Document{fldValTime: nosql.Time(time.Time(d).UTC())}
   500  	default:
   501  		encPb()
   502  	}
   503  	return nosql.Document{fldValue: doc}
   504  }
   505  
   506  func asInt(v nosql.Value) (nosql.Int, error) {
   507  	var vi nosql.Int
   508  	switch v := v.(type) {
   509  	case nosql.Int:
   510  		vi = v
   511  	case nosql.Float:
   512  		vi = nosql.Int(v)
   513  	default:
   514  		return 0, fmt.Errorf("unexpected type for int field: %T", v)
   515  	}
   516  	return vi, nil
   517  }
   518  
   519  func toQuadValue(opt *Traits, d nosql.Document) (quad.Value, error) {
   520  	if len(d) == 0 {
   521  		return nil, nil
   522  	}
   523  	var err error
   524  	// prefer protobuf representation
   525  	if v, ok := d[fldValPb]; ok {
   526  		var b []byte
   527  		switch v := v.(type) {
   528  		case nosql.String:
   529  			b, err = base64.StdEncoding.DecodeString(string(v))
   530  		case nosql.Bytes:
   531  			b = []byte(v)
   532  		default:
   533  			err = fmt.Errorf("unexpected type for pb field: %T", v)
   534  		}
   535  		if err != nil {
   536  			return nil, err
   537  		}
   538  		var p pquads.Value
   539  		if err := p.Unmarshal(b); err != nil {
   540  			return nil, fmt.Errorf("couldn't decode value: %v", err)
   541  		}
   542  		return p.ToNative(), nil
   543  	} else if v, ok := d[fldValInt]; ok {
   544  		if opt.Number32 {
   545  			// parse from string, so we are confident that we will get exactly the same value
   546  			if vs, ok := d[fldValStrInt].(nosql.String); ok {
   547  				iv := quad.Int(stoi(string(vs)))
   548  				return iv, nil
   549  			}
   550  		}
   551  		vi, err := asInt(v)
   552  		if err != nil {
   553  			return nil, err
   554  		}
   555  		return quad.Int(vi), nil
   556  	} else if v, ok := d[fldValFloat]; ok {
   557  		var vf quad.Float
   558  		switch v := v.(type) {
   559  		case nosql.Int:
   560  			vf = quad.Float(v)
   561  		case nosql.Float:
   562  			vf = quad.Float(v)
   563  		default:
   564  			return nil, fmt.Errorf("unexpected type for float field: %T", v)
   565  		}
   566  		return vf, nil
   567  	} else if v, ok := d[fldValBool]; ok {
   568  		var vb quad.Bool
   569  		switch v := v.(type) {
   570  		case nosql.Bool:
   571  			vb = quad.Bool(v)
   572  		default:
   573  			return nil, fmt.Errorf("unexpected type for bool field: %T", v)
   574  		}
   575  		return vb, nil
   576  	} else if v, ok := d[fldValTime]; ok {
   577  		var vt quad.Time
   578  		switch v := v.(type) {
   579  		case nosql.Time:
   580  			vt = quad.Time(v)
   581  		case nosql.String:
   582  			var t time.Time
   583  			if err := t.UnmarshalJSON([]byte(`"` + string(v) + `"`)); err != nil {
   584  				return nil, err
   585  			}
   586  			vt = quad.Time(t)
   587  		default:
   588  			return nil, fmt.Errorf("unexpected type for bool field: %T", v)
   589  		}
   590  		return vt, nil
   591  	}
   592  	vs, ok := d[fldValData].(nosql.String)
   593  	if !ok {
   594  		return nil, fmt.Errorf("unknown value format: %T", d[fldValData])
   595  	}
   596  	if len(d) == 1 {
   597  		return quad.String(vs), nil
   598  	}
   599  	if ok, _ := d[fldIRI].(nosql.Bool); ok {
   600  		return quad.IRI(vs), nil
   601  	} else if ok, _ := d[fldBNode].(nosql.Bool); ok {
   602  		return quad.BNode(vs), nil
   603  	} else if typ, ok := d[fldType].(nosql.String); ok {
   604  		return quad.TypedString{Value: quad.String(vs), Type: quad.IRI(typ)}, nil
   605  	} else if typ, ok := d[fldLang].(nosql.String); ok {
   606  		return quad.LangString{Value: quad.String(vs), Lang: string(typ)}, nil
   607  	}
   608  	return nil, fmt.Errorf("unsupported value: %#v", d)
   609  }
   610  
   611  func (qs *QuadStore) Quad(val graph.Ref) quad.Quad {
   612  	h := val.(QuadHash)
   613  	return quad.Quad{
   614  		Subject:   qs.NameOf(NodeHash(h.Get(quad.Subject))),
   615  		Predicate: qs.NameOf(NodeHash(h.Get(quad.Predicate))),
   616  		Object:    qs.NameOf(NodeHash(h.Get(quad.Object))),
   617  		Label:     qs.NameOf(NodeHash(h.Get(quad.Label))),
   618  	}
   619  }
   620  
   621  func (qs *QuadStore) QuadIterator(d quad.Direction, val graph.Ref) graph.Iterator {
   622  	h, ok := val.(NodeHash)
   623  	if !ok {
   624  		return iterator.NewNull()
   625  	}
   626  	return NewLinksToIterator(qs, "quads", []Linkage{{Dir: d, Val: h}})
   627  }
   628  
   629  func (qs *QuadStore) QuadIteratorSize(ctx context.Context, d quad.Direction, v graph.Ref) (graph.Size, error) {
   630  	h, ok := v.(NodeHash)
   631  	if !ok {
   632  		return graph.Size{Size: 0, Exact: true}, nil
   633  	}
   634  	sz, err := qs.getSize("quads", linkageToFilters([]Linkage{{Dir: d, Val: h}}))
   635  	if err != nil {
   636  		return graph.Size{}, err
   637  	}
   638  	return graph.Size{
   639  		Size:  sz,
   640  		Exact: true,
   641  	}, nil
   642  }
   643  
   644  func (qs *QuadStore) NodesAllIterator() graph.Iterator {
   645  	return NewIterator(qs, "nodes")
   646  }
   647  
   648  func (qs *QuadStore) QuadsAllIterator() graph.Iterator {
   649  	return NewIterator(qs, "quads")
   650  }
   651  
   652  func (qs *QuadStore) hashOf(s quad.Value) NodeHash {
   653  	return NodeHash(hashOf(s))
   654  }
   655  
   656  func (qs *QuadStore) ValueOf(s quad.Value) graph.Ref {
   657  	if s == nil {
   658  		return nil
   659  	}
   660  	return qs.hashOf(s)
   661  }
   662  
   663  func (qs *QuadStore) NameOf(v graph.Ref) quad.Value {
   664  	if v == nil {
   665  		return nil
   666  	} else if v, ok := v.(graph.PreFetchedValue); ok {
   667  		return v.NameOf()
   668  	}
   669  	hash := v.(NodeHash)
   670  	if hash == "" {
   671  		return nil
   672  	}
   673  	if val, ok := qs.ids.Get(string(hash)); ok {
   674  		return val.(quad.Value)
   675  	}
   676  	nd, err := qs.db.FindByKey(context.TODO(), colNodes, hash.key())
   677  	if err != nil {
   678  		clog.Errorf("couldn't retrieve node %v: %v", v, err)
   679  		return nil
   680  	}
   681  	dv, _ := nd[fldValue].(nosql.Document)
   682  	qv, err := toQuadValue(&qs.opt, dv)
   683  	if err != nil {
   684  		clog.Errorf("couldn't convert node %v: %v", v, err)
   685  		return nil
   686  	}
   687  	if id, _ := nd[fldHash].(nosql.String); id == nosql.String(hash) && qv != nil {
   688  		qs.ids.Put(string(hash), qv)
   689  	}
   690  	return qv
   691  }
   692  
   693  func (qs *QuadStore) Stats(ctx context.Context, exact bool) (graph.Stats, error) {
   694  	// TODO(barakmich): Make size real; store it in the log, and retrieve it.
   695  	nodes, err := qs.db.Query(colNodes).Count(ctx)
   696  	if err != nil {
   697  		return graph.Stats{}, err
   698  	}
   699  	quads, err := qs.db.Query(colQuads).Count(ctx)
   700  	if err != nil {
   701  		return graph.Stats{}, err
   702  	}
   703  	return graph.Stats{
   704  		Nodes: graph.Size{
   705  			Size:  nodes,
   706  			Exact: true,
   707  		},
   708  		Quads: graph.Size{
   709  			Size:  quads,
   710  			Exact: true,
   711  		},
   712  	}, nil
   713  }
   714  
   715  func (qs *QuadStore) Size() int64 {
   716  	count, err := qs.db.Query(colQuads).Count(context.TODO())
   717  	if err != nil {
   718  		clog.Errorf("%v", err)
   719  		return 0
   720  	}
   721  	return count
   722  }
   723  
   724  func (qs *QuadStore) Close() error {
   725  	return qs.db.Close()
   726  }
   727  
   728  func (qs *QuadStore) QuadDirection(in graph.Ref, d quad.Direction) graph.Ref {
   729  	return NodeHash(in.(QuadHash).Get(d))
   730  }
   731  
   732  func (qs *QuadStore) getSize(col string, constraints []nosql.FieldFilter) (int64, error) {
   733  	cacheKey := ""
   734  	for _, c := range constraints { // FIXME
   735  		cacheKey += fmt.Sprint(c.Path, c.Filter, c.Value)
   736  	}
   737  	key := col + cacheKey
   738  	if val, ok := qs.sizes.Get(key); ok {
   739  		return val.(int64), nil
   740  	}
   741  	q := qs.db.Query(col)
   742  	if len(constraints) != 0 {
   743  		q = q.WithFields(constraints...)
   744  	}
   745  	size, err := q.Count(context.TODO())
   746  	if err != nil {
   747  		clog.Errorf("error getting size for iterator: %v", err)
   748  		return -1, err
   749  	}
   750  	qs.sizes.Put(key, int64(size))
   751  	return int64(size), nil
   752  }