github.com/cayleygraph/cayley@v0.7.7/graph/sql/sqlite/sqlite.go (about) 1 //+build cgo 2 3 package sqlite 4 5 import ( 6 "database/sql" 7 "fmt" 8 "regexp" 9 "strings" 10 11 "github.com/cayleygraph/cayley/clog" 12 "github.com/cayleygraph/cayley/graph" 13 graphlog "github.com/cayleygraph/cayley/graph/log" 14 csql "github.com/cayleygraph/cayley/graph/sql" 15 "github.com/cayleygraph/quad" 16 sqlite3 "github.com/mattn/go-sqlite3" 17 ) 18 19 const Type = "sqlite" 20 21 var QueryDialect = csql.QueryDialect{ 22 RegexpOp: "REGEXP", 23 FieldQuote: func(name string) string { 24 return "`" + name + "`" 25 }, 26 Placeholder: func(n int) string { return "?" }, 27 } 28 29 func init() { 30 regex := func(re, s string) (bool, error) { 31 return regexp.MatchString(re, s) 32 } 33 sql.Register("sqlite3-regexp", 34 &sqlite3.SQLiteDriver{ 35 ConnectHook: func(conn *sqlite3.SQLiteConn) error { 36 return conn.RegisterFunc("regexp", regex, true) 37 }, 38 }) 39 csql.Register(Type, csql.Registration{ 40 Driver: "sqlite3-regexp", 41 HashType: fmt.Sprintf(`BINARY(%d)`, quad.HashSize), 42 BytesType: `BLOB`, 43 HorizonType: `INTEGER`, 44 TimeType: `DATETIME`, 45 QueryDialect: QueryDialect, 46 NoOffsetWithoutLimit: true, 47 NoForeignKeys: true, 48 Error: func(err error) error { 49 return err 50 }, 51 Estimated: nil, 52 RunTx: runTxSqlite, 53 }) 54 } 55 56 func runTxSqlite(tx *sql.Tx, nodes []graphlog.NodeUpdate, quads []graphlog.QuadUpdate, opts graph.IgnoreOpts) error { 57 // update node ref counts and insert nodes 58 var ( 59 // prepared statements for each value type 60 insertValue = make(map[csql.ValueType]*sql.Stmt) 61 updateValue *sql.Stmt 62 ) 63 for _, n := range nodes { 64 if n.RefInc >= 0 { 65 nodeKey, values, err := csql.NodeValues(csql.NodeHash{n.Hash}, n.Val) 66 if err != nil { 67 return err 68 } 69 values = append([]interface{}{n.RefInc}, values...) 70 values = append(values, n.RefInc) // one more time for UPDATE 71 stmt, ok := insertValue[nodeKey] 72 if !ok { 73 var ph = make([]string, len(values)-1) // excluding last increment 74 for i := range ph { 75 ph[i] = "?" 76 } 77 stmt, err = tx.Prepare(`INSERT INTO nodes(refs, hash, ` + 78 strings.Join(nodeKey.Columns(), ", ") + 79 `) VALUES (` + strings.Join(ph, ", ") + 80 `) ON CONFLICT(hash) DO UPDATE SET refs = refs + ?;`) 81 if err != nil { 82 return err 83 } 84 insertValue[nodeKey] = stmt 85 } 86 _, err = stmt.Exec(values...) 87 err = convInsertError(err) 88 if err != nil { 89 clog.Errorf("couldn't exec INSERT statement: %v", err) 90 return err 91 } 92 } else { 93 panic("unexpected node update") 94 } 95 } 96 for _, s := range insertValue { 97 s.Close() 98 } 99 if s := updateValue; s != nil { 100 s.Close() 101 } 102 insertValue = nil 103 updateValue = nil 104 105 // now we can deal with quads 106 ignore := "" 107 if opts.IgnoreDup { 108 ignore = " OR IGNORE" 109 } 110 111 var ( 112 insertQuad *sql.Stmt 113 err error 114 ) 115 for _, d := range quads { 116 dirs := make([]interface{}, 0, len(quad.Directions)) 117 for _, h := range d.Quad.Dirs() { 118 dirs = append(dirs, csql.NodeHash{h}.SQLValue()) 119 } 120 if !d.Del { 121 if insertQuad == nil { 122 insertQuad, err = tx.Prepare(`INSERT` + ignore + ` INTO quads(subject_hash, predicate_hash, object_hash, label_hash, ts) VALUES (?, ?, ?, ?, datetime());`) 123 if err != nil { 124 return err 125 } 126 insertValue = make(map[csql.ValueType]*sql.Stmt) 127 } 128 _, err := insertQuad.Exec(dirs...) 129 err = convInsertError(err) 130 if err != nil { 131 if _, ok := err.(*graph.DeltaError); !ok { 132 clog.Errorf("couldn't exec INSERT statement: %v", err) 133 } 134 return err 135 } 136 } else { 137 panic("unexpected quad delete") 138 } 139 } 140 return nil 141 } 142 143 func convInsertError(err error) error { 144 if err == nil { 145 return nil 146 } 147 if e, ok := err.(sqlite3.Error); ok { 148 if e.Code == sqlite3.ErrConstraint { 149 return &graph.DeltaError{Err: graph.ErrQuadExists} 150 } 151 } 152 return err 153 }