github.com/wlan0/docker@v1.5.0/pkg/graphdb/graphdb.go (about) 1 package graphdb 2 3 import ( 4 "database/sql" 5 "fmt" 6 "path" 7 "strings" 8 "sync" 9 ) 10 11 const ( 12 createEntityTable = ` 13 CREATE TABLE IF NOT EXISTS entity ( 14 id text NOT NULL PRIMARY KEY 15 );` 16 17 createEdgeTable = ` 18 CREATE TABLE IF NOT EXISTS edge ( 19 "entity_id" text NOT NULL, 20 "parent_id" text NULL, 21 "name" text NOT NULL, 22 CONSTRAINT "parent_fk" FOREIGN KEY ("parent_id") REFERENCES "entity" ("id"), 23 CONSTRAINT "entity_fk" FOREIGN KEY ("entity_id") REFERENCES "entity" ("id") 24 ); 25 ` 26 27 createEdgeIndices = ` 28 CREATE UNIQUE INDEX IF NOT EXISTS "name_parent_ix" ON "edge" (parent_id, name); 29 ` 30 ) 31 32 // Entity with a unique id 33 type Entity struct { 34 id string 35 } 36 37 // An Edge connects two entities together 38 type Edge struct { 39 EntityID string 40 Name string 41 ParentID string 42 } 43 44 type Entities map[string]*Entity 45 type Edges []*Edge 46 47 type WalkFunc func(fullPath string, entity *Entity) error 48 49 // Graph database for storing entities and their relationships 50 type Database struct { 51 conn *sql.DB 52 mux sync.RWMutex 53 } 54 55 func IsNonUniqueNameError(err error) bool { 56 str := err.Error() 57 // sqlite 3.7.17-1ubuntu1 returns: 58 // Set failure: Abort due to constraint violation: columns parent_id, name are not unique 59 if strings.HasSuffix(str, "name are not unique") { 60 return true 61 } 62 // sqlite-3.8.3-1.fc20 returns: 63 // Set failure: Abort due to constraint violation: UNIQUE constraint failed: edge.parent_id, edge.name 64 if strings.Contains(str, "UNIQUE constraint failed") && strings.Contains(str, "edge.name") { 65 return true 66 } 67 // sqlite-3.6.20-1.el6 returns: 68 // Set failure: Abort due to constraint violation: constraint failed 69 if strings.HasSuffix(str, "constraint failed") { 70 return true 71 } 72 return false 73 } 74 75 // Create a new graph database initialized with a root entity 76 func NewDatabase(conn *sql.DB) (*Database, error) { 77 if conn == nil { 78 return nil, fmt.Errorf("Database connection cannot be nil") 79 } 80 db := &Database{conn: conn} 81 82 // Create root entities 83 tx, err := conn.Begin() 84 if err != nil { 85 return nil, err 86 } 87 88 if _, err := tx.Exec(createEntityTable); err != nil { 89 return nil, err 90 } 91 if _, err := tx.Exec(createEdgeTable); err != nil { 92 return nil, err 93 } 94 if _, err := tx.Exec(createEdgeIndices); err != nil { 95 return nil, err 96 } 97 98 if _, err := tx.Exec("DELETE FROM entity where id = ?", "0"); err != nil { 99 tx.Rollback() 100 return nil, err 101 } 102 103 if _, err := tx.Exec("INSERT INTO entity (id) VALUES (?);", "0"); err != nil { 104 tx.Rollback() 105 return nil, err 106 } 107 108 if _, err := tx.Exec("DELETE FROM edge where entity_id=? and name=?", "0", "/"); err != nil { 109 tx.Rollback() 110 return nil, err 111 } 112 113 if _, err := tx.Exec("INSERT INTO edge (entity_id, name) VALUES(?,?);", "0", "/"); err != nil { 114 tx.Rollback() 115 return nil, err 116 } 117 118 if err := tx.Commit(); err != nil { 119 return nil, err 120 } 121 122 return db, nil 123 } 124 125 // Close the underlying connection to the database 126 func (db *Database) Close() error { 127 return db.conn.Close() 128 } 129 130 // Set the entity id for a given path 131 func (db *Database) Set(fullPath, id string) (*Entity, error) { 132 db.mux.Lock() 133 defer db.mux.Unlock() 134 135 tx, err := db.conn.Begin() 136 if err != nil { 137 return nil, err 138 } 139 140 var entityID string 141 if err := tx.QueryRow("SELECT id FROM entity WHERE id = ?;", id).Scan(&entityID); err != nil { 142 if err == sql.ErrNoRows { 143 if _, err := tx.Exec("INSERT INTO entity (id) VALUES(?);", id); err != nil { 144 tx.Rollback() 145 return nil, err 146 } 147 } else { 148 tx.Rollback() 149 return nil, err 150 } 151 } 152 e := &Entity{id} 153 154 parentPath, name := splitPath(fullPath) 155 if err := db.setEdge(parentPath, name, e, tx); err != nil { 156 tx.Rollback() 157 return nil, err 158 } 159 160 if err := tx.Commit(); err != nil { 161 return nil, err 162 } 163 return e, nil 164 } 165 166 // Return true if a name already exists in the database 167 func (db *Database) Exists(name string) bool { 168 db.mux.RLock() 169 defer db.mux.RUnlock() 170 171 e, err := db.get(name) 172 if err != nil { 173 return false 174 } 175 return e != nil 176 } 177 178 func (db *Database) setEdge(parentPath, name string, e *Entity, tx *sql.Tx) error { 179 parent, err := db.get(parentPath) 180 if err != nil { 181 return err 182 } 183 if parent.id == e.id { 184 return fmt.Errorf("Cannot set self as child") 185 } 186 187 if _, err := tx.Exec("INSERT INTO edge (parent_id, name, entity_id) VALUES (?,?,?);", parent.id, name, e.id); err != nil { 188 return err 189 } 190 return nil 191 } 192 193 // Return the root "/" entity for the database 194 func (db *Database) RootEntity() *Entity { 195 return &Entity{ 196 id: "0", 197 } 198 } 199 200 // Return the entity for a given path 201 func (db *Database) Get(name string) *Entity { 202 db.mux.RLock() 203 defer db.mux.RUnlock() 204 205 e, err := db.get(name) 206 if err != nil { 207 return nil 208 } 209 return e 210 } 211 212 func (db *Database) get(name string) (*Entity, error) { 213 e := db.RootEntity() 214 // We always know the root name so return it if 215 // it is requested 216 if name == "/" { 217 return e, nil 218 } 219 220 parts := split(name) 221 for i := 1; i < len(parts); i++ { 222 p := parts[i] 223 if p == "" { 224 continue 225 } 226 227 next := db.child(e, p) 228 if next == nil { 229 return nil, fmt.Errorf("Cannot find child for %s", name) 230 } 231 e = next 232 } 233 return e, nil 234 235 } 236 237 // List all entities by from the name 238 // The key will be the full path of the entity 239 func (db *Database) List(name string, depth int) Entities { 240 db.mux.RLock() 241 defer db.mux.RUnlock() 242 243 out := Entities{} 244 e, err := db.get(name) 245 if err != nil { 246 return out 247 } 248 249 children, err := db.children(e, name, depth, nil) 250 if err != nil { 251 return out 252 } 253 254 for _, c := range children { 255 out[c.FullPath] = c.Entity 256 } 257 return out 258 } 259 260 // Walk through the child graph of an entity, calling walkFunc for each child entity. 261 // It is safe for walkFunc to call graph functions. 262 func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error { 263 children, err := db.Children(name, depth) 264 if err != nil { 265 return err 266 } 267 268 // Note: the database lock must not be held while calling walkFunc 269 for _, c := range children { 270 if err := walkFunc(c.FullPath, c.Entity); err != nil { 271 return err 272 } 273 } 274 return nil 275 } 276 277 // Return the children of the specified entity 278 func (db *Database) Children(name string, depth int) ([]WalkMeta, error) { 279 db.mux.RLock() 280 defer db.mux.RUnlock() 281 282 e, err := db.get(name) 283 if err != nil { 284 return nil, err 285 } 286 287 return db.children(e, name, depth, nil) 288 } 289 290 // Return the parents of a specified entity 291 func (db *Database) Parents(name string) ([]string, error) { 292 db.mux.RLock() 293 defer db.mux.RUnlock() 294 295 e, err := db.get(name) 296 if err != nil { 297 return nil, err 298 } 299 return db.parents(e) 300 } 301 302 // Return the refrence count for a specified id 303 func (db *Database) Refs(id string) int { 304 db.mux.RLock() 305 defer db.mux.RUnlock() 306 307 var count int 308 if err := db.conn.QueryRow("SELECT COUNT(*) FROM edge WHERE entity_id = ?;", id).Scan(&count); err != nil { 309 return 0 310 } 311 return count 312 } 313 314 // Return all the id's path references 315 func (db *Database) RefPaths(id string) Edges { 316 db.mux.RLock() 317 defer db.mux.RUnlock() 318 319 refs := Edges{} 320 321 rows, err := db.conn.Query("SELECT name, parent_id FROM edge WHERE entity_id = ?;", id) 322 if err != nil { 323 return refs 324 } 325 defer rows.Close() 326 327 for rows.Next() { 328 var name string 329 var parentID string 330 if err := rows.Scan(&name, &parentID); err != nil { 331 return refs 332 } 333 refs = append(refs, &Edge{ 334 EntityID: id, 335 Name: name, 336 ParentID: parentID, 337 }) 338 } 339 return refs 340 } 341 342 // Delete the reference to an entity at a given path 343 func (db *Database) Delete(name string) error { 344 db.mux.Lock() 345 defer db.mux.Unlock() 346 347 if name == "/" { 348 return fmt.Errorf("Cannot delete root entity") 349 } 350 351 parentPath, n := splitPath(name) 352 parent, err := db.get(parentPath) 353 if err != nil { 354 return err 355 } 356 357 if _, err := db.conn.Exec("DELETE FROM edge WHERE parent_id = ? AND name = ?;", parent.id, n); err != nil { 358 return err 359 } 360 return nil 361 } 362 363 // Remove the entity with the specified id 364 // Walk the graph to make sure all references to the entity 365 // are removed and return the number of references removed 366 func (db *Database) Purge(id string) (int, error) { 367 db.mux.Lock() 368 defer db.mux.Unlock() 369 370 tx, err := db.conn.Begin() 371 if err != nil { 372 return -1, err 373 } 374 375 // Delete all edges 376 rows, err := tx.Exec("DELETE FROM edge WHERE entity_id = ?;", id) 377 if err != nil { 378 tx.Rollback() 379 return -1, err 380 } 381 382 changes, err := rows.RowsAffected() 383 if err != nil { 384 return -1, err 385 } 386 387 // Delete entity 388 if _, err := tx.Exec("DELETE FROM entity where id = ?;", id); err != nil { 389 tx.Rollback() 390 return -1, err 391 } 392 393 if err := tx.Commit(); err != nil { 394 return -1, err 395 } 396 397 return int(changes), nil 398 } 399 400 // Rename an edge for a given path 401 func (db *Database) Rename(currentName, newName string) error { 402 db.mux.Lock() 403 defer db.mux.Unlock() 404 405 parentPath, name := splitPath(currentName) 406 newParentPath, newEdgeName := splitPath(newName) 407 408 if parentPath != newParentPath { 409 return fmt.Errorf("Cannot rename when root paths do not match %s != %s", parentPath, newParentPath) 410 } 411 412 parent, err := db.get(parentPath) 413 if err != nil { 414 return err 415 } 416 417 rows, err := db.conn.Exec("UPDATE edge SET name = ? WHERE parent_id = ? AND name = ?;", newEdgeName, parent.id, name) 418 if err != nil { 419 return err 420 } 421 i, err := rows.RowsAffected() 422 if err != nil { 423 return err 424 } 425 if i == 0 { 426 return fmt.Errorf("Cannot locate edge for %s %s", parent.id, name) 427 } 428 return nil 429 } 430 431 type WalkMeta struct { 432 Parent *Entity 433 Entity *Entity 434 FullPath string 435 Edge *Edge 436 } 437 438 func (db *Database) children(e *Entity, name string, depth int, entities []WalkMeta) ([]WalkMeta, error) { 439 if e == nil { 440 return entities, nil 441 } 442 443 rows, err := db.conn.Query("SELECT entity_id, name FROM edge where parent_id = ?;", e.id) 444 if err != nil { 445 return nil, err 446 } 447 defer rows.Close() 448 449 for rows.Next() { 450 var entityID, entityName string 451 if err := rows.Scan(&entityID, &entityName); err != nil { 452 return nil, err 453 } 454 child := &Entity{entityID} 455 edge := &Edge{ 456 ParentID: e.id, 457 Name: entityName, 458 EntityID: child.id, 459 } 460 461 meta := WalkMeta{ 462 Parent: e, 463 Entity: child, 464 FullPath: path.Join(name, edge.Name), 465 Edge: edge, 466 } 467 468 entities = append(entities, meta) 469 470 if depth != 0 { 471 nDepth := depth 472 if depth != -1 { 473 nDepth -= 1 474 } 475 entities, err = db.children(child, meta.FullPath, nDepth, entities) 476 if err != nil { 477 return nil, err 478 } 479 } 480 } 481 482 return entities, nil 483 } 484 485 func (db *Database) parents(e *Entity) (parents []string, err error) { 486 if e == nil { 487 return parents, nil 488 } 489 490 rows, err := db.conn.Query("SELECT parent_id FROM edge where entity_id = ?;", e.id) 491 if err != nil { 492 return nil, err 493 } 494 defer rows.Close() 495 496 for rows.Next() { 497 var parentID string 498 if err := rows.Scan(&parentID); err != nil { 499 return nil, err 500 } 501 parents = append(parents, parentID) 502 } 503 504 return parents, nil 505 } 506 507 // Return the entity based on the parent path and name 508 func (db *Database) child(parent *Entity, name string) *Entity { 509 var id string 510 if err := db.conn.QueryRow("SELECT entity_id FROM edge WHERE parent_id = ? AND name = ?;", parent.id, name).Scan(&id); err != nil { 511 return nil 512 } 513 return &Entity{id} 514 } 515 516 // Return the id used to reference this entity 517 func (e *Entity) ID() string { 518 return e.id 519 } 520 521 // Return the paths sorted by depth 522 func (e Entities) Paths() []string { 523 out := make([]string, len(e)) 524 var i int 525 for k := range e { 526 out[i] = k 527 i++ 528 } 529 sortByDepth(out) 530 531 return out 532 }