github.com/cayleygraph/cayley@v0.7.7/graph/quadwriter.go (about) 1 // Copyright 2014 The Cayley Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package graph 16 17 // Defines the interface for consistent replication of a graph instance. 18 // 19 // Separate from the backend, this dictates how individual quads get 20 // identified and replicated consistently across (potentially) multiple 21 // instances. The simplest case is to keep an append-only log of quad 22 // changes. 23 24 import ( 25 "context" 26 "errors" 27 "io" 28 29 "github.com/cayleygraph/quad" 30 ) 31 32 type Procedure int8 33 34 func (p Procedure) String() string { 35 switch p { 36 case +1: 37 return "add" 38 case -1: 39 return "delete" 40 default: 41 return "invalid" 42 } 43 } 44 45 // The different types of actions a transaction can do. 46 const ( 47 Add Procedure = +1 48 Delete Procedure = -1 49 ) 50 51 type Delta struct { 52 Quad quad.Quad 53 Action Procedure 54 } 55 56 // Unwrap returns an original QuadStore value if it was wrapped by Handle. 57 // This prevents shadowing of optional interface implementations. 58 func Unwrap(qs QuadStore) QuadStore { 59 if h, ok := qs.(*Handle); ok { 60 return h.QuadStore 61 } 62 return qs 63 } 64 65 type Handle struct { 66 QuadStore 67 QuadWriter 68 } 69 70 type IgnoreOpts struct { 71 IgnoreDup, IgnoreMissing bool 72 } 73 74 func (h *Handle) Close() error { 75 err := h.QuadWriter.Close() 76 h.QuadStore.Close() 77 return err 78 } 79 80 var ( 81 ErrQuadExists = errors.New("quad exists") 82 ErrQuadNotExist = errors.New("quad does not exist") 83 ErrInvalidAction = errors.New("invalid action") 84 ErrNodeNotExists = errors.New("node does not exist") 85 ) 86 87 // DeltaError records an error and the delta that caused it. 88 type DeltaError struct { 89 Delta Delta 90 Err error 91 } 92 93 func (e *DeltaError) Error() string { 94 if !e.Delta.Quad.IsValid() { 95 return e.Err.Error() 96 } 97 return e.Delta.Action.String() + " " + e.Delta.Quad.String() + ": " + e.Err.Error() 98 } 99 100 // IsQuadExist returns whether an error is a DeltaError 101 // with the Err field equal to ErrQuadExists. 102 func IsQuadExist(err error) bool { 103 if err == ErrQuadExists { 104 return true 105 } 106 de, ok := err.(*DeltaError) 107 return ok && de.Err == ErrQuadExists 108 } 109 110 // IsQuadNotExist returns whether an error is a DeltaError 111 // with the Err field equal to ErrQuadNotExist. 112 func IsQuadNotExist(err error) bool { 113 if err == ErrQuadNotExist { 114 return true 115 } 116 de, ok := err.(*DeltaError) 117 return ok && de.Err == ErrQuadNotExist 118 } 119 120 // IsInvalidAction returns whether an error is a DeltaError 121 // with the Err field equal to ErrInvalidAction. 122 func IsInvalidAction(err error) bool { 123 if err == ErrInvalidAction { 124 return true 125 } 126 de, ok := err.(*DeltaError) 127 return ok && de.Err == ErrInvalidAction 128 } 129 130 var ( 131 // IgnoreDuplicates specifies whether duplicate quads 132 // cause an error during loading or are ignored. 133 IgnoreDuplicates = true 134 135 // IgnoreMissing specifies whether missing quads 136 // cause an error during deletion or are ignored. 137 IgnoreMissing = false 138 ) 139 140 type QuadWriter interface { 141 // AddQuad adds a quad to the store. 142 AddQuad(quad.Quad) error 143 144 // TODO(barakmich): Deprecate in favor of transaction. 145 // AddQuadSet adds a set of quads to the store, atomically if possible. 146 AddQuadSet([]quad.Quad) error 147 148 // RemoveQuad removes a quad matching the given one from the database, 149 // if it exists. Does nothing otherwise. 150 RemoveQuad(quad.Quad) error 151 152 // ApplyTransaction applies a set of quad changes. 153 ApplyTransaction(*Transaction) error 154 155 // RemoveNode removes all quads which have the given node as subject, predicate, object, or label. 156 // 157 // It returns ErrNodeNotExists if node is missing. 158 RemoveNode(quad.Value) error 159 160 // Close cleans up replication and closes the writing aspect of the database. 161 Close() error 162 } 163 164 type NewQuadWriterFunc func(QuadStore, Options) (QuadWriter, error) 165 166 var writerRegistry = make(map[string]NewQuadWriterFunc) 167 168 func RegisterWriter(name string, newFunc NewQuadWriterFunc) { 169 if _, found := writerRegistry[name]; found { 170 panic("already registered QuadWriter " + name) 171 } 172 writerRegistry[name] = newFunc 173 } 174 175 func NewQuadWriter(name string, qs QuadStore, opts Options) (QuadWriter, error) { 176 newFunc, hasNew := writerRegistry[name] 177 if !hasNew { 178 return nil, errors.New("replication: name '" + name + "' is not registered") 179 } 180 return newFunc(qs, opts) 181 } 182 183 func WriterMethods() []string { 184 t := make([]string, 0, len(writerRegistry)) 185 for n := range writerRegistry { 186 t = append(t, n) 187 } 188 return t 189 } 190 191 type BatchWriter interface { 192 quad.WriteCloser 193 Flush() error 194 } 195 196 // NewWriter creates a quad writer for a given QuadStore. 197 // 198 // Caller must call Flush or Close to flush an internal buffer. 199 func NewWriter(qs QuadWriter) BatchWriter { 200 return &batchWriter{qs: qs} 201 } 202 203 type batchWriter struct { 204 qs QuadWriter 205 buf []quad.Quad 206 } 207 208 func (w *batchWriter) flushBuffer(force bool) error { 209 if !force && len(w.buf) < quad.DefaultBatch { 210 return nil 211 } 212 _, err := w.WriteQuads(w.buf) 213 w.buf = w.buf[:0] 214 return err 215 } 216 217 func (w *batchWriter) WriteQuad(q quad.Quad) error { 218 if err := w.flushBuffer(false); err != nil { 219 return err 220 } 221 w.buf = append(w.buf, q) 222 return nil 223 } 224 func (w *batchWriter) WriteQuads(quads []quad.Quad) (int, error) { 225 if err := w.qs.AddQuadSet(quads); err != nil { 226 return 0, err 227 } 228 return len(quads), nil 229 } 230 func (w *batchWriter) Flush() error { 231 return w.flushBuffer(true) 232 } 233 func (w *batchWriter) Close() error { 234 return w.Flush() 235 } 236 237 // NewTxWriter creates a writer that applies a given procedures for all quads in stream. 238 // If procedure is zero, Add operation will be used. 239 func NewTxWriter(tx *Transaction, p Procedure) quad.Writer { 240 if p == 0 { 241 p = Add 242 } 243 return &txWriter{tx: tx, p: p} 244 } 245 246 type txWriter struct { 247 tx *Transaction 248 p Procedure 249 } 250 251 func (w *txWriter) WriteQuad(q quad.Quad) error { 252 switch w.p { 253 case Add: 254 w.tx.AddQuad(q) 255 case Delete: 256 w.tx.RemoveQuad(q) 257 default: 258 return ErrInvalidAction 259 } 260 return nil 261 } 262 263 func (w *txWriter) WriteQuads(buf []quad.Quad) (int, error) { 264 for i, q := range buf { 265 if err := w.WriteQuad(q); err != nil { 266 return i, err 267 } 268 } 269 return len(buf), nil 270 } 271 272 // NewRemover creates a quad writer for a given QuadStore which removes quads instead of adding them. 273 func NewRemover(qs QuadWriter) BatchWriter { 274 return &removeWriter{qs: qs} 275 } 276 277 type removeWriter struct { 278 qs QuadWriter 279 } 280 281 func (w *removeWriter) WriteQuad(q quad.Quad) error { 282 return w.qs.RemoveQuad(q) 283 } 284 func (w *removeWriter) WriteQuads(quads []quad.Quad) (int, error) { 285 tx := NewTransaction() 286 for _, q := range quads { 287 tx.RemoveQuad(q) 288 } 289 if err := w.qs.ApplyTransaction(tx); err != nil { 290 return 0, err 291 } 292 return len(quads), nil 293 } 294 func (w *removeWriter) Flush() error { 295 return nil // TODO: batch deletes automatically 296 } 297 func (w *removeWriter) Close() error { return nil } 298 299 // NewResultReader creates a quad reader for a given QuadStore. 300 func NewQuadStoreReader(qs QuadStore) quad.ReadSkipCloser { 301 return NewResultReader(qs, nil) 302 } 303 304 // NewResultReader creates a quad reader for a given QuadStore and iterator. 305 // If iterator is nil QuadsAllIterator will be used. 306 // 307 // Only quads returned by iterator's Result will be used. 308 // 309 // Iterator will be closed with the reader. 310 func NewResultReader(qs QuadStore, it Iterator) quad.ReadSkipCloser { 311 if it == nil { 312 it = qs.QuadsAllIterator() 313 } 314 return &quadReader{qs: qs, it: it} 315 } 316 317 type quadReader struct { 318 qs QuadStore 319 it Iterator 320 } 321 322 func (r *quadReader) ReadQuad() (quad.Quad, error) { 323 if r.it.Next(context.TODO()) { 324 return r.qs.Quad(r.it.Result()), nil 325 } 326 err := r.it.Err() 327 if err == nil { 328 err = io.EOF 329 } 330 return quad.Quad{}, err 331 } 332 func (r *quadReader) SkipQuad() error { 333 if r.it.Next(context.TODO()) { 334 return nil 335 } 336 if err := r.it.Err(); err != nil { 337 return err 338 } 339 return io.EOF 340 } 341 func (r *quadReader) Close() error { return r.it.Close() }