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