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

     1  package sql
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"database/sql/driver"
     7  	"fmt"
     8  	"strings"
     9  	"sync"
    10  	"time"
    11  
    12  	"github.com/cayleygraph/cayley/clog"
    13  	"github.com/cayleygraph/cayley/graph"
    14  	"github.com/cayleygraph/cayley/graph/iterator"
    15  	"github.com/cayleygraph/cayley/graph/log"
    16  	"github.com/cayleygraph/cayley/internal/lru"
    17  	"github.com/cayleygraph/quad"
    18  	"github.com/cayleygraph/quad/pquads"
    19  )
    20  
    21  // Type string for generic sql QuadStore.
    22  //
    23  // Deprecated: use specific types from sub-packages.
    24  const QuadStoreType = "sql"
    25  
    26  func init() {
    27  	// Deprecated QS registration that resolves backend type via "flavor" option.
    28  	registerQuadStore(QuadStoreType, "")
    29  }
    30  
    31  func registerQuadStore(name, typ string) {
    32  	graph.RegisterQuadStore(name, graph.QuadStoreRegistration{
    33  		NewFunc: func(addr string, options graph.Options) (graph.QuadStore, error) {
    34  			return New(typ, addr, options)
    35  		},
    36  		UpgradeFunc: nil,
    37  		InitFunc: func(addr string, options graph.Options) error {
    38  			return Init(typ, addr, options)
    39  		},
    40  		IsPersistent: true,
    41  	})
    42  }
    43  
    44  var _ Value = StringVal("")
    45  
    46  type StringVal string
    47  
    48  func (v StringVal) SQLValue() interface{} {
    49  	return escapeNullByte(string(v))
    50  }
    51  
    52  type IntVal int64
    53  
    54  func (v IntVal) SQLValue() interface{} {
    55  	return int64(v)
    56  }
    57  
    58  type FloatVal float64
    59  
    60  func (v FloatVal) SQLValue() interface{} {
    61  	return float64(v)
    62  }
    63  
    64  type BoolVal bool
    65  
    66  func (v BoolVal) SQLValue() interface{} {
    67  	return bool(v)
    68  }
    69  
    70  type TimeVal time.Time
    71  
    72  func (v TimeVal) SQLValue() interface{} {
    73  	return time.Time(v)
    74  }
    75  
    76  type NodeHash struct {
    77  	graph.ValueHash
    78  }
    79  
    80  func (h NodeHash) SQLValue() interface{} {
    81  	if !h.Valid() {
    82  		return nil
    83  	}
    84  	return []byte(h.ValueHash[:])
    85  }
    86  func (h *NodeHash) Scan(src interface{}) error {
    87  	if src == nil {
    88  		*h = NodeHash{}
    89  		return nil
    90  	}
    91  	b, ok := src.([]byte)
    92  	if !ok {
    93  		return fmt.Errorf("cannot scan %T to NodeHash", src)
    94  	}
    95  	if len(b) == 0 {
    96  		*h = NodeHash{}
    97  		return nil
    98  	} else if len(b) != quad.HashSize {
    99  		return fmt.Errorf("unexpected hash length: %d", len(b))
   100  	}
   101  	copy(h.ValueHash[:], b)
   102  	return nil
   103  }
   104  
   105  func HashOf(s quad.Value) NodeHash {
   106  	return NodeHash{graph.HashOf(s)}
   107  }
   108  
   109  type QuadHashes struct {
   110  	graph.QuadHash
   111  }
   112  
   113  type QuadStore struct {
   114  	db      *sql.DB
   115  	opt     *Optimizer
   116  	flavor  Registration
   117  	ids     *lru.Cache
   118  	sizes   *lru.Cache
   119  	noSizes bool
   120  
   121  	mu    sync.RWMutex
   122  	nodes int64
   123  	quads int64
   124  }
   125  
   126  func connect(addr string, flavor string, opts graph.Options) (*sql.DB, error) {
   127  	maxOpenConnections, err := opts.IntKey("maxopenconnections", -1)
   128  
   129  	if err != nil {
   130  		return nil, fmt.Errorf("could not retrieve maxopenconnections from options: %v", err)
   131  	}
   132  
   133  	maxIdleConnections, err := opts.IntKey("maxidleconnections", -1)
   134  
   135  	if err != nil {
   136  		return nil, fmt.Errorf("could not retrieve maxIdleConnections from options: %v", err)
   137  	}
   138  
   139  	connMaxLifetime, err := opts.StringKey("connmaxlifetime", "")
   140  
   141  	if err != nil {
   142  		return nil, fmt.Errorf("could not retrieve connmaxlifetime from options: %v", err)
   143  	}
   144  
   145  	var connDuration time.Duration
   146  	if connMaxLifetime != "" {
   147  		connDuration, err = time.ParseDuration(connMaxLifetime)
   148  
   149  		if err != nil {
   150  			return nil, fmt.Errorf("couldn't parse connmaxlifetime string: %v", err)
   151  		}
   152  	}
   153  
   154  	// TODO(barakmich): Parse options for more friendly addr
   155  	conn, err := sql.Open(flavor, addr)
   156  	if err != nil {
   157  		clog.Errorf("Couldn't open database at %s: %#v", addr, err)
   158  		return nil, err
   159  	}
   160  
   161  	// "Open may just validate its arguments without creating a connection to the database."
   162  	// "To verify that the data source name is valid, call Ping."
   163  	// Source: http://golang.org/pkg/database/sql/#Open
   164  	if err := conn.Ping(); err != nil {
   165  		clog.Errorf("Couldn't open database at %s: %#v", addr, err)
   166  		return nil, err
   167  	}
   168  
   169  	if maxOpenConnections != -1 {
   170  		conn.SetMaxOpenConns(maxOpenConnections)
   171  	}
   172  	if maxIdleConnections != -1 {
   173  		conn.SetMaxIdleConns(maxIdleConnections)
   174  	}
   175  	if connDuration != 0 {
   176  		conn.SetConnMaxLifetime(connDuration)
   177  	}
   178  
   179  	return conn, nil
   180  }
   181  
   182  var nodesColumns = []string{
   183  	"hash",
   184  	"value",
   185  	"value_string",
   186  	"datatype",
   187  	"language",
   188  	"iri",
   189  	"bnode",
   190  	"value_int",
   191  	"value_bool",
   192  	"value_float",
   193  	"value_time",
   194  }
   195  
   196  var nodeInsertColumns = [][]string{
   197  	{"value"},
   198  	{"value_string", "iri"},
   199  	{"value_string", "bnode"},
   200  	{"value_string"},
   201  	{"value_string", "datatype"},
   202  	{"value_string", "language"},
   203  	{"value_int"},
   204  	{"value_bool"},
   205  	{"value_float"},
   206  	{"value_time"},
   207  }
   208  
   209  func typeFromOpts(opts graph.Options) string {
   210  	flavor, _ := opts.StringKey("flavor", "postgres")
   211  	return flavor
   212  }
   213  
   214  func Init(typ string, addr string, options graph.Options) error {
   215  	if typ == "" {
   216  		typ = typeFromOpts(options)
   217  	}
   218  	fl, ok := types[typ]
   219  	if !ok {
   220  		return fmt.Errorf("unsupported sql database: %s", typ)
   221  	}
   222  	conn, err := connect(addr, fl.Driver, options)
   223  	if err != nil {
   224  		return err
   225  	}
   226  	defer conn.Close()
   227  
   228  	nodesSql := fl.nodesTable()
   229  	quadsSql := fl.quadsTable()
   230  	indexes := fl.quadIndexes(options)
   231  
   232  	if fl.NoSchemaChangesInTx {
   233  		_, err = conn.Exec(nodesSql)
   234  		if err != nil {
   235  			err = fl.Error(err)
   236  			clog.Errorf("Cannot create nodes table: %v", err)
   237  			return err
   238  		}
   239  		_, err = conn.Exec(quadsSql)
   240  		if err != nil {
   241  			err = fl.Error(err)
   242  			clog.Errorf("Cannot create quad table: %v", err)
   243  			return err
   244  		}
   245  		for _, index := range indexes {
   246  			if _, err = conn.Exec(index); err != nil {
   247  				clog.Errorf("Cannot create index: %v", err)
   248  				return err
   249  			}
   250  		}
   251  	} else {
   252  		tx, err := conn.Begin()
   253  		if err != nil {
   254  			clog.Errorf("Couldn't begin creation transaction: %s", err)
   255  			return err
   256  		}
   257  
   258  		_, err = tx.Exec(nodesSql)
   259  		if err != nil {
   260  			tx.Rollback()
   261  			err = fl.Error(err)
   262  			clog.Errorf("Cannot create nodes table: %v", err)
   263  			return err
   264  		}
   265  		_, err = tx.Exec(quadsSql)
   266  		if err != nil {
   267  			tx.Rollback()
   268  			err = fl.Error(err)
   269  			clog.Errorf("Cannot create quad table: %v", err)
   270  			return err
   271  		}
   272  		for _, index := range indexes {
   273  			if _, err = tx.Exec(index); err != nil {
   274  				clog.Errorf("Cannot create index: %v", err)
   275  				tx.Rollback()
   276  				return err
   277  			}
   278  		}
   279  		tx.Commit()
   280  	}
   281  	return nil
   282  }
   283  
   284  func New(typ string, addr string, options graph.Options) (graph.QuadStore, error) {
   285  	if typ == "" {
   286  		typ = typeFromOpts(options)
   287  	}
   288  	fl, ok := types[typ]
   289  	if !ok {
   290  		return nil, fmt.Errorf("unsupported sql database: %s", typ)
   291  	}
   292  	conn, err := connect(addr, fl.Driver, options)
   293  	if err != nil {
   294  		return nil, err
   295  	}
   296  	qs := &QuadStore{
   297  		db:      conn,
   298  		opt:     NewOptimizer(),
   299  		flavor:  fl,
   300  		quads:   -1,
   301  		nodes:   -1,
   302  		sizes:   lru.New(1024),
   303  		ids:     lru.New(1024),
   304  		noSizes: true, // Skip size checking by default.
   305  	}
   306  	qs.opt.SetRegexpOp(qs.flavor.RegexpOp)
   307  	if qs.flavor.NoOffsetWithoutLimit {
   308  		qs.opt.NoOffsetWithoutLimit()
   309  	}
   310  
   311  	if local, err := options.BoolKey("local_optimize", false); err != nil {
   312  		return nil, err
   313  	} else if ok && local {
   314  		qs.noSizes = false
   315  	}
   316  	return qs, nil
   317  }
   318  
   319  func escapeNullByte(s string) string {
   320  	return strings.Replace(s, "\u0000", `\x00`, -1)
   321  }
   322  func unescapeNullByte(s string) string {
   323  	return strings.Replace(s, `\x00`, "\u0000", -1)
   324  }
   325  
   326  type ValueType int
   327  
   328  func (t ValueType) Columns() []string {
   329  	return nodeInsertColumns[t]
   330  }
   331  
   332  func NodeValues(h NodeHash, v quad.Value) (ValueType, []interface{}, error) {
   333  	var (
   334  		nodeKey ValueType
   335  		values  = []interface{}{h.SQLValue(), nil, nil}[:1]
   336  	)
   337  	switch v := v.(type) {
   338  	case quad.IRI:
   339  		nodeKey = 1
   340  		values = append(values, string(v), true)
   341  	case quad.BNode:
   342  		nodeKey = 2
   343  		values = append(values, string(v), true)
   344  	case quad.String:
   345  		nodeKey = 3
   346  		values = append(values, escapeNullByte(string(v)))
   347  	case quad.TypedString:
   348  		nodeKey = 4
   349  		values = append(values, escapeNullByte(string(v.Value)), string(v.Type))
   350  	case quad.LangString:
   351  		nodeKey = 5
   352  		values = append(values, escapeNullByte(string(v.Value)), v.Lang)
   353  	case quad.Int:
   354  		nodeKey = 6
   355  		values = append(values, int64(v))
   356  	case quad.Bool:
   357  		nodeKey = 7
   358  		values = append(values, bool(v))
   359  	case quad.Float:
   360  		nodeKey = 8
   361  		values = append(values, float64(v))
   362  	case quad.Time:
   363  		nodeKey = 9
   364  		values = append(values, time.Time(v))
   365  	default:
   366  		nodeKey = 0
   367  		p, err := pquads.MarshalValue(v)
   368  		if err != nil {
   369  			clog.Errorf("couldn't marshal value: %v", err)
   370  			return 0, nil, err
   371  		}
   372  		values = append(values, p)
   373  	}
   374  	return nodeKey, values, nil
   375  }
   376  
   377  func (qs *QuadStore) NewQuadWriter() (quad.WriteCloser, error) {
   378  	return &quadWriter{qs: qs}, nil
   379  }
   380  
   381  type quadWriter struct {
   382  	qs     *QuadStore
   383  	deltas []graph.Delta
   384  }
   385  
   386  func (w *quadWriter) WriteQuad(q quad.Quad) error {
   387  	_, err := w.WriteQuads([]quad.Quad{q})
   388  	return err
   389  }
   390  
   391  func (w *quadWriter) WriteQuads(buf []quad.Quad) (int, error) {
   392  	// TODO(dennwc): write an optimized implementation
   393  	w.deltas = w.deltas[:0]
   394  	if cap(w.deltas) < len(buf) {
   395  		w.deltas = make([]graph.Delta, 0, len(buf))
   396  	}
   397  	for _, q := range buf {
   398  		w.deltas = append(w.deltas, graph.Delta{
   399  			Quad: q, Action: graph.Add,
   400  		})
   401  	}
   402  	err := w.qs.ApplyDeltas(w.deltas, graph.IgnoreOpts{
   403  		IgnoreDup: true,
   404  	})
   405  	w.deltas = w.deltas[:0]
   406  	if err != nil {
   407  		return 0, err
   408  	}
   409  	return len(buf), nil
   410  }
   411  
   412  func (w *quadWriter) Close() error {
   413  	w.deltas = nil
   414  	return nil
   415  }
   416  
   417  func (qs *QuadStore) ApplyDeltas(in []graph.Delta, opts graph.IgnoreOpts) error {
   418  	// first calculate values ref deltas
   419  	deltas := graphlog.SplitDeltas(in)
   420  
   421  	tx, err := qs.db.Begin()
   422  	if err != nil {
   423  		clog.Errorf("couldn't begin write transaction: %v", err)
   424  		return err
   425  	}
   426  
   427  	retry := qs.flavor.TxRetry
   428  	if retry == nil {
   429  		retry = func(tx *sql.Tx, stmts func() error) error {
   430  			return stmts()
   431  		}
   432  	}
   433  	p := make([]string, 4)
   434  	for i := range p {
   435  		p[i] = qs.flavor.Placeholder(i + 1)
   436  	}
   437  
   438  	err = retry(tx, func() error {
   439  		err = qs.flavor.RunTx(tx, deltas.IncNode, deltas.QuadAdd, opts)
   440  		if err != nil {
   441  			return err
   442  		}
   443  		// quad delete is also generic, execute here
   444  		var (
   445  			deleteQuad   *sql.Stmt
   446  			deleteTriple *sql.Stmt
   447  		)
   448  		fixNodes := make(map[graph.ValueHash]int)
   449  		for _, d := range deltas.QuadDel {
   450  			dirs := make([]interface{}, 0, len(quad.Directions))
   451  			for _, h := range d.Quad.Dirs() {
   452  				dirs = append(dirs, NodeHash{h}.SQLValue())
   453  			}
   454  			if deleteQuad == nil {
   455  				deleteQuad, err = tx.Prepare(`DELETE FROM quads WHERE subject_hash=` + p[0] + ` and predicate_hash=` + p[1] + ` and object_hash=` + p[2] + ` and label_hash=` + p[3] + `;`)
   456  				if err != nil {
   457  					return err
   458  				}
   459  				deleteTriple, err = tx.Prepare(`DELETE FROM quads WHERE subject_hash=` + p[0] + ` and predicate_hash=` + p[1] + ` and object_hash=` + p[2] + ` and label_hash is null;`)
   460  				if err != nil {
   461  					return err
   462  				}
   463  			}
   464  			stmt := deleteQuad
   465  			if i := len(dirs) - 1; dirs[i] == nil {
   466  				stmt = deleteTriple
   467  				dirs = dirs[:i]
   468  			}
   469  			result, err := stmt.Exec(dirs...)
   470  			if err != nil {
   471  				clog.Errorf("couldn't exec DELETE statement: %v", err)
   472  				return err
   473  			}
   474  			affected, err := result.RowsAffected()
   475  			if err != nil {
   476  				clog.Errorf("couldn't get DELETE RowsAffected: %v", err)
   477  				return err
   478  			}
   479  			if affected != 1 {
   480  				if !opts.IgnoreMissing {
   481  					// TODO: reference to delta
   482  					return &graph.DeltaError{Err: graph.ErrQuadNotExist}
   483  				}
   484  				// revert counters for all directions of this quad
   485  				for _, dir := range quad.Directions {
   486  					if h := d.Quad.Get(dir); h.Valid() {
   487  						fixNodes[h]++
   488  					}
   489  				}
   490  			}
   491  		}
   492  		if len(deltas.DecNode) == 0 {
   493  			return nil
   494  		}
   495  		// node update SQL is generic enough to run it here
   496  		updateNode, err := tx.Prepare(`UPDATE nodes SET refs = refs + ` + p[0] + ` WHERE hash = ` + p[1] + `;`)
   497  		if err != nil {
   498  			return err
   499  		}
   500  		for _, n := range deltas.DecNode {
   501  			n.RefInc += fixNodes[n.Hash]
   502  			if n.RefInc == 0 {
   503  				continue
   504  			}
   505  			_, err := updateNode.Exec(n.RefInc, NodeHash{n.Hash}.SQLValue())
   506  			if err != nil {
   507  				clog.Errorf("couldn't exec UPDATE statement: %v", err)
   508  				return err
   509  			}
   510  		}
   511  		// and remove unused nodes at last
   512  		_, err = tx.Exec(`DELETE FROM nodes WHERE refs <= 0;`)
   513  		if err != nil {
   514  			clog.Errorf("couldn't exec DELETE nodes statement: %v", err)
   515  			return err
   516  		}
   517  		return nil
   518  	})
   519  	if err != nil {
   520  		tx.Rollback()
   521  		return err
   522  	}
   523  
   524  	qs.mu.Lock()
   525  	// TODO(barakmich): Sync size with writes.
   526  	qs.quads = -1
   527  	qs.nodes = -1
   528  	qs.mu.Unlock()
   529  	return tx.Commit()
   530  }
   531  
   532  func (qs *QuadStore) Quad(val graph.Ref) quad.Quad {
   533  	h := val.(QuadHashes)
   534  	return quad.Quad{
   535  		Subject:   qs.NameOf(h.Get(quad.Subject)),
   536  		Predicate: qs.NameOf(h.Get(quad.Predicate)),
   537  		Object:    qs.NameOf(h.Get(quad.Object)),
   538  		Label:     qs.NameOf(h.Get(quad.Label)),
   539  	}
   540  }
   541  
   542  func (qs *QuadStore) QuadIterator(d quad.Direction, val graph.Ref) graph.Iterator {
   543  	v, ok := val.(Value)
   544  	if !ok {
   545  		return iterator.NewNull()
   546  	}
   547  	sel := AllQuads("")
   548  	sel.WhereEq("", dirField(d), v)
   549  	return qs.NewIterator(sel)
   550  }
   551  
   552  func (qs *QuadStore) querySize(ctx context.Context, sel Select) (graph.Size, error) {
   553  	sel.Fields = []Field{
   554  		{Name: "COUNT(*)", Raw: true}, // TODO: proper support for expressions
   555  	}
   556  	var sz int64
   557  	err := qs.QueryRow(ctx, sel).Scan(&sz)
   558  	if err != nil {
   559  		return graph.Size{}, err
   560  	}
   561  	return graph.Size{
   562  		Size:  sz,
   563  		Exact: true,
   564  	}, nil
   565  }
   566  
   567  func (qs *QuadStore) QuadIteratorSize(ctx context.Context, d quad.Direction, val graph.Ref) (graph.Size, error) {
   568  	v, ok := val.(Value)
   569  	if !ok {
   570  		return graph.Size{Size: 0, Exact: true}, nil
   571  	}
   572  	sel := AllQuads("")
   573  	sel.WhereEq("", dirField(d), v)
   574  	return qs.querySize(ctx, sel)
   575  }
   576  
   577  func (qs *QuadStore) NodesAllIterator() graph.Iterator {
   578  	return qs.NewIterator(AllNodes())
   579  }
   580  
   581  func (qs *QuadStore) QuadsAllIterator() graph.Iterator {
   582  	return qs.NewIterator(AllQuads(""))
   583  }
   584  
   585  func (qs *QuadStore) ValueOf(s quad.Value) graph.Ref {
   586  	return NodeHash(HashOf(s))
   587  }
   588  
   589  // NullTime represents a time.Time that may be null. NullTime implements the
   590  // sql.Scanner interface so it can be used as a scan destination, similar to
   591  // sql.NullString.
   592  type NullTime struct {
   593  	Time  time.Time
   594  	Valid bool // Valid is true if Time is not NULL
   595  }
   596  
   597  // Scan implements the Scanner interface.
   598  func (nt *NullTime) Scan(value interface{}) error {
   599  	if value == nil {
   600  		nt.Time, nt.Valid = time.Time{}, false
   601  		return nil
   602  	}
   603  	switch value := value.(type) {
   604  	case time.Time:
   605  		nt.Time, nt.Valid = value, true
   606  	case []byte:
   607  		t, err := time.Parse("2006-01-02 15:04:05.999999", string(value))
   608  		if err != nil {
   609  			return err
   610  		}
   611  		nt.Time, nt.Valid = t, true
   612  	default:
   613  		return fmt.Errorf("unsupported time format: %T: %v", value, value)
   614  	}
   615  	return nil
   616  }
   617  
   618  // Value implements the driver Valuer interface.
   619  func (nt NullTime) Value() (driver.Value, error) {
   620  	if !nt.Valid {
   621  		return nil, nil
   622  	}
   623  	return nt.Time, nil
   624  }
   625  
   626  func (qs *QuadStore) NameOf(v graph.Ref) quad.Value {
   627  	if v == nil {
   628  		if clog.V(2) {
   629  			clog.Infof("NameOf was nil")
   630  		}
   631  		return nil
   632  	} else if v, ok := v.(graph.PreFetchedValue); ok {
   633  		return v.NameOf()
   634  	}
   635  	var hash NodeHash
   636  	switch h := v.(type) {
   637  	case graph.PreFetchedValue:
   638  		return h.NameOf()
   639  	case NodeHash:
   640  		hash = h
   641  	case graph.ValueHash:
   642  		hash = NodeHash{h}
   643  	default:
   644  		panic(fmt.Errorf("unexpected token: %T", v))
   645  	}
   646  	if !hash.Valid() {
   647  		if clog.V(2) {
   648  			clog.Infof("NameOf was nil")
   649  		}
   650  		return nil
   651  	}
   652  	if val, ok := qs.ids.Get(hash.String()); ok {
   653  		return val.(quad.Value)
   654  	}
   655  	query := `SELECT
   656  		value,
   657  		value_string,
   658  		datatype,
   659  		language,
   660  		iri,
   661  		bnode,
   662  		value_int,
   663  		value_bool,
   664  		value_float,
   665  		value_time
   666  	FROM nodes WHERE hash = ` + qs.flavor.Placeholder(1) + ` LIMIT 1;`
   667  	c := qs.db.QueryRow(query, hash.SQLValue())
   668  	var (
   669  		data   []byte
   670  		str    sql.NullString
   671  		typ    sql.NullString
   672  		lang   sql.NullString
   673  		iri    sql.NullBool
   674  		bnode  sql.NullBool
   675  		vint   sql.NullInt64
   676  		vbool  sql.NullBool
   677  		vfloat sql.NullFloat64
   678  		vtime  NullTime
   679  	)
   680  	if err := c.Scan(
   681  		&data,
   682  		&str,
   683  		&typ,
   684  		&lang,
   685  		&iri,
   686  		&bnode,
   687  		&vint,
   688  		&vbool,
   689  		&vfloat,
   690  		&vtime,
   691  	); err != nil {
   692  		if err != sql.ErrNoRows {
   693  			clog.Errorf("Couldn't execute value lookup: %v", err)
   694  		}
   695  		return nil
   696  	}
   697  	var val quad.Value
   698  	if str.Valid {
   699  		if iri.Bool {
   700  			val = quad.IRI(str.String)
   701  		} else if bnode.Bool {
   702  			val = quad.BNode(str.String)
   703  		} else if lang.Valid {
   704  			val = quad.LangString{
   705  				Value: quad.String(unescapeNullByte(str.String)),
   706  				Lang:  lang.String,
   707  			}
   708  		} else if typ.Valid {
   709  			val = quad.TypedString{
   710  				Value: quad.String(unescapeNullByte(str.String)),
   711  				Type:  quad.IRI(typ.String),
   712  			}
   713  		} else {
   714  			val = quad.String(unescapeNullByte(str.String))
   715  		}
   716  	} else if vint.Valid {
   717  		val = quad.Int(vint.Int64)
   718  	} else if vbool.Valid {
   719  		val = quad.Bool(vbool.Bool)
   720  	} else if vfloat.Valid {
   721  		val = quad.Float(vfloat.Float64)
   722  	} else if vtime.Valid {
   723  		val = quad.Time(vtime.Time)
   724  	} else {
   725  		qv, err := pquads.UnmarshalValue(data)
   726  		if err != nil {
   727  			clog.Errorf("Couldn't unmarshal value: %v", err)
   728  			return nil
   729  		}
   730  		val = qv
   731  	}
   732  	if val != nil {
   733  		qs.ids.Put(hash.String(), val)
   734  	}
   735  	return val
   736  }
   737  
   738  func (qs *QuadStore) Stats(ctx context.Context, exact bool) (graph.Stats, error) {
   739  	st := graph.Stats{
   740  		Nodes: graph.Size{Exact: true},
   741  		Quads: graph.Size{Exact: true},
   742  	}
   743  	qs.mu.RLock()
   744  	st.Quads.Size = qs.quads
   745  	st.Nodes.Size = qs.nodes
   746  	qs.mu.RUnlock()
   747  	if st.Quads.Size >= 0 {
   748  		return st, nil
   749  	}
   750  	query := func(table string) string {
   751  		return "SELECT COUNT(*) FROM " + table + ";"
   752  	}
   753  	if !exact && qs.flavor.Estimated != nil {
   754  		query = qs.flavor.Estimated
   755  		st.Quads.Exact = false
   756  		st.Nodes.Exact = false
   757  	}
   758  	err := qs.db.QueryRow(query("quads")).Scan(&st.Quads.Size)
   759  	if err != nil {
   760  		return graph.Stats{}, err
   761  	}
   762  	err = qs.db.QueryRow(query("nodes")).Scan(&st.Nodes.Size)
   763  	if err != nil {
   764  		return graph.Stats{}, err
   765  	}
   766  	if st.Quads.Exact {
   767  		qs.mu.Lock()
   768  		qs.quads = st.Quads.Size
   769  		qs.nodes = st.Nodes.Size
   770  		qs.mu.Unlock()
   771  	}
   772  	return st, nil
   773  }
   774  
   775  func (qs *QuadStore) Close() error {
   776  	return qs.db.Close()
   777  }
   778  
   779  func (qs *QuadStore) QuadDirection(in graph.Ref, d quad.Direction) graph.Ref {
   780  	return NodeHash{in.(QuadHashes).Get(d)}
   781  }
   782  
   783  func (qs *QuadStore) sizeForIterator(dir quad.Direction, hash NodeHash) int64 {
   784  	var err error
   785  	if qs.noSizes {
   786  		st, _ := qs.Stats(context.TODO(), false)
   787  		if dir == quad.Predicate {
   788  			return (st.Quads.Size / 100) + 1
   789  		}
   790  		return (st.Quads.Size / 1000) + 1
   791  	}
   792  	if val, ok := qs.sizes.Get(hash.String() + string(dir.Prefix())); ok {
   793  		return val.(int64)
   794  	}
   795  	var size int64
   796  	if clog.V(4) {
   797  		clog.Infof("sql: getting size for select %s, %v", dir.String(), hash)
   798  	}
   799  	err = qs.db.QueryRow(
   800  		fmt.Sprintf("SELECT count(*) FROM quads WHERE %s_hash = "+qs.flavor.Placeholder(1)+";", dir.String()), hash.SQLValue()).Scan(&size)
   801  	if err != nil {
   802  		clog.Errorf("Error getting size from SQL database: %v", err)
   803  		return 0
   804  	}
   805  	qs.sizes.Put(hash.String()+string(dir.Prefix()), size)
   806  	return size
   807  }