Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add sqlite backend #751

Merged
merged 3 commits into from
Feb 6, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file modified build.sh
100644 → 100755
Empty file.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ require (
github.com/lib/pq v0.0.0-20170324204654-2704adc878c2
github.com/linkeddata/gojsonld v0.0.0-20170418210642-4f5db6791326
github.com/magiconair/properties v0.0.0-20170321093039-51463bfca257 // indirect
github.com/mattn/go-sqlite3 v1.10.0
github.com/mitchellh/mapstructure v0.0.0-20170422000251-cc8532a8e9a5 // indirect
github.com/onsi/ginkgo v1.7.0 // indirect
github.com/onsi/gomega v1.4.3 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ github.com/linkeddata/gojsonld v0.0.0-20170418210642-4f5db6791326 h1:YP3lfXXYiQV
github.com/linkeddata/gojsonld v0.0.0-20170418210642-4f5db6791326/go.mod h1:nfqkuSNlsk1bvti/oa7TThx4KmRMBmSxf3okHI9wp3E=
github.com/magiconair/properties v0.0.0-20170321093039-51463bfca257 h1:nqPtTOaJx2zFKWinLXKQYQRfxXTZN3GoJ602Uo65VGY=
github.com/magiconair/properties v0.0.0-20170321093039-51463bfca257/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mitchellh/mapstructure v0.0.0-20170422000251-cc8532a8e9a5 h1:AjVRQelY0a5wq4bUgs08/+iblph90u9H58uzaH7Cw3g=
github.com/mitchellh/mapstructure v0.0.0-20170422000251-cc8532a8e9a5/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
Expand Down
1 change: 1 addition & 0 deletions graph/all/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ import (
_ "github.com/cayleygraph/cayley/graph/sql/cockroach"
_ "github.com/cayleygraph/cayley/graph/sql/mysql"
_ "github.com/cayleygraph/cayley/graph/sql/postgres"
_ "github.com/cayleygraph/cayley/graph/sql/sqlite"
)
1 change: 1 addition & 0 deletions graph/sql/cockroach/cockroach_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ func makeCockroach(t testing.TB) (string, graph.Options, func()) {

var conf = &sqltest.Config{
TimeRound: true,
TimeInMcs: true,
}

func TestCockroach(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions graph/sql/postgres/postgres_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ func makePostgres(t testing.TB) (string, graph.Options, func()) {

var conf = &sqltest.Config{
TimeRound: true,
TimeInMcs: true,
}

func TestPostgres(t *testing.T) {
Expand Down
151 changes: 151 additions & 0 deletions graph/sql/sqlite/sqlite.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package sqlite

import (
"database/sql"
"fmt"
"regexp"
"strings"

"github.com/cayleygraph/cayley/clog"
"github.com/cayleygraph/cayley/graph"
graphlog "github.com/cayleygraph/cayley/graph/log"
csql "github.com/cayleygraph/cayley/graph/sql"
"github.com/cayleygraph/cayley/quad"
sqlite3 "github.com/mattn/go-sqlite3"
)

const Type = "sqlite"

var QueryDialect = csql.QueryDialect{
RegexpOp: "REGEXP",
FieldQuote: func(name string) string {
return "`" + name + "`"
},
Placeholder: func(n int) string { return "?" },
}

func init() {
regex := func(re, s string) (bool, error) {
return regexp.MatchString(re, s)
}
sql.Register("sqlite3-regexp",
&sqlite3.SQLiteDriver{
ConnectHook: func(conn *sqlite3.SQLiteConn) error {
return conn.RegisterFunc("regexp", regex, true)
},
})
csql.Register(Type, csql.Registration{
Driver: "sqlite3-regexp",
HashType: fmt.Sprintf(`BINARY(%d)`, quad.HashSize),
BytesType: `BLOB`,
HorizonType: `INTEGER`,
TimeType: `DATETIME`,
QueryDialect: QueryDialect,
NoOffsetWithoutLimit: true,
NoForeignKeys: true,
Error: func(err error) error {
return err
},
Estimated: nil,
RunTx: runTxSqlite,
})
}

func runTxSqlite(tx *sql.Tx, nodes []graphlog.NodeUpdate, quads []graphlog.QuadUpdate, opts graph.IgnoreOpts) error {
// update node ref counts and insert nodes
var (
// prepared statements for each value type
insertValue = make(map[csql.ValueType]*sql.Stmt)
updateValue *sql.Stmt
)
for _, n := range nodes {
if n.RefInc >= 0 {
nodeKey, values, err := csql.NodeValues(csql.NodeHash{n.Hash}, n.Val)
if err != nil {
return err
}
values = append([]interface{}{n.RefInc}, values...)
values = append(values, n.RefInc) // one more time for UPDATE
stmt, ok := insertValue[nodeKey]
if !ok {
var ph = make([]string, len(values)-1) // excluding last increment
for i := range ph {
ph[i] = "?"
}
stmt, err = tx.Prepare(`INSERT INTO nodes(refs, hash, ` +
strings.Join(nodeKey.Columns(), ", ") +
`) VALUES (` + strings.Join(ph, ", ") +
`) ON CONFLICT(hash) DO UPDATE SET refs = refs + ?;`)
if err != nil {
return err
}
insertValue[nodeKey] = stmt
}
_, err = stmt.Exec(values...)
err = convInsertError(err)
if err != nil {
clog.Errorf("couldn't exec INSERT statement: %v", err)
return err
}
} else {
panic("unexpected node update")
}
}
for _, s := range insertValue {
s.Close()
}
if s := updateValue; s != nil {
s.Close()
}
insertValue = nil
updateValue = nil

// now we can deal with quads
ignore := ""
if opts.IgnoreDup {
ignore = " OR IGNORE"
}

var (
insertQuad *sql.Stmt
err error
)
for _, d := range quads {
dirs := make([]interface{}, 0, len(quad.Directions))
for _, h := range d.Quad.Dirs() {
dirs = append(dirs, csql.NodeHash{h}.SQLValue())
}
if !d.Del {
if insertQuad == nil {
insertQuad, err = tx.Prepare(`INSERT` + ignore + ` INTO quads(subject_hash, predicate_hash, object_hash, label_hash, ts) VALUES (?, ?, ?, ?, datetime());`)
if err != nil {
return err
}
insertValue = make(map[csql.ValueType]*sql.Stmt)
}
_, err := insertQuad.Exec(dirs...)
err = convInsertError(err)
if err != nil {
if _, ok := err.(*graph.DeltaError); !ok {
clog.Errorf("couldn't exec INSERT statement: %v", err)
}
return err
}
} else {
panic("unexpected quad delete")
}
}
return nil
}

func convInsertError(err error) error {
if err == nil {
return nil
}
if e, ok := err.(sqlite3.Error); ok {
if e.Code == sqlite3.ErrConstraint {
return &graph.DeltaError{Err: graph.ErrQuadExists}
}
}
return err
}
34 changes: 34 additions & 0 deletions graph/sql/sqlite/sqlite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package sqlite

import (
"fmt"
"io/ioutil"
"os"
"testing"

"github.com/cayleygraph/cayley/graph"
"github.com/cayleygraph/cayley/graph/sql/sqltest"
)

func makeSqlite(t testing.TB) (string, graph.Options, func()) {
tmpFile, err := ioutil.TempFile("", fmt.Sprintf("cayley_test_%s*", Type))
if err != nil {
t.Fatalf("Could not create working directory: %v", err)
}
return fmt.Sprintf("file:%s?_loc=UTC", tmpFile.Name()), nil, func() {
os.RemoveAll(tmpFile.Name())
}
}

var conf = &sqltest.Config{
TimeRound: true,
TimeInMcs: false,
}

func TestSqlite(t *testing.T) {
sqltest.TestAll(t, Type, makeSqlite, conf)
}

func BenchmarkSqlite(t *testing.B) {
sqltest.BenchmarkAll(t, Type, makeSqlite, conf)
}
5 changes: 3 additions & 2 deletions graph/sql/sqltest/sqltest.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,21 @@ import (

type Config struct {
TimeRound bool
TimeInMcs bool
}

func (c Config) quadStore() *graphtest.Config {
return &graphtest.Config{
NoPrimitives: true,
TimeInMcs: true,
TimeInMcs: c.TimeInMcs,
TimeRound: c.TimeRound,
OptimizesComparison: true,
}
}

func TestAll(t *testing.T, typ string, fnc DatabaseFunc, c *Config) {
if c == nil {
c = &Config{}
c = &Config{TimeInMcs: true}
}
create := makeDatabaseFunc(typ, fnc)
t.Run("qs", func(t *testing.T) {
Expand Down