graphdb.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  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. return false
  58. }
  59. // Create a new graph database initialized with a root entity
  60. func NewDatabase(conn *sql.DB, init bool) (*Database, error) {
  61. if conn == nil {
  62. return nil, fmt.Errorf("Database connection cannot be nil")
  63. }
  64. db := &Database{conn: conn}
  65. if init {
  66. if _, err := conn.Exec(createEntityTable); err != nil {
  67. return nil, err
  68. }
  69. if _, err := conn.Exec(createEdgeTable); err != nil {
  70. return nil, err
  71. }
  72. if _, err := conn.Exec(createEdgeIndices); err != nil {
  73. return nil, err
  74. }
  75. rollback := func() {
  76. conn.Exec("ROLLBACK")
  77. }
  78. // Create root entities
  79. if _, err := conn.Exec("BEGIN"); err != nil {
  80. return nil, err
  81. }
  82. if _, err := conn.Exec("INSERT INTO entity (id) VALUES (?);", "0"); err != nil {
  83. rollback()
  84. return nil, err
  85. }
  86. if _, err := conn.Exec("INSERT INTO edge (entity_id, name) VALUES(?,?);", "0", "/"); err != nil {
  87. rollback()
  88. return nil, err
  89. }
  90. if _, err := conn.Exec("COMMIT"); err != nil {
  91. return nil, err
  92. }
  93. }
  94. return db, nil
  95. }
  96. // Close the underlying connection to the database
  97. func (db *Database) Close() error {
  98. return db.conn.Close()
  99. }
  100. // Set the entity id for a given path
  101. func (db *Database) Set(fullPath, id string) (*Entity, error) {
  102. db.mux.Lock()
  103. defer db.mux.Unlock()
  104. rollback := func() {
  105. db.conn.Exec("ROLLBACK")
  106. }
  107. if _, err := db.conn.Exec("BEGIN EXCLUSIVE"); err != nil {
  108. return nil, err
  109. }
  110. var entityId string
  111. if err := db.conn.QueryRow("SELECT id FROM entity WHERE id = ?;", id).Scan(&entityId); err != nil {
  112. if err == sql.ErrNoRows {
  113. if _, err := db.conn.Exec("INSERT INTO entity (id) VALUES(?);", id); err != nil {
  114. rollback()
  115. return nil, err
  116. }
  117. } else {
  118. rollback()
  119. return nil, err
  120. }
  121. }
  122. e := &Entity{id}
  123. parentPath, name := splitPath(fullPath)
  124. if err := db.setEdge(parentPath, name, e); err != nil {
  125. rollback()
  126. return nil, err
  127. }
  128. if _, err := db.conn.Exec("COMMIT"); err != nil {
  129. return nil, err
  130. }
  131. return e, nil
  132. }
  133. // Return true if a name already exists in the database
  134. func (db *Database) Exists(name string) bool {
  135. db.mux.RLock()
  136. defer db.mux.RUnlock()
  137. e, err := db.get(name)
  138. if err != nil {
  139. return false
  140. }
  141. return e != nil
  142. }
  143. func (db *Database) setEdge(parentPath, name string, e *Entity) error {
  144. parent, err := db.get(parentPath)
  145. if err != nil {
  146. return err
  147. }
  148. if parent.id == e.id {
  149. return fmt.Errorf("Cannot set self as child")
  150. }
  151. if _, err := db.conn.Exec("INSERT INTO edge (parent_id, name, entity_id) VALUES (?,?,?);", parent.id, name, e.id); err != nil {
  152. return err
  153. }
  154. return nil
  155. }
  156. // Return the root "/" entity for the database
  157. func (db *Database) RootEntity() *Entity {
  158. return &Entity{
  159. id: "0",
  160. }
  161. }
  162. // Return the entity for a given path
  163. func (db *Database) Get(name string) *Entity {
  164. db.mux.RLock()
  165. defer db.mux.RUnlock()
  166. e, err := db.get(name)
  167. if err != nil {
  168. return nil
  169. }
  170. return e
  171. }
  172. func (db *Database) get(name string) (*Entity, error) {
  173. e := db.RootEntity()
  174. // We always know the root name so return it if
  175. // it is requested
  176. if name == "/" {
  177. return e, nil
  178. }
  179. parts := split(name)
  180. for i := 1; i < len(parts); i++ {
  181. p := parts[i]
  182. if p == "" {
  183. continue
  184. }
  185. next := db.child(e, p)
  186. if next == nil {
  187. return nil, fmt.Errorf("Cannot find child for %s", name)
  188. }
  189. e = next
  190. }
  191. return e, nil
  192. }
  193. // List all entities by from the name
  194. // The key will be the full path of the entity
  195. func (db *Database) List(name string, depth int) Entities {
  196. db.mux.RLock()
  197. defer db.mux.RUnlock()
  198. out := Entities{}
  199. e, err := db.get(name)
  200. if err != nil {
  201. return out
  202. }
  203. children, err := db.children(e, name, depth, nil)
  204. if err != nil {
  205. return out
  206. }
  207. for _, c := range children {
  208. out[c.FullPath] = c.Entity
  209. }
  210. return out
  211. }
  212. // Walk through the child graph of an entity, calling walkFunc for each child entity.
  213. // It is safe for walkFunc to call graph functions.
  214. func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error {
  215. children, err := db.Children(name, depth)
  216. if err != nil {
  217. return err
  218. }
  219. // Note: the database lock must not be held while calling walkFunc
  220. for _, c := range children {
  221. if err := walkFunc(c.FullPath, c.Entity); err != nil {
  222. return err
  223. }
  224. }
  225. return nil
  226. }
  227. // Return the children of the specified entity
  228. func (db *Database) Children(name string, depth int) ([]WalkMeta, error) {
  229. db.mux.RLock()
  230. defer db.mux.RUnlock()
  231. e, err := db.get(name)
  232. if err != nil {
  233. return nil, err
  234. }
  235. return db.children(e, name, depth, nil)
  236. }
  237. // Return the refrence count for a specified id
  238. func (db *Database) Refs(id string) int {
  239. db.mux.RLock()
  240. defer db.mux.RUnlock()
  241. var count int
  242. if err := db.conn.QueryRow("SELECT COUNT(*) FROM edge WHERE entity_id = ?;", id).Scan(&count); err != nil {
  243. return 0
  244. }
  245. return count
  246. }
  247. // Return all the id's path references
  248. func (db *Database) RefPaths(id string) Edges {
  249. db.mux.RLock()
  250. defer db.mux.RUnlock()
  251. refs := Edges{}
  252. rows, err := db.conn.Query("SELECT name, parent_id FROM edge WHERE entity_id = ?;", id)
  253. if err != nil {
  254. return refs
  255. }
  256. defer rows.Close()
  257. for rows.Next() {
  258. var name string
  259. var parentId string
  260. if err := rows.Scan(&name, &parentId); err != nil {
  261. return refs
  262. }
  263. refs = append(refs, &Edge{
  264. EntityID: id,
  265. Name: name,
  266. ParentID: parentId,
  267. })
  268. }
  269. return refs
  270. }
  271. // Delete the reference to an entity at a given path
  272. func (db *Database) Delete(name string) error {
  273. db.mux.Lock()
  274. defer db.mux.Unlock()
  275. if name == "/" {
  276. return fmt.Errorf("Cannot delete root entity")
  277. }
  278. parentPath, n := splitPath(name)
  279. parent, err := db.get(parentPath)
  280. if err != nil {
  281. return err
  282. }
  283. if _, err := db.conn.Exec("DELETE FROM edge WHERE parent_id = ? AND name = ?;", parent.id, n); err != nil {
  284. return err
  285. }
  286. return nil
  287. }
  288. // Remove the entity with the specified id
  289. // Walk the graph to make sure all references to the entity
  290. // are removed and return the number of references removed
  291. func (db *Database) Purge(id string) (int, error) {
  292. db.mux.Lock()
  293. defer db.mux.Unlock()
  294. rollback := func() {
  295. db.conn.Exec("ROLLBACK")
  296. }
  297. if _, err := db.conn.Exec("BEGIN"); err != nil {
  298. return -1, err
  299. }
  300. // Delete all edges
  301. rows, err := db.conn.Exec("DELETE FROM edge WHERE entity_id = ?;", id)
  302. if err != nil {
  303. rollback()
  304. return -1, err
  305. }
  306. changes, err := rows.RowsAffected()
  307. if err != nil {
  308. return -1, err
  309. }
  310. // Delete entity
  311. if _, err := db.conn.Exec("DELETE FROM entity where id = ?;", id); err != nil {
  312. rollback()
  313. return -1, err
  314. }
  315. if _, err := db.conn.Exec("COMMIT"); err != nil {
  316. return -1, err
  317. }
  318. return int(changes), nil
  319. }
  320. // Rename an edge for a given path
  321. func (db *Database) Rename(currentName, newName string) error {
  322. db.mux.Lock()
  323. defer db.mux.Unlock()
  324. parentPath, name := splitPath(currentName)
  325. newParentPath, newEdgeName := splitPath(newName)
  326. if parentPath != newParentPath {
  327. return fmt.Errorf("Cannot rename when root paths do not match %s != %s", parentPath, newParentPath)
  328. }
  329. parent, err := db.get(parentPath)
  330. if err != nil {
  331. return err
  332. }
  333. rows, err := db.conn.Exec("UPDATE edge SET name = ? WHERE parent_id = ? AND name = ?;", newEdgeName, parent.id, name)
  334. if err != nil {
  335. return err
  336. }
  337. i, err := rows.RowsAffected()
  338. if err != nil {
  339. return err
  340. }
  341. if i == 0 {
  342. return fmt.Errorf("Cannot locate edge for %s %s", parent.id, name)
  343. }
  344. return nil
  345. }
  346. type WalkMeta struct {
  347. Parent *Entity
  348. Entity *Entity
  349. FullPath string
  350. Edge *Edge
  351. }
  352. func (db *Database) children(e *Entity, name string, depth int, entities []WalkMeta) ([]WalkMeta, error) {
  353. if e == nil {
  354. return entities, nil
  355. }
  356. rows, err := db.conn.Query("SELECT entity_id, name FROM edge where parent_id = ?;", e.id)
  357. if err != nil {
  358. return nil, err
  359. }
  360. defer rows.Close()
  361. for rows.Next() {
  362. var entityId, entityName string
  363. if err := rows.Scan(&entityId, &entityName); err != nil {
  364. return nil, err
  365. }
  366. child := &Entity{entityId}
  367. edge := &Edge{
  368. ParentID: e.id,
  369. Name: entityName,
  370. EntityID: child.id,
  371. }
  372. meta := WalkMeta{
  373. Parent: e,
  374. Entity: child,
  375. FullPath: path.Join(name, edge.Name),
  376. Edge: edge,
  377. }
  378. entities = append(entities, meta)
  379. if depth != 0 {
  380. nDepth := depth
  381. if depth != -1 {
  382. nDepth -= 1
  383. }
  384. entities, err = db.children(child, meta.FullPath, nDepth, entities)
  385. if err != nil {
  386. return nil, err
  387. }
  388. }
  389. }
  390. return entities, nil
  391. }
  392. // Return the entity based on the parent path and name
  393. func (db *Database) child(parent *Entity, name string) *Entity {
  394. var id string
  395. if err := db.conn.QueryRow("SELECT entity_id FROM edge WHERE parent_id = ? AND name = ?;", parent.id, name).Scan(&id); err != nil {
  396. return nil
  397. }
  398. return &Entity{id}
  399. }
  400. // Return the id used to reference this entity
  401. func (e *Entity) ID() string {
  402. return e.id
  403. }
  404. // Return the paths sorted by depth
  405. func (e Entities) Paths() []string {
  406. out := make([]string, len(e))
  407. var i int
  408. for k := range e {
  409. out[i] = k
  410. i++
  411. }
  412. sortByDepth(out)
  413. return out
  414. }