github.com/cayleygraph/cayley@v0.7.7/graph/sql/postgres/postgres.go (about) 1 package postgres 2 3 import ( 4 "database/sql" 5 "fmt" 6 "strconv" 7 "strings" 8 9 "github.com/cayleygraph/cayley/clog" 10 "github.com/cayleygraph/cayley/graph" 11 "github.com/cayleygraph/cayley/graph/log" 12 csql "github.com/cayleygraph/cayley/graph/sql" 13 "github.com/cayleygraph/quad" 14 "github.com/lib/pq" 15 ) 16 17 const Type = "postgres" 18 19 var QueryDialect = csql.QueryDialect{ 20 RegexpOp: "~", 21 FieldQuote: pq.QuoteIdentifier, 22 Placeholder: func(n int) string { 23 return fmt.Sprintf("$%d", n) 24 }, 25 } 26 27 func init() { 28 csql.Register(Type, csql.Registration{ 29 Driver: "postgres", 30 HashType: `BYTEA`, 31 BytesType: `BYTEA`, 32 HorizonType: `BIGSERIAL`, 33 TimeType: `timestamp with time zone`, 34 QueryDialect: QueryDialect, 35 ConditionalIndexes: true, 36 FillFactor: true, 37 Error: ConvError, 38 Estimated: func(table string) string { 39 return "SELECT reltuples::BIGINT AS estimate FROM pg_class WHERE relname='" + table + "';" 40 }, 41 RunTx: RunTxPostgres, 42 }) 43 } 44 45 func ConvError(err error) error { 46 e, ok := err.(*pq.Error) 47 if !ok { 48 return err 49 } 50 switch e.Code { 51 case "42P07": 52 return graph.ErrDatabaseExists 53 } 54 return err 55 } 56 57 func convInsertError(err error) error { 58 if err == nil { 59 return err 60 } 61 if pe, ok := err.(*pq.Error); ok { 62 if pe.Code == "23505" { 63 // TODO: reference to delta 64 return &graph.DeltaError{Err: graph.ErrQuadExists} 65 } 66 } 67 return err 68 } 69 70 //func copyFromPG(tx *sql.Tx, in []graph.Delta, opts graph.IgnoreOpts) error { 71 // panic("broken") 72 // stmt, err := tx.Prepare(pq.CopyIn("quads", "subject", "predicate", "object", "label", "id", "ts", "subject_hash", "predicate_hash", "object_hash", "label_hash")) 73 // if err != nil { 74 // clog.Errorf("couldn't prepare COPY statement: %v", err) 75 // return err 76 // } 77 // for _, d := range in { 78 // s, p, o, l, err := marshalQuadDirections(d.Quad) 79 // if err != nil { 80 // clog.Errorf("couldn't marshal quads: %v", err) 81 // return err 82 // } 83 // _, err = stmt.Exec( 84 // s, 85 // p, 86 // o, 87 // l, 88 // d.ID.Int(), 89 // d.Timestamp, 90 // hashOf(d.Quad.Subject), 91 // hashOf(d.Quad.Predicate), 92 // hashOf(d.Quad.Object), 93 // hashOf(d.Quad.Label), 94 // ) 95 // if err != nil { 96 // err = convInsertErrorPG(err) 97 // clog.Errorf("couldn't execute COPY statement: %v", err) 98 // return err 99 // } 100 // } 101 // _, err = stmt.Exec() 102 // if err != nil { 103 // err = convInsertErrorPG(err) 104 // return err 105 // } 106 // _ = stmt.Close() // COPY will be closed on last Exec, this will return non-nil error in all cases 107 // return nil 108 //} 109 110 func RunTxPostgres(tx *sql.Tx, nodes []graphlog.NodeUpdate, quads []graphlog.QuadUpdate, opts graph.IgnoreOpts) error { 111 return RunTx(tx, nodes, quads, opts, "") 112 } 113 114 func RunTx(tx *sql.Tx, nodes []graphlog.NodeUpdate, quads []graphlog.QuadUpdate, opts graph.IgnoreOpts, onConflict string) error { 115 // update node ref counts and insert nodes 116 var ( 117 // prepared statements for each value type 118 insertValue = make(map[csql.ValueType]*sql.Stmt) 119 updateValue *sql.Stmt 120 ) 121 for _, n := range nodes { 122 if n.RefInc >= 0 { 123 nodeKey, values, err := csql.NodeValues(csql.NodeHash{n.Hash}, n.Val) 124 if err != nil { 125 return err 126 } 127 values = append([]interface{}{n.RefInc}, values...) 128 stmt, ok := insertValue[nodeKey] 129 if !ok { 130 var ph = make([]string, len(values)) 131 for i := range ph { 132 ph[i] = "$" + strconv.FormatInt(int64(i)+1, 10) 133 } 134 stmt, err = tx.Prepare(`INSERT INTO nodes(refs, hash, ` + 135 strings.Join(nodeKey.Columns(), ", ") + 136 `) VALUES (` + strings.Join(ph, ", ") + 137 `) ON CONFLICT (hash) DO UPDATE SET refs = nodes.refs + EXCLUDED.refs;`) 138 if err != nil { 139 return err 140 } 141 insertValue[nodeKey] = stmt 142 } 143 _, err = stmt.Exec(values...) 144 err = convInsertError(err) 145 if err != nil { 146 clog.Errorf("couldn't exec INSERT statement: %v", err) 147 return err 148 } 149 } else { 150 panic("unexpected node update") 151 } 152 } 153 for _, s := range insertValue { 154 s.Close() 155 } 156 if s := updateValue; s != nil { 157 s.Close() 158 } 159 insertValue = nil 160 updateValue = nil 161 162 // now we can deal with quads 163 164 // TODO: copy 165 //if allAdds && !opts.IgnoreDup { 166 // return qs.copyFrom(tx, in, opts) 167 //} 168 169 end := ";" 170 if opts.IgnoreDup { 171 end = ` ON CONFLICT ` + onConflict + ` DO NOTHING;` 172 } 173 174 var ( 175 insertQuad *sql.Stmt 176 err error 177 ) 178 for _, d := range quads { 179 dirs := make([]interface{}, 0, len(quad.Directions)) 180 for _, h := range d.Quad.Dirs() { 181 dirs = append(dirs, csql.NodeHash{h}.SQLValue()) 182 } 183 if !d.Del { 184 if insertQuad == nil { 185 insertQuad, err = tx.Prepare(`INSERT INTO quads(subject_hash, predicate_hash, object_hash, label_hash, ts) VALUES ($1, $2, $3, $4, now())` + end) 186 if err != nil { 187 return err 188 } 189 insertValue = make(map[csql.ValueType]*sql.Stmt) 190 } 191 _, err := insertQuad.Exec(dirs...) 192 err = convInsertError(err) 193 if err != nil { 194 if _, ok := err.(*graph.DeltaError); !ok { 195 clog.Errorf("couldn't exec INSERT statement: %v", err) 196 } 197 return err 198 } 199 } else { 200 panic("unexpected quad delete") 201 } 202 } 203 return nil 204 }