graphdb.go 11 KB

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