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