github.com/guilhermebr/docker@v1.4.2-0.20150428121140-67da055cebca/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 changes, err := rows.RowsAffected() 382 if err != nil { 383 return -1, err 384 } 385 386 // Clear who's using this id as parent 387 refs, err := tx.Exec("DELETE FROM edge WHERE parent_id = ?;", id) 388 if err != nil { 389 tx.Rollback() 390 return -1, err 391 } 392 refsCount, err := refs.RowsAffected() 393 if err != nil { 394 return -1, err 395 } 396 397 // Delete entity 398 if _, err := tx.Exec("DELETE FROM entity where id = ?;", id); err != nil { 399 tx.Rollback() 400 return -1, err 401 } 402 403 if err := tx.Commit(); err != nil { 404 return -1, err 405 } 406 407 return int(changes + refsCount), nil 408 } 409 410 // Rename an edge for a given path 411 func (db *Database) Rename(currentName, newName string) error { 412 db.mux.Lock() 413 defer db.mux.Unlock() 414 415 parentPath, name := splitPath(currentName) 416 newParentPath, newEdgeName := splitPath(newName) 417 418 if parentPath != newParentPath { 419 return fmt.Errorf("Cannot rename when root paths do not match %s != %s", parentPath, newParentPath) 420 } 421 422 parent, err := db.get(parentPath) 423 if err != nil { 424 return err 425 } 426 427 rows, err := db.conn.Exec("UPDATE edge SET name = ? WHERE parent_id = ? AND name = ?;", newEdgeName, parent.id, name) 428 if err != nil { 429 return err 430 } 431 i, err := rows.RowsAffected() 432 if err != nil { 433 return err 434 } 435 if i == 0 { 436 return fmt.Errorf("Cannot locate edge for %s %s", parent.id, name) 437 } 438 return nil 439 } 440 441 type WalkMeta struct { 442 Parent *Entity 443 Entity *Entity 444 FullPath string 445 Edge *Edge 446 } 447 448 func (db *Database) children(e *Entity, name string, depth int, entities []WalkMeta) ([]WalkMeta, error) { 449 if e == nil { 450 return entities, nil 451 } 452 453 rows, err := db.conn.Query("SELECT entity_id, name FROM edge where parent_id = ?;", e.id) 454 if err != nil { 455 return nil, err 456 } 457 defer rows.Close() 458 459 for rows.Next() { 460 var entityID, entityName string 461 if err := rows.Scan(&entityID, &entityName); err != nil { 462 return nil, err 463 } 464 child := &Entity{entityID} 465 edge := &Edge{ 466 ParentID: e.id, 467 Name: entityName, 468 EntityID: child.id, 469 } 470 471 meta := WalkMeta{ 472 Parent: e, 473 Entity: child, 474 FullPath: path.Join(name, edge.Name), 475 Edge: edge, 476 } 477 478 entities = append(entities, meta) 479 480 if depth != 0 { 481 nDepth := depth 482 if depth != -1 { 483 nDepth -= 1 484 } 485 entities, err = db.children(child, meta.FullPath, nDepth, entities) 486 if err != nil { 487 return nil, err 488 } 489 } 490 } 491 492 return entities, nil 493 } 494 495 func (db *Database) parents(e *Entity) (parents []string, err error) { 496 if e == nil { 497 return parents, nil 498 } 499 500 rows, err := db.conn.Query("SELECT parent_id FROM edge where entity_id = ?;", e.id) 501 if err != nil { 502 return nil, err 503 } 504 defer rows.Close() 505 506 for rows.Next() { 507 var parentID string 508 if err := rows.Scan(&parentID); err != nil { 509 return nil, err 510 } 511 parents = append(parents, parentID) 512 } 513 514 return parents, nil 515 } 516 517 // Return the entity based on the parent path and name 518 func (db *Database) child(parent *Entity, name string) *Entity { 519 var id string 520 if err := db.conn.QueryRow("SELECT entity_id FROM edge WHERE parent_id = ? AND name = ?;", parent.id, name).Scan(&id); err != nil { 521 return nil 522 } 523 return &Entity{id} 524 } 525 526 // Return the id used to reference this entity 527 func (e *Entity) ID() string { 528 return e.id 529 } 530 531 // Return the paths sorted by depth 532 func (e Entities) Paths() []string { 533 out := make([]string, len(e)) 534 var i int 535 for k := range e { 536 out[i] = k 537 i++ 538 } 539 sortByDepth(out) 540 541 return out 542 }