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