Subcommits: implement iterator and remove ResultTree add Err() to sql remove redundant and less helpful indices, change fillfactor, and use COPY FROM
270 lines
5.6 KiB
Go
270 lines
5.6 KiB
Go
// 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 sql
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
|
|
"github.com/barakmich/glog"
|
|
|
|
"github.com/google/cayley/graph"
|
|
"github.com/google/cayley/graph/iterator"
|
|
"github.com/google/cayley/quad"
|
|
)
|
|
|
|
type Iterator struct {
|
|
uid uint64
|
|
tags graph.Tagger
|
|
qs *QuadStore
|
|
dir quad.Direction
|
|
val graph.Value
|
|
size int64
|
|
isAll bool
|
|
table string
|
|
cursor *sql.Rows
|
|
result graph.Value
|
|
err error
|
|
}
|
|
|
|
func (it *Iterator) makeCursor() {
|
|
var cursor *sql.Rows
|
|
var err error
|
|
if it.cursor != nil {
|
|
it.cursor.Close()
|
|
}
|
|
if it.isAll {
|
|
if it.table == "quads" {
|
|
cursor, err = it.qs.db.Query(`SELECT subject, predicate, object, label FROM quads;`)
|
|
if err != nil {
|
|
glog.Errorln("Couldn't get cursor from SQL database: %v", err)
|
|
cursor = nil
|
|
}
|
|
} else {
|
|
glog.V(4).Infoln("sql: getting node query")
|
|
cursor, err = it.qs.db.Query(`SELECT node FROM
|
|
(
|
|
SELECT subject FROM quads
|
|
UNION
|
|
SELECT predicate FROM quads
|
|
UNION
|
|
SELECT object FROM quads
|
|
UNION
|
|
SELECT label FROM quads
|
|
) AS DistinctNodes (node) WHERE node IS NOT NULL;`)
|
|
if err != nil {
|
|
glog.Errorln("Couldn't get cursor from SQL database: %v", err)
|
|
cursor = nil
|
|
}
|
|
glog.V(4).Infoln("sql: got node query")
|
|
}
|
|
} else {
|
|
cursor, err = it.qs.db.Query(
|
|
fmt.Sprintf("SELECT subject, predicate, object, label FROM quads WHERE %s = $1;", it.dir.String()), it.val.(string))
|
|
if err != nil {
|
|
glog.Errorln("Couldn't get cursor from SQL database: %v", err)
|
|
cursor = nil
|
|
}
|
|
}
|
|
it.cursor = cursor
|
|
}
|
|
|
|
func NewIterator(qs *QuadStore, d quad.Direction, val graph.Value) *Iterator {
|
|
it := &Iterator{
|
|
uid: iterator.NextUID(),
|
|
qs: qs,
|
|
dir: d,
|
|
size: -1,
|
|
val: val,
|
|
table: "quads",
|
|
isAll: false,
|
|
}
|
|
return it
|
|
}
|
|
|
|
func NewAllIterator(qs *QuadStore, table string) *Iterator {
|
|
var size int64
|
|
it := &Iterator{
|
|
uid: iterator.NextUID(),
|
|
qs: qs,
|
|
dir: quad.Any,
|
|
size: size,
|
|
table: table,
|
|
isAll: true,
|
|
}
|
|
return it
|
|
}
|
|
|
|
func (it *Iterator) UID() uint64 {
|
|
return it.uid
|
|
}
|
|
|
|
func (it *Iterator) Reset() {
|
|
it.err = nil
|
|
it.Close()
|
|
}
|
|
|
|
func (it *Iterator) Err() error {
|
|
return it.err
|
|
}
|
|
|
|
func (it *Iterator) Close() error {
|
|
if it.cursor != nil {
|
|
err := it.cursor.Close()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
it.cursor = nil
|
|
}
|
|
return nil
|
|
}
|
|
|
|
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 {
|
|
var m *Iterator
|
|
if it.isAll {
|
|
m = NewAllIterator(it.qs, it.table)
|
|
} else {
|
|
m = NewIterator(it.qs, it.dir, it.val)
|
|
}
|
|
m.tags.CopyFrom(it)
|
|
return m
|
|
}
|
|
|
|
func (it *Iterator) SubIterators() []graph.Iterator {
|
|
return nil
|
|
}
|
|
|
|
func (it *Iterator) Next() bool {
|
|
graph.NextLogIn(it)
|
|
if it.cursor == nil {
|
|
it.makeCursor()
|
|
}
|
|
if !it.cursor.Next() {
|
|
glog.V(4).Infoln("sql: No next")
|
|
err := it.cursor.Err()
|
|
if err != nil {
|
|
glog.Errorf("Cursor error in SQL: %v", err)
|
|
it.err = err
|
|
}
|
|
it.cursor.Close()
|
|
return false
|
|
}
|
|
if it.table == "nodes" {
|
|
var node string
|
|
err := it.cursor.Scan(&node)
|
|
if err != nil {
|
|
glog.Errorf("Error nexting node iterator: %v", err)
|
|
it.err = err
|
|
return false
|
|
}
|
|
it.result = node
|
|
return true
|
|
}
|
|
var q quad.Quad
|
|
err := it.cursor.Scan(&q.Subject, &q.Predicate, &q.Object, &q.Label)
|
|
if err != nil {
|
|
glog.Errorf("Error scanning sql iterator: %v", err)
|
|
it.err = err
|
|
return false
|
|
}
|
|
it.result = q
|
|
return graph.NextLogOut(it, it.result, true)
|
|
}
|
|
|
|
func (it *Iterator) Contains(v graph.Value) bool {
|
|
graph.ContainsLogIn(it, v)
|
|
if it.isAll {
|
|
return graph.ContainsLogOut(it, v, true)
|
|
}
|
|
q := v.(quad.Quad)
|
|
if q.Get(it.dir) == it.val.(string) {
|
|
return graph.ContainsLogOut(it, v, true)
|
|
}
|
|
return graph.ContainsLogOut(it, v, false)
|
|
}
|
|
|
|
func (it *Iterator) Size() (int64, bool) {
|
|
if it.size != -1 {
|
|
return it.size, true
|
|
}
|
|
it.size = it.qs.sizeForIterator(it.isAll, it.dir, it.val.(string))
|
|
return it.size, true
|
|
}
|
|
|
|
func (it *Iterator) Result() graph.Value {
|
|
return it.result
|
|
}
|
|
|
|
func (it *Iterator) NextPath() bool {
|
|
return false
|
|
}
|
|
|
|
var sqlType graph.Type
|
|
|
|
func init() {
|
|
sqlType = graph.RegisterIterator("sql")
|
|
}
|
|
|
|
func Type() graph.Type { return sqlType }
|
|
|
|
func (it *Iterator) Type() graph.Type {
|
|
if it.isAll {
|
|
return graph.All
|
|
}
|
|
return sqlType
|
|
}
|
|
|
|
func (it *Iterator) Sorted() bool { return true }
|
|
func (it *Iterator) Optimize() (graph.Iterator, bool) { return it, false }
|
|
|
|
func (it *Iterator) Describe() graph.Description {
|
|
size, _ := it.Size()
|
|
return graph.Description{
|
|
UID: it.UID(),
|
|
Name: fmt.Sprintf("%s/%s", it.val, it.dir),
|
|
Type: it.Type(),
|
|
Size: size,
|
|
}
|
|
}
|
|
|
|
func (it *Iterator) Stats() graph.IteratorStats {
|
|
size, _ := it.Size()
|
|
if it.table == "nodes" {
|
|
return graph.IteratorStats{
|
|
ContainsCost: 1,
|
|
NextCost: 9999,
|
|
Size: size,
|
|
}
|
|
}
|
|
return graph.IteratorStats{
|
|
ContainsCost: 1,
|
|
NextCost: 5,
|
|
Size: size,
|
|
}
|
|
}
|