Browse code

Move utility package 'graphdb' to pkg/graphdb

Solomon Hykes authored on 2013/12/24 08:33:06
Showing 18 changed files
1 1
deleted file mode 100644
... ...
@@ -1 +0,0 @@
1
-Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
2 1
deleted file mode 100644
... ...
@@ -1,5 +0,0 @@
1
-package graphdb
2
-
3
-func NewSqliteConn(root string) (*Database, error) {
4
-	panic("Not implemented")
5
-}
6 1
deleted file mode 100644
... ...
@@ -1,23 +0,0 @@
1
-package graphdb
2
-
3
-import (
4
-	_ "code.google.com/p/gosqlite/sqlite3" // registers sqlite
5
-	"database/sql"
6
-	"os"
7
-)
8
-
9
-func NewSqliteConn(root string) (*Database, error) {
10
-	initDatabase := false
11
-	if _, err := os.Stat(root); err != nil {
12
-		if os.IsNotExist(err) {
13
-			initDatabase = true
14
-		} else {
15
-			return nil, err
16
-		}
17
-	}
18
-	conn, err := sql.Open("sqlite3", root)
19
-	if err != nil {
20
-		return nil, err
21
-	}
22
-	return NewDatabase(conn, initDatabase)
23
-}
24 1
deleted file mode 100644
... ...
@@ -1,473 +0,0 @@
1
-package graphdb
2
-
3
-import (
4
-	"database/sql"
5
-	"fmt"
6
-	"path"
7
-	"sync"
8
-)
9
-
10
-const (
11
-	createEntityTable = `
12
-    CREATE TABLE IF NOT EXISTS entity (
13
-        id text NOT NULL PRIMARY KEY
14
-    );`
15
-
16
-	createEdgeTable = `
17
-    CREATE TABLE IF NOT EXISTS edge (
18
-        "entity_id" text NOT NULL,
19
-        "parent_id" text NULL,
20
-        "name" text NOT NULL,
21
-        CONSTRAINT "parent_fk" FOREIGN KEY ("parent_id") REFERENCES "entity" ("id"),
22
-        CONSTRAINT "entity_fk" FOREIGN KEY ("entity_id") REFERENCES "entity" ("id")
23
-        );
24
-    `
25
-
26
-	createEdgeIndices = `
27
-    CREATE UNIQUE INDEX IF NOT EXISTS "name_parent_ix" ON "edge" (parent_id, name);
28
-    `
29
-)
30
-
31
-// Entity with a unique id
32
-type Entity struct {
33
-	id string
34
-}
35
-
36
-// An Edge connects two entities together
37
-type Edge struct {
38
-	EntityID string
39
-	Name     string
40
-	ParentID string
41
-}
42
-
43
-type Entities map[string]*Entity
44
-type Edges []*Edge
45
-
46
-type WalkFunc func(fullPath string, entity *Entity) error
47
-
48
-// Graph database for storing entities and their relationships
49
-type Database struct {
50
-	conn *sql.DB
51
-	mux  sync.RWMutex
52
-}
53
-
54
-// Create a new graph database initialized with a root entity
55
-func NewDatabase(conn *sql.DB, init bool) (*Database, error) {
56
-	if conn == nil {
57
-		return nil, fmt.Errorf("Database connection cannot be nil")
58
-	}
59
-	db := &Database{conn: conn}
60
-
61
-	if init {
62
-		if _, err := conn.Exec(createEntityTable); err != nil {
63
-			return nil, err
64
-		}
65
-		if _, err := conn.Exec(createEdgeTable); err != nil {
66
-			return nil, err
67
-		}
68
-		if _, err := conn.Exec(createEdgeIndices); err != nil {
69
-			return nil, err
70
-		}
71
-
72
-		rollback := func() {
73
-			conn.Exec("ROLLBACK")
74
-		}
75
-
76
-		// Create root entities
77
-		if _, err := conn.Exec("BEGIN"); err != nil {
78
-			return nil, err
79
-		}
80
-		if _, err := conn.Exec("INSERT INTO entity (id) VALUES (?);", "0"); err != nil {
81
-			rollback()
82
-			return nil, err
83
-		}
84
-
85
-		if _, err := conn.Exec("INSERT INTO edge (entity_id, name) VALUES(?,?);", "0", "/"); err != nil {
86
-			rollback()
87
-			return nil, err
88
-		}
89
-
90
-		if _, err := conn.Exec("COMMIT"); err != nil {
91
-			return nil, err
92
-		}
93
-	}
94
-	return db, nil
95
-}
96
-
97
-// Close the underlying connection to the database
98
-func (db *Database) Close() error {
99
-	return db.conn.Close()
100
-}
101
-
102
-// Set the entity id for a given path
103
-func (db *Database) Set(fullPath, id string) (*Entity, error) {
104
-	db.mux.Lock()
105
-	defer db.mux.Unlock()
106
-
107
-	rollback := func() {
108
-		db.conn.Exec("ROLLBACK")
109
-	}
110
-	if _, err := db.conn.Exec("BEGIN EXCLUSIVE"); err != nil {
111
-		return nil, err
112
-	}
113
-	var entityId string
114
-	if err := db.conn.QueryRow("SELECT id FROM entity WHERE id = ?;", id).Scan(&entityId); err != nil {
115
-		if err == sql.ErrNoRows {
116
-			if _, err := db.conn.Exec("INSERT INTO entity (id) VALUES(?);", id); err != nil {
117
-				rollback()
118
-				return nil, err
119
-			}
120
-		} else {
121
-			rollback()
122
-			return nil, err
123
-		}
124
-	}
125
-	e := &Entity{id}
126
-
127
-	parentPath, name := splitPath(fullPath)
128
-	if err := db.setEdge(parentPath, name, e); err != nil {
129
-		rollback()
130
-		return nil, err
131
-	}
132
-
133
-	if _, err := db.conn.Exec("COMMIT"); err != nil {
134
-		return nil, err
135
-	}
136
-	return e, nil
137
-}
138
-
139
-// Return true if a name already exists in the database
140
-func (db *Database) Exists(name string) bool {
141
-	db.mux.RLock()
142
-	defer db.mux.RUnlock()
143
-
144
-	e, err := db.get(name)
145
-	if err != nil {
146
-		return false
147
-	}
148
-	return e != nil
149
-}
150
-
151
-func (db *Database) setEdge(parentPath, name string, e *Entity) error {
152
-	parent, err := db.get(parentPath)
153
-	if err != nil {
154
-		return err
155
-	}
156
-	if parent.id == e.id {
157
-		return fmt.Errorf("Cannot set self as child")
158
-	}
159
-
160
-	if _, err := db.conn.Exec("INSERT INTO edge (parent_id, name, entity_id) VALUES (?,?,?);", parent.id, name, e.id); err != nil {
161
-		return err
162
-	}
163
-	return nil
164
-}
165
-
166
-// Return the root "/" entity for the database
167
-func (db *Database) RootEntity() *Entity {
168
-	return &Entity{
169
-		id: "0",
170
-	}
171
-}
172
-
173
-// Return the entity for a given path
174
-func (db *Database) Get(name string) *Entity {
175
-	db.mux.RLock()
176
-	defer db.mux.RUnlock()
177
-
178
-	e, err := db.get(name)
179
-	if err != nil {
180
-		return nil
181
-	}
182
-	return e
183
-}
184
-
185
-func (db *Database) get(name string) (*Entity, error) {
186
-	e := db.RootEntity()
187
-	// We always know the root name so return it if
188
-	// it is requested
189
-	if name == "/" {
190
-		return e, nil
191
-	}
192
-
193
-	parts := split(name)
194
-	for i := 1; i < len(parts); i++ {
195
-		p := parts[i]
196
-		if p == "" {
197
-			continue
198
-		}
199
-
200
-		next := db.child(e, p)
201
-		if next == nil {
202
-			return nil, fmt.Errorf("Cannot find child for %s", name)
203
-		}
204
-		e = next
205
-	}
206
-	return e, nil
207
-
208
-}
209
-
210
-// List all entities by from the name
211
-// The key will be the full path of the entity
212
-func (db *Database) List(name string, depth int) Entities {
213
-	db.mux.RLock()
214
-	defer db.mux.RUnlock()
215
-
216
-	out := Entities{}
217
-	e, err := db.get(name)
218
-	if err != nil {
219
-		return out
220
-	}
221
-
222
-	children, err := db.children(e, name, depth, nil)
223
-	if err != nil {
224
-		return out
225
-	}
226
-
227
-	for _, c := range children {
228
-		out[c.FullPath] = c.Entity
229
-	}
230
-	return out
231
-}
232
-
233
-// Walk through the child graph of an entity, calling walkFunc for each child entity.
234
-// It is safe for walkFunc to call graph functions.
235
-func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error {
236
-	children, err := db.Children(name, depth)
237
-	if err != nil {
238
-		return err
239
-	}
240
-
241
-	// Note: the database lock must not be held while calling walkFunc
242
-	for _, c := range children {
243
-		if err := walkFunc(c.FullPath, c.Entity); err != nil {
244
-			return err
245
-		}
246
-	}
247
-	return nil
248
-}
249
-
250
-// Return the children of the specified entity
251
-func (db *Database) Children(name string, depth int) ([]WalkMeta, error) {
252
-	db.mux.RLock()
253
-	defer db.mux.RUnlock()
254
-
255
-	e, err := db.get(name)
256
-	if err != nil {
257
-		return nil, err
258
-	}
259
-
260
-	return db.children(e, name, depth, nil)
261
-}
262
-
263
-// Return the refrence count for a specified id
264
-func (db *Database) Refs(id string) int {
265
-	db.mux.RLock()
266
-	defer db.mux.RUnlock()
267
-
268
-	var count int
269
-	if err := db.conn.QueryRow("SELECT COUNT(*) FROM edge WHERE entity_id = ?;", id).Scan(&count); err != nil {
270
-		return 0
271
-	}
272
-	return count
273
-}
274
-
275
-// Return all the id's path references
276
-func (db *Database) RefPaths(id string) Edges {
277
-	db.mux.RLock()
278
-	defer db.mux.RUnlock()
279
-
280
-	refs := Edges{}
281
-
282
-	rows, err := db.conn.Query("SELECT name, parent_id FROM edge WHERE entity_id = ?;", id)
283
-	if err != nil {
284
-		return refs
285
-	}
286
-	defer rows.Close()
287
-
288
-	for rows.Next() {
289
-		var name string
290
-		var parentId string
291
-		if err := rows.Scan(&name, &parentId); err != nil {
292
-			return refs
293
-		}
294
-		refs = append(refs, &Edge{
295
-			EntityID: id,
296
-			Name:     name,
297
-			ParentID: parentId,
298
-		})
299
-	}
300
-	return refs
301
-}
302
-
303
-// Delete the reference to an entity at a given path
304
-func (db *Database) Delete(name string) error {
305
-	db.mux.Lock()
306
-	defer db.mux.Unlock()
307
-
308
-	if name == "/" {
309
-		return fmt.Errorf("Cannot delete root entity")
310
-	}
311
-
312
-	parentPath, n := splitPath(name)
313
-	parent, err := db.get(parentPath)
314
-	if err != nil {
315
-		return err
316
-	}
317
-
318
-	if _, err := db.conn.Exec("DELETE FROM edge WHERE parent_id = ? AND name = ?;", parent.id, n); err != nil {
319
-		return err
320
-	}
321
-	return nil
322
-}
323
-
324
-// Remove the entity with the specified id
325
-// Walk the graph to make sure all references to the entity
326
-// are removed and return the number of references removed
327
-func (db *Database) Purge(id string) (int, error) {
328
-	db.mux.Lock()
329
-	defer db.mux.Unlock()
330
-
331
-	rollback := func() {
332
-		db.conn.Exec("ROLLBACK")
333
-	}
334
-
335
-	if _, err := db.conn.Exec("BEGIN"); err != nil {
336
-		return -1, err
337
-	}
338
-
339
-	// Delete all edges
340
-	rows, err := db.conn.Exec("DELETE FROM edge WHERE entity_id = ?;", id)
341
-	if err != nil {
342
-		rollback()
343
-		return -1, err
344
-	}
345
-
346
-	changes, err := rows.RowsAffected()
347
-	if err != nil {
348
-		return -1, err
349
-	}
350
-
351
-	// Delete entity
352
-	if _, err := db.conn.Exec("DELETE FROM entity where id = ?;", id); err != nil {
353
-		rollback()
354
-		return -1, err
355
-	}
356
-
357
-	if _, err := db.conn.Exec("COMMIT"); err != nil {
358
-		return -1, err
359
-	}
360
-	return int(changes), nil
361
-}
362
-
363
-// Rename an edge for a given path
364
-func (db *Database) Rename(currentName, newName string) error {
365
-	db.mux.Lock()
366
-	defer db.mux.Unlock()
367
-
368
-	parentPath, name := splitPath(currentName)
369
-	newParentPath, newEdgeName := splitPath(newName)
370
-
371
-	if parentPath != newParentPath {
372
-		return fmt.Errorf("Cannot rename when root paths do not match %s != %s", parentPath, newParentPath)
373
-	}
374
-
375
-	parent, err := db.get(parentPath)
376
-	if err != nil {
377
-		return err
378
-	}
379
-
380
-	rows, err := db.conn.Exec("UPDATE edge SET name = ? WHERE parent_id = ? AND name = ?;", newEdgeName, parent.id, name)
381
-	if err != nil {
382
-		return err
383
-	}
384
-	i, err := rows.RowsAffected()
385
-	if err != nil {
386
-		return err
387
-	}
388
-	if i == 0 {
389
-		return fmt.Errorf("Cannot locate edge for %s %s", parent.id, name)
390
-	}
391
-	return nil
392
-}
393
-
394
-type WalkMeta struct {
395
-	Parent   *Entity
396
-	Entity   *Entity
397
-	FullPath string
398
-	Edge     *Edge
399
-}
400
-
401
-func (db *Database) children(e *Entity, name string, depth int, entities []WalkMeta) ([]WalkMeta, error) {
402
-	if e == nil {
403
-		return entities, nil
404
-	}
405
-
406
-	rows, err := db.conn.Query("SELECT entity_id, name FROM edge where parent_id = ?;", e.id)
407
-	if err != nil {
408
-		return nil, err
409
-	}
410
-	defer rows.Close()
411
-
412
-	for rows.Next() {
413
-		var entityId, entityName string
414
-		if err := rows.Scan(&entityId, &entityName); err != nil {
415
-			return nil, err
416
-		}
417
-		child := &Entity{entityId}
418
-		edge := &Edge{
419
-			ParentID: e.id,
420
-			Name:     entityName,
421
-			EntityID: child.id,
422
-		}
423
-
424
-		meta := WalkMeta{
425
-			Parent:   e,
426
-			Entity:   child,
427
-			FullPath: path.Join(name, edge.Name),
428
-			Edge:     edge,
429
-		}
430
-
431
-		entities = append(entities, meta)
432
-
433
-		if depth != 0 {
434
-			nDepth := depth
435
-			if depth != -1 {
436
-				nDepth -= 1
437
-			}
438
-			entities, err = db.children(child, meta.FullPath, nDepth, entities)
439
-			if err != nil {
440
-				return nil, err
441
-			}
442
-		}
443
-	}
444
-
445
-	return entities, nil
446
-}
447
-
448
-// Return the entity based on the parent path and name
449
-func (db *Database) child(parent *Entity, name string) *Entity {
450
-	var id string
451
-	if err := db.conn.QueryRow("SELECT entity_id FROM edge WHERE parent_id = ? AND name = ?;", parent.id, name).Scan(&id); err != nil {
452
-		return nil
453
-	}
454
-	return &Entity{id}
455
-}
456
-
457
-// Return the id used to reference this entity
458
-func (e *Entity) ID() string {
459
-	return e.id
460
-}
461
-
462
-// Return the paths sorted by depth
463
-func (e Entities) Paths() []string {
464
-	out := make([]string, len(e))
465
-	var i int
466
-	for k := range e {
467
-		out[i] = k
468
-		i++
469
-	}
470
-	sortByDepth(out)
471
-
472
-	return out
473
-}
474 1
deleted file mode 100644
... ...
@@ -1,540 +0,0 @@
1
-package graphdb
2
-
3
-import (
4
-	_ "code.google.com/p/gosqlite/sqlite3"
5
-	"database/sql"
6
-	"fmt"
7
-	"os"
8
-	"path"
9
-	"strconv"
10
-	"testing"
11
-)
12
-
13
-func newTestDb(t *testing.T) (*Database, string) {
14
-	p := path.Join(os.TempDir(), "sqlite.db")
15
-	conn, err := sql.Open("sqlite3", p)
16
-	db, err := NewDatabase(conn, true)
17
-	if err != nil {
18
-		t.Fatal(err)
19
-	}
20
-	return db, p
21
-}
22
-
23
-func destroyTestDb(dbPath string) {
24
-	os.Remove(dbPath)
25
-}
26
-
27
-func TestNewDatabase(t *testing.T) {
28
-	db, dbpath := newTestDb(t)
29
-	if db == nil {
30
-		t.Fatal("Database should not be nil")
31
-	}
32
-	db.Close()
33
-	defer destroyTestDb(dbpath)
34
-}
35
-
36
-func TestCreateRootEnity(t *testing.T) {
37
-	db, dbpath := newTestDb(t)
38
-	defer destroyTestDb(dbpath)
39
-	root := db.RootEntity()
40
-	if root == nil {
41
-		t.Fatal("Root entity should not be nil")
42
-	}
43
-}
44
-
45
-func TestGetRootEntity(t *testing.T) {
46
-	db, dbpath := newTestDb(t)
47
-	defer destroyTestDb(dbpath)
48
-
49
-	e := db.Get("/")
50
-	if e == nil {
51
-		t.Fatal("Entity should not be nil")
52
-	}
53
-	if e.ID() != "0" {
54
-		t.Fatalf("Enity id should be 0, got %s", e.ID())
55
-	}
56
-}
57
-
58
-func TestSetEntityWithDifferentName(t *testing.T) {
59
-	db, dbpath := newTestDb(t)
60
-	defer destroyTestDb(dbpath)
61
-
62
-	db.Set("/test", "1")
63
-	if _, err := db.Set("/other", "1"); err != nil {
64
-		t.Fatal(err)
65
-	}
66
-}
67
-
68
-func TestSetDuplicateEntity(t *testing.T) {
69
-	db, dbpath := newTestDb(t)
70
-	defer destroyTestDb(dbpath)
71
-
72
-	if _, err := db.Set("/foo", "42"); err != nil {
73
-		t.Fatal(err)
74
-	}
75
-	if _, err := db.Set("/foo", "43"); err == nil {
76
-		t.Fatalf("Creating an entry with a duplciate path did not cause an error")
77
-	}
78
-}
79
-
80
-func TestCreateChild(t *testing.T) {
81
-	db, dbpath := newTestDb(t)
82
-	defer destroyTestDb(dbpath)
83
-
84
-	child, err := db.Set("/db", "1")
85
-	if err != nil {
86
-		t.Fatal(err)
87
-	}
88
-	if child == nil {
89
-		t.Fatal("Child should not be nil")
90
-	}
91
-	if child.ID() != "1" {
92
-		t.Fail()
93
-	}
94
-}
95
-
96
-func TestListAllRootChildren(t *testing.T) {
97
-	db, dbpath := newTestDb(t)
98
-	defer destroyTestDb(dbpath)
99
-
100
-	for i := 1; i < 6; i++ {
101
-		a := strconv.Itoa(i)
102
-		if _, err := db.Set("/"+a, a); err != nil {
103
-			t.Fatal(err)
104
-		}
105
-	}
106
-	entries := db.List("/", -1)
107
-	if len(entries) != 5 {
108
-		t.Fatalf("Expect 5 entries for / got %d", len(entries))
109
-	}
110
-}
111
-
112
-func TestListAllSubChildren(t *testing.T) {
113
-	db, dbpath := newTestDb(t)
114
-	defer destroyTestDb(dbpath)
115
-
116
-	_, err := db.Set("/webapp", "1")
117
-	if err != nil {
118
-		t.Fatal(err)
119
-	}
120
-	child2, err := db.Set("/db", "2")
121
-	if err != nil {
122
-		t.Fatal(err)
123
-	}
124
-	child4, err := db.Set("/logs", "4")
125
-	if err != nil {
126
-		t.Fatal(err)
127
-	}
128
-	if _, err := db.Set("/db/logs", child4.ID()); err != nil {
129
-		t.Fatal(err)
130
-	}
131
-
132
-	child3, err := db.Set("/sentry", "3")
133
-	if err != nil {
134
-		t.Fatal(err)
135
-	}
136
-	if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
137
-		t.Fatal(err)
138
-	}
139
-	if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
140
-		t.Fatal(err)
141
-	}
142
-
143
-	entries := db.List("/webapp", 1)
144
-	if len(entries) != 3 {
145
-		t.Fatalf("Expect 3 entries for / got %d", len(entries))
146
-	}
147
-
148
-	entries = db.List("/webapp", 0)
149
-	if len(entries) != 2 {
150
-		t.Fatalf("Expect 2 entries for / got %d", len(entries))
151
-	}
152
-}
153
-
154
-func TestAddSelfAsChild(t *testing.T) {
155
-	db, dbpath := newTestDb(t)
156
-	defer destroyTestDb(dbpath)
157
-
158
-	child, err := db.Set("/test", "1")
159
-	if err != nil {
160
-		t.Fatal(err)
161
-	}
162
-	if _, err := db.Set("/test/other", child.ID()); err == nil {
163
-		t.Fatal("Error should not be nil")
164
-	}
165
-}
166
-
167
-func TestAddChildToNonExistantRoot(t *testing.T) {
168
-	db, dbpath := newTestDb(t)
169
-	defer destroyTestDb(dbpath)
170
-
171
-	if _, err := db.Set("/myapp", "1"); err != nil {
172
-		t.Fatal(err)
173
-	}
174
-
175
-	if _, err := db.Set("/myapp/proxy/db", "2"); err == nil {
176
-		t.Fatal("Error should not be nil")
177
-	}
178
-}
179
-
180
-func TestWalkAll(t *testing.T) {
181
-	db, dbpath := newTestDb(t)
182
-	defer destroyTestDb(dbpath)
183
-	_, err := db.Set("/webapp", "1")
184
-	if err != nil {
185
-		t.Fatal(err)
186
-	}
187
-	child2, err := db.Set("/db", "2")
188
-	if err != nil {
189
-		t.Fatal(err)
190
-	}
191
-	child4, err := db.Set("/db/logs", "4")
192
-	if err != nil {
193
-		t.Fatal(err)
194
-	}
195
-	if _, err := db.Set("/webapp/logs", child4.ID()); err != nil {
196
-		t.Fatal(err)
197
-	}
198
-
199
-	child3, err := db.Set("/sentry", "3")
200
-	if err != nil {
201
-		t.Fatal(err)
202
-	}
203
-	if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
204
-		t.Fatal(err)
205
-	}
206
-	if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
207
-		t.Fatal(err)
208
-	}
209
-
210
-	child5, err := db.Set("/gograph", "5")
211
-	if err != nil {
212
-		t.Fatal(err)
213
-	}
214
-	if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
215
-		t.Fatal(err)
216
-	}
217
-
218
-	if err := db.Walk("/", func(p string, e *Entity) error {
219
-		t.Logf("Path: %s Entity: %s", p, e.ID())
220
-		return nil
221
-	}, -1); err != nil {
222
-		t.Fatal(err)
223
-	}
224
-}
225
-
226
-func TestGetEntityByPath(t *testing.T) {
227
-	db, dbpath := newTestDb(t)
228
-	defer destroyTestDb(dbpath)
229
-	_, err := db.Set("/webapp", "1")
230
-	if err != nil {
231
-		t.Fatal(err)
232
-	}
233
-	child2, err := db.Set("/db", "2")
234
-	if err != nil {
235
-		t.Fatal(err)
236
-	}
237
-	child4, err := db.Set("/logs", "4")
238
-	if err != nil {
239
-		t.Fatal(err)
240
-	}
241
-	if _, err := db.Set("/db/logs", child4.ID()); err != nil {
242
-		t.Fatal(err)
243
-	}
244
-
245
-	child3, err := db.Set("/sentry", "3")
246
-	if err != nil {
247
-		t.Fatal(err)
248
-	}
249
-	if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
250
-		t.Fatal(err)
251
-	}
252
-	if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
253
-		t.Fatal(err)
254
-	}
255
-
256
-	child5, err := db.Set("/gograph", "5")
257
-	if err != nil {
258
-		t.Fatal(err)
259
-	}
260
-	if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
261
-		t.Fatal(err)
262
-	}
263
-
264
-	entity := db.Get("/webapp/db/logs")
265
-	if entity == nil {
266
-		t.Fatal("Entity should not be nil")
267
-	}
268
-	if entity.ID() != "4" {
269
-		t.Fatalf("Expected to get entity with id 4, got %s", entity.ID())
270
-	}
271
-}
272
-
273
-func TestEnitiesPaths(t *testing.T) {
274
-	db, dbpath := newTestDb(t)
275
-	defer destroyTestDb(dbpath)
276
-	_, err := db.Set("/webapp", "1")
277
-	if err != nil {
278
-		t.Fatal(err)
279
-	}
280
-	child2, err := db.Set("/db", "2")
281
-	if err != nil {
282
-		t.Fatal(err)
283
-	}
284
-	child4, err := db.Set("/logs", "4")
285
-	if err != nil {
286
-		t.Fatal(err)
287
-	}
288
-	if _, err := db.Set("/db/logs", child4.ID()); err != nil {
289
-		t.Fatal(err)
290
-	}
291
-
292
-	child3, err := db.Set("/sentry", "3")
293
-	if err != nil {
294
-		t.Fatal(err)
295
-	}
296
-	if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
297
-		t.Fatal(err)
298
-	}
299
-	if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
300
-		t.Fatal(err)
301
-	}
302
-
303
-	child5, err := db.Set("/gograph", "5")
304
-	if err != nil {
305
-		t.Fatal(err)
306
-	}
307
-	if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
308
-		t.Fatal(err)
309
-	}
310
-
311
-	out := db.List("/", -1)
312
-	for _, p := range out.Paths() {
313
-		t.Log(p)
314
-	}
315
-}
316
-
317
-func TestDeleteRootEntity(t *testing.T) {
318
-	db, dbpath := newTestDb(t)
319
-	defer destroyTestDb(dbpath)
320
-
321
-	if err := db.Delete("/"); err == nil {
322
-		t.Fatal("Error should not be nil")
323
-	}
324
-}
325
-
326
-func TestDeleteEntity(t *testing.T) {
327
-	db, dbpath := newTestDb(t)
328
-	defer destroyTestDb(dbpath)
329
-	_, err := db.Set("/webapp", "1")
330
-	if err != nil {
331
-		t.Fatal(err)
332
-	}
333
-	child2, err := db.Set("/db", "2")
334
-	if err != nil {
335
-		t.Fatal(err)
336
-	}
337
-	child4, err := db.Set("/logs", "4")
338
-	if err != nil {
339
-		t.Fatal(err)
340
-	}
341
-	if _, err := db.Set("/db/logs", child4.ID()); err != nil {
342
-		t.Fatal(err)
343
-	}
344
-
345
-	child3, err := db.Set("/sentry", "3")
346
-	if err != nil {
347
-		t.Fatal(err)
348
-	}
349
-	if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
350
-		t.Fatal(err)
351
-	}
352
-	if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
353
-		t.Fatal(err)
354
-	}
355
-
356
-	child5, err := db.Set("/gograph", "5")
357
-	if err != nil {
358
-		t.Fatal(err)
359
-	}
360
-	if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
361
-		t.Fatal(err)
362
-	}
363
-
364
-	if err := db.Delete("/webapp/sentry"); err != nil {
365
-		t.Fatal(err)
366
-	}
367
-	entity := db.Get("/webapp/sentry")
368
-	if entity != nil {
369
-		t.Fatal("Entity /webapp/sentry should be nil")
370
-	}
371
-}
372
-
373
-func TestCountRefs(t *testing.T) {
374
-	db, dbpath := newTestDb(t)
375
-	defer destroyTestDb(dbpath)
376
-
377
-	db.Set("/webapp", "1")
378
-
379
-	if db.Refs("1") != 1 {
380
-		t.Fatal("Expect reference count to be 1")
381
-	}
382
-
383
-	db.Set("/db", "2")
384
-	db.Set("/webapp/db", "2")
385
-	if db.Refs("2") != 2 {
386
-		t.Fatal("Expect reference count to be 2")
387
-	}
388
-}
389
-
390
-func TestPurgeId(t *testing.T) {
391
-	db, dbpath := newTestDb(t)
392
-	defer destroyTestDb(dbpath)
393
-
394
-	db.Set("/webapp", "1")
395
-
396
-	if db.Refs("1") != 1 {
397
-		t.Fatal("Expect reference count to be 1")
398
-	}
399
-
400
-	db.Set("/db", "2")
401
-	db.Set("/webapp/db", "2")
402
-
403
-	count, err := db.Purge("2")
404
-	if err != nil {
405
-		t.Fatal(err)
406
-	}
407
-	if count != 2 {
408
-		t.Fatal("Expected 2 references to be removed")
409
-	}
410
-}
411
-
412
-func TestRename(t *testing.T) {
413
-	db, dbpath := newTestDb(t)
414
-	defer destroyTestDb(dbpath)
415
-
416
-	db.Set("/webapp", "1")
417
-
418
-	if db.Refs("1") != 1 {
419
-		t.Fatal("Expect reference count to be 1")
420
-	}
421
-
422
-	db.Set("/db", "2")
423
-	db.Set("/webapp/db", "2")
424
-
425
-	if db.Get("/webapp/db") == nil {
426
-		t.Fatal("Cannot find entity at path /webapp/db")
427
-	}
428
-
429
-	if err := db.Rename("/webapp/db", "/webapp/newdb"); err != nil {
430
-		t.Fatal(err)
431
-	}
432
-	if db.Get("/webapp/db") != nil {
433
-		t.Fatal("Entity should not exist at /webapp/db")
434
-	}
435
-	if db.Get("/webapp/newdb") == nil {
436
-		t.Fatal("Cannot find entity at path /webapp/newdb")
437
-	}
438
-
439
-}
440
-
441
-func TestCreateMultipleNames(t *testing.T) {
442
-	db, dbpath := newTestDb(t)
443
-	defer destroyTestDb(dbpath)
444
-
445
-	db.Set("/db", "1")
446
-	if _, err := db.Set("/myapp", "1"); err != nil {
447
-		t.Fatal(err)
448
-	}
449
-
450
-	db.Walk("/", func(p string, e *Entity) error {
451
-		t.Logf("%s\n", p)
452
-		return nil
453
-	}, -1)
454
-}
455
-
456
-func TestRefPaths(t *testing.T) {
457
-	db, dbpath := newTestDb(t)
458
-	defer destroyTestDb(dbpath)
459
-
460
-	db.Set("/webapp", "1")
461
-
462
-	db.Set("/db", "2")
463
-	db.Set("/webapp/db", "2")
464
-
465
-	refs := db.RefPaths("2")
466
-	if len(refs) != 2 {
467
-		t.Fatalf("Expected reference count to be 2, got %d", len(refs))
468
-	}
469
-}
470
-
471
-func TestExistsTrue(t *testing.T) {
472
-	db, dbpath := newTestDb(t)
473
-	defer destroyTestDb(dbpath)
474
-
475
-	db.Set("/testing", "1")
476
-
477
-	if !db.Exists("/testing") {
478
-		t.Fatalf("/tesing should exist")
479
-	}
480
-}
481
-
482
-func TestExistsFalse(t *testing.T) {
483
-	db, dbpath := newTestDb(t)
484
-	defer destroyTestDb(dbpath)
485
-
486
-	db.Set("/toerhe", "1")
487
-
488
-	if db.Exists("/testing") {
489
-		t.Fatalf("/tesing should not exist")
490
-	}
491
-
492
-}
493
-
494
-func TestGetNameWithTrailingSlash(t *testing.T) {
495
-	db, dbpath := newTestDb(t)
496
-	defer destroyTestDb(dbpath)
497
-
498
-	db.Set("/todo", "1")
499
-
500
-	e := db.Get("/todo/")
501
-	if e == nil {
502
-		t.Fatalf("Entity should not be nil")
503
-	}
504
-}
505
-
506
-func TestConcurrentWrites(t *testing.T) {
507
-	db, dbpath := newTestDb(t)
508
-	defer destroyTestDb(dbpath)
509
-
510
-	errs := make(chan error, 2)
511
-
512
-	save := func(name string, id string) {
513
-		if _, err := db.Set(fmt.Sprintf("/%s", name), id); err != nil {
514
-			errs <- err
515
-		}
516
-		errs <- nil
517
-	}
518
-	purge := func(id string) {
519
-		if _, err := db.Purge(id); err != nil {
520
-			errs <- err
521
-		}
522
-		errs <- nil
523
-	}
524
-
525
-	save("/1", "1")
526
-
527
-	go purge("1")
528
-	go save("/2", "2")
529
-
530
-	any := false
531
-	for i := 0; i < 2; i++ {
532
-		if err := <-errs; err != nil {
533
-			any = true
534
-			t.Log(err)
535
-		}
536
-	}
537
-	if any {
538
-		t.Fatal()
539
-	}
540
-}
541 1
deleted file mode 100644
... ...
@@ -1,27 +0,0 @@
1
-package graphdb
2
-
3
-import "sort"
4
-
5
-type pathSorter struct {
6
-	paths []string
7
-	by    func(i, j string) bool
8
-}
9
-
10
-func sortByDepth(paths []string) {
11
-	s := &pathSorter{paths, func(i, j string) bool {
12
-		return PathDepth(i) > PathDepth(j)
13
-	}}
14
-	sort.Sort(s)
15
-}
16
-
17
-func (s *pathSorter) Len() int {
18
-	return len(s.paths)
19
-}
20
-
21
-func (s *pathSorter) Swap(i, j int) {
22
-	s.paths[i], s.paths[j] = s.paths[j], s.paths[i]
23
-}
24
-
25
-func (s *pathSorter) Less(i, j int) bool {
26
-	return s.by(s.paths[i], s.paths[j])
27
-}
28 1
deleted file mode 100644
... ...
@@ -1,29 +0,0 @@
1
-package graphdb
2
-
3
-import (
4
-	"testing"
5
-)
6
-
7
-func TestSort(t *testing.T) {
8
-	paths := []string{
9
-		"/",
10
-		"/myreallylongname",
11
-		"/app/db",
12
-	}
13
-
14
-	sortByDepth(paths)
15
-
16
-	if len(paths) != 3 {
17
-		t.Fatalf("Expected 3 parts got %d", len(paths))
18
-	}
19
-
20
-	if paths[0] != "/app/db" {
21
-		t.Fatalf("Expected /app/db got %s", paths[0])
22
-	}
23
-	if paths[1] != "/myreallylongname" {
24
-		t.Fatalf("Expected /myreallylongname got %s", paths[1])
25
-	}
26
-	if paths[2] != "/" {
27
-		t.Fatalf("Expected / got %s", paths[2])
28
-	}
29
-}
30 1
deleted file mode 100644
... ...
@@ -1,32 +0,0 @@
1
-package graphdb
2
-
3
-import (
4
-	"path"
5
-	"strings"
6
-)
7
-
8
-// Split p on /
9
-func split(p string) []string {
10
-	return strings.Split(p, "/")
11
-}
12
-
13
-// Returns the depth or number of / in a given path
14
-func PathDepth(p string) int {
15
-	parts := split(p)
16
-	if len(parts) == 2 && parts[1] == "" {
17
-		return 1
18
-	}
19
-	return len(parts)
20
-}
21
-
22
-func splitPath(p string) (parent, name string) {
23
-	if p[0] != '/' {
24
-		p = "/" + p
25
-	}
26
-	parent, name = path.Split(p)
27
-	l := len(parent)
28
-	if parent[l-1] == '/' {
29
-		parent = parent[:l-1]
30
-	}
31
-	return
32
-}
33 1
new file mode 100644
... ...
@@ -0,0 +1 @@
0
+Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
0 1
new file mode 100644
... ...
@@ -0,0 +1,5 @@
0
+package graphdb
1
+
2
+func NewSqliteConn(root string) (*Database, error) {
3
+	panic("Not implemented")
4
+}
0 5
new file mode 100644
... ...
@@ -0,0 +1,23 @@
0
+package graphdb
1
+
2
+import (
3
+	_ "code.google.com/p/gosqlite/sqlite3" // registers sqlite
4
+	"database/sql"
5
+	"os"
6
+)
7
+
8
+func NewSqliteConn(root string) (*Database, error) {
9
+	initDatabase := false
10
+	if _, err := os.Stat(root); err != nil {
11
+		if os.IsNotExist(err) {
12
+			initDatabase = true
13
+		} else {
14
+			return nil, err
15
+		}
16
+	}
17
+	conn, err := sql.Open("sqlite3", root)
18
+	if err != nil {
19
+		return nil, err
20
+	}
21
+	return NewDatabase(conn, initDatabase)
22
+}
0 23
new file mode 100644
... ...
@@ -0,0 +1,473 @@
0
+package graphdb
1
+
2
+import (
3
+	"database/sql"
4
+	"fmt"
5
+	"path"
6
+	"sync"
7
+)
8
+
9
+const (
10
+	createEntityTable = `
11
+    CREATE TABLE IF NOT EXISTS entity (
12
+        id text NOT NULL PRIMARY KEY
13
+    );`
14
+
15
+	createEdgeTable = `
16
+    CREATE TABLE IF NOT EXISTS edge (
17
+        "entity_id" text NOT NULL,
18
+        "parent_id" text NULL,
19
+        "name" text NOT NULL,
20
+        CONSTRAINT "parent_fk" FOREIGN KEY ("parent_id") REFERENCES "entity" ("id"),
21
+        CONSTRAINT "entity_fk" FOREIGN KEY ("entity_id") REFERENCES "entity" ("id")
22
+        );
23
+    `
24
+
25
+	createEdgeIndices = `
26
+    CREATE UNIQUE INDEX IF NOT EXISTS "name_parent_ix" ON "edge" (parent_id, name);
27
+    `
28
+)
29
+
30
+// Entity with a unique id
31
+type Entity struct {
32
+	id string
33
+}
34
+
35
+// An Edge connects two entities together
36
+type Edge struct {
37
+	EntityID string
38
+	Name     string
39
+	ParentID string
40
+}
41
+
42
+type Entities map[string]*Entity
43
+type Edges []*Edge
44
+
45
+type WalkFunc func(fullPath string, entity *Entity) error
46
+
47
+// Graph database for storing entities and their relationships
48
+type Database struct {
49
+	conn *sql.DB
50
+	mux  sync.RWMutex
51
+}
52
+
53
+// Create a new graph database initialized with a root entity
54
+func NewDatabase(conn *sql.DB, init bool) (*Database, error) {
55
+	if conn == nil {
56
+		return nil, fmt.Errorf("Database connection cannot be nil")
57
+	}
58
+	db := &Database{conn: conn}
59
+
60
+	if init {
61
+		if _, err := conn.Exec(createEntityTable); err != nil {
62
+			return nil, err
63
+		}
64
+		if _, err := conn.Exec(createEdgeTable); err != nil {
65
+			return nil, err
66
+		}
67
+		if _, err := conn.Exec(createEdgeIndices); err != nil {
68
+			return nil, err
69
+		}
70
+
71
+		rollback := func() {
72
+			conn.Exec("ROLLBACK")
73
+		}
74
+
75
+		// Create root entities
76
+		if _, err := conn.Exec("BEGIN"); err != nil {
77
+			return nil, err
78
+		}
79
+		if _, err := conn.Exec("INSERT INTO entity (id) VALUES (?);", "0"); err != nil {
80
+			rollback()
81
+			return nil, err
82
+		}
83
+
84
+		if _, err := conn.Exec("INSERT INTO edge (entity_id, name) VALUES(?,?);", "0", "/"); err != nil {
85
+			rollback()
86
+			return nil, err
87
+		}
88
+
89
+		if _, err := conn.Exec("COMMIT"); err != nil {
90
+			return nil, err
91
+		}
92
+	}
93
+	return db, nil
94
+}
95
+
96
+// Close the underlying connection to the database
97
+func (db *Database) Close() error {
98
+	return db.conn.Close()
99
+}
100
+
101
+// Set the entity id for a given path
102
+func (db *Database) Set(fullPath, id string) (*Entity, error) {
103
+	db.mux.Lock()
104
+	defer db.mux.Unlock()
105
+
106
+	rollback := func() {
107
+		db.conn.Exec("ROLLBACK")
108
+	}
109
+	if _, err := db.conn.Exec("BEGIN EXCLUSIVE"); err != nil {
110
+		return nil, err
111
+	}
112
+	var entityId string
113
+	if err := db.conn.QueryRow("SELECT id FROM entity WHERE id = ?;", id).Scan(&entityId); err != nil {
114
+		if err == sql.ErrNoRows {
115
+			if _, err := db.conn.Exec("INSERT INTO entity (id) VALUES(?);", id); err != nil {
116
+				rollback()
117
+				return nil, err
118
+			}
119
+		} else {
120
+			rollback()
121
+			return nil, err
122
+		}
123
+	}
124
+	e := &Entity{id}
125
+
126
+	parentPath, name := splitPath(fullPath)
127
+	if err := db.setEdge(parentPath, name, e); err != nil {
128
+		rollback()
129
+		return nil, err
130
+	}
131
+
132
+	if _, err := db.conn.Exec("COMMIT"); err != nil {
133
+		return nil, err
134
+	}
135
+	return e, nil
136
+}
137
+
138
+// Return true if a name already exists in the database
139
+func (db *Database) Exists(name string) bool {
140
+	db.mux.RLock()
141
+	defer db.mux.RUnlock()
142
+
143
+	e, err := db.get(name)
144
+	if err != nil {
145
+		return false
146
+	}
147
+	return e != nil
148
+}
149
+
150
+func (db *Database) setEdge(parentPath, name string, e *Entity) 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
+
159
+	if _, err := db.conn.Exec("INSERT INTO edge (parent_id, name, entity_id) VALUES (?,?,?);", parent.id, name, e.id); err != nil {
160
+		return err
161
+	}
162
+	return nil
163
+}
164
+
165
+// Return the root "/" entity for the database
166
+func (db *Database) RootEntity() *Entity {
167
+	return &Entity{
168
+		id: "0",
169
+	}
170
+}
171
+
172
+// Return the entity for a given path
173
+func (db *Database) Get(name string) *Entity {
174
+	db.mux.RLock()
175
+	defer db.mux.RUnlock()
176
+
177
+	e, err := db.get(name)
178
+	if err != nil {
179
+		return nil
180
+	}
181
+	return e
182
+}
183
+
184
+func (db *Database) get(name string) (*Entity, error) {
185
+	e := db.RootEntity()
186
+	// We always know the root name so return it if
187
+	// it is requested
188
+	if name == "/" {
189
+		return e, nil
190
+	}
191
+
192
+	parts := split(name)
193
+	for i := 1; i < len(parts); i++ {
194
+		p := parts[i]
195
+		if p == "" {
196
+			continue
197
+		}
198
+
199
+		next := db.child(e, p)
200
+		if next == nil {
201
+			return nil, fmt.Errorf("Cannot find child for %s", name)
202
+		}
203
+		e = next
204
+	}
205
+	return e, nil
206
+
207
+}
208
+
209
+// List all entities by from the name
210
+// The key will be the full path of the entity
211
+func (db *Database) List(name string, depth int) Entities {
212
+	db.mux.RLock()
213
+	defer db.mux.RUnlock()
214
+
215
+	out := Entities{}
216
+	e, err := db.get(name)
217
+	if err != nil {
218
+		return out
219
+	}
220
+
221
+	children, err := db.children(e, name, depth, nil)
222
+	if err != nil {
223
+		return out
224
+	}
225
+
226
+	for _, c := range children {
227
+		out[c.FullPath] = c.Entity
228
+	}
229
+	return out
230
+}
231
+
232
+// Walk through the child graph of an entity, calling walkFunc for each child entity.
233
+// It is safe for walkFunc to call graph functions.
234
+func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error {
235
+	children, err := db.Children(name, depth)
236
+	if err != nil {
237
+		return err
238
+	}
239
+
240
+	// Note: the database lock must not be held while calling walkFunc
241
+	for _, c := range children {
242
+		if err := walkFunc(c.FullPath, c.Entity); err != nil {
243
+			return err
244
+		}
245
+	}
246
+	return nil
247
+}
248
+
249
+// Return the children of the specified entity
250
+func (db *Database) Children(name string, depth int) ([]WalkMeta, error) {
251
+	db.mux.RLock()
252
+	defer db.mux.RUnlock()
253
+
254
+	e, err := db.get(name)
255
+	if err != nil {
256
+		return nil, err
257
+	}
258
+
259
+	return db.children(e, name, depth, nil)
260
+}
261
+
262
+// Return the refrence count for a specified id
263
+func (db *Database) Refs(id string) int {
264
+	db.mux.RLock()
265
+	defer db.mux.RUnlock()
266
+
267
+	var count int
268
+	if err := db.conn.QueryRow("SELECT COUNT(*) FROM edge WHERE entity_id = ?;", id).Scan(&count); err != nil {
269
+		return 0
270
+	}
271
+	return count
272
+}
273
+
274
+// Return all the id's path references
275
+func (db *Database) RefPaths(id string) Edges {
276
+	db.mux.RLock()
277
+	defer db.mux.RUnlock()
278
+
279
+	refs := Edges{}
280
+
281
+	rows, err := db.conn.Query("SELECT name, parent_id FROM edge WHERE entity_id = ?;", id)
282
+	if err != nil {
283
+		return refs
284
+	}
285
+	defer rows.Close()
286
+
287
+	for rows.Next() {
288
+		var name string
289
+		var parentId string
290
+		if err := rows.Scan(&name, &parentId); err != nil {
291
+			return refs
292
+		}
293
+		refs = append(refs, &Edge{
294
+			EntityID: id,
295
+			Name:     name,
296
+			ParentID: parentId,
297
+		})
298
+	}
299
+	return refs
300
+}
301
+
302
+// Delete the reference to an entity at a given path
303
+func (db *Database) Delete(name string) error {
304
+	db.mux.Lock()
305
+	defer db.mux.Unlock()
306
+
307
+	if name == "/" {
308
+		return fmt.Errorf("Cannot delete root entity")
309
+	}
310
+
311
+	parentPath, n := splitPath(name)
312
+	parent, err := db.get(parentPath)
313
+	if err != nil {
314
+		return err
315
+	}
316
+
317
+	if _, err := db.conn.Exec("DELETE FROM edge WHERE parent_id = ? AND name = ?;", parent.id, n); err != nil {
318
+		return err
319
+	}
320
+	return nil
321
+}
322
+
323
+// Remove the entity with the specified id
324
+// Walk the graph to make sure all references to the entity
325
+// are removed and return the number of references removed
326
+func (db *Database) Purge(id string) (int, error) {
327
+	db.mux.Lock()
328
+	defer db.mux.Unlock()
329
+
330
+	rollback := func() {
331
+		db.conn.Exec("ROLLBACK")
332
+	}
333
+
334
+	if _, err := db.conn.Exec("BEGIN"); err != nil {
335
+		return -1, err
336
+	}
337
+
338
+	// Delete all edges
339
+	rows, err := db.conn.Exec("DELETE FROM edge WHERE entity_id = ?;", id)
340
+	if err != nil {
341
+		rollback()
342
+		return -1, err
343
+	}
344
+
345
+	changes, err := rows.RowsAffected()
346
+	if err != nil {
347
+		return -1, err
348
+	}
349
+
350
+	// Delete entity
351
+	if _, err := db.conn.Exec("DELETE FROM entity where id = ?;", id); err != nil {
352
+		rollback()
353
+		return -1, err
354
+	}
355
+
356
+	if _, err := db.conn.Exec("COMMIT"); err != nil {
357
+		return -1, err
358
+	}
359
+	return int(changes), nil
360
+}
361
+
362
+// Rename an edge for a given path
363
+func (db *Database) Rename(currentName, newName string) error {
364
+	db.mux.Lock()
365
+	defer db.mux.Unlock()
366
+
367
+	parentPath, name := splitPath(currentName)
368
+	newParentPath, newEdgeName := splitPath(newName)
369
+
370
+	if parentPath != newParentPath {
371
+		return fmt.Errorf("Cannot rename when root paths do not match %s != %s", parentPath, newParentPath)
372
+	}
373
+
374
+	parent, err := db.get(parentPath)
375
+	if err != nil {
376
+		return err
377
+	}
378
+
379
+	rows, err := db.conn.Exec("UPDATE edge SET name = ? WHERE parent_id = ? AND name = ?;", newEdgeName, parent.id, name)
380
+	if err != nil {
381
+		return err
382
+	}
383
+	i, err := rows.RowsAffected()
384
+	if err != nil {
385
+		return err
386
+	}
387
+	if i == 0 {
388
+		return fmt.Errorf("Cannot locate edge for %s %s", parent.id, name)
389
+	}
390
+	return nil
391
+}
392
+
393
+type WalkMeta struct {
394
+	Parent   *Entity
395
+	Entity   *Entity
396
+	FullPath string
397
+	Edge     *Edge
398
+}
399
+
400
+func (db *Database) children(e *Entity, name string, depth int, entities []WalkMeta) ([]WalkMeta, error) {
401
+	if e == nil {
402
+		return entities, nil
403
+	}
404
+
405
+	rows, err := db.conn.Query("SELECT entity_id, name FROM edge where parent_id = ?;", e.id)
406
+	if err != nil {
407
+		return nil, err
408
+	}
409
+	defer rows.Close()
410
+
411
+	for rows.Next() {
412
+		var entityId, entityName string
413
+		if err := rows.Scan(&entityId, &entityName); err != nil {
414
+			return nil, err
415
+		}
416
+		child := &Entity{entityId}
417
+		edge := &Edge{
418
+			ParentID: e.id,
419
+			Name:     entityName,
420
+			EntityID: child.id,
421
+		}
422
+
423
+		meta := WalkMeta{
424
+			Parent:   e,
425
+			Entity:   child,
426
+			FullPath: path.Join(name, edge.Name),
427
+			Edge:     edge,
428
+		}
429
+
430
+		entities = append(entities, meta)
431
+
432
+		if depth != 0 {
433
+			nDepth := depth
434
+			if depth != -1 {
435
+				nDepth -= 1
436
+			}
437
+			entities, err = db.children(child, meta.FullPath, nDepth, entities)
438
+			if err != nil {
439
+				return nil, err
440
+			}
441
+		}
442
+	}
443
+
444
+	return entities, nil
445
+}
446
+
447
+// Return the entity based on the parent path and name
448
+func (db *Database) child(parent *Entity, name string) *Entity {
449
+	var id string
450
+	if err := db.conn.QueryRow("SELECT entity_id FROM edge WHERE parent_id = ? AND name = ?;", parent.id, name).Scan(&id); err != nil {
451
+		return nil
452
+	}
453
+	return &Entity{id}
454
+}
455
+
456
+// Return the id used to reference this entity
457
+func (e *Entity) ID() string {
458
+	return e.id
459
+}
460
+
461
+// Return the paths sorted by depth
462
+func (e Entities) Paths() []string {
463
+	out := make([]string, len(e))
464
+	var i int
465
+	for k := range e {
466
+		out[i] = k
467
+		i++
468
+	}
469
+	sortByDepth(out)
470
+
471
+	return out
472
+}
0 473
new file mode 100644
... ...
@@ -0,0 +1,540 @@
0
+package graphdb
1
+
2
+import (
3
+	_ "code.google.com/p/gosqlite/sqlite3"
4
+	"database/sql"
5
+	"fmt"
6
+	"os"
7
+	"path"
8
+	"strconv"
9
+	"testing"
10
+)
11
+
12
+func newTestDb(t *testing.T) (*Database, string) {
13
+	p := path.Join(os.TempDir(), "sqlite.db")
14
+	conn, err := sql.Open("sqlite3", p)
15
+	db, err := NewDatabase(conn, true)
16
+	if err != nil {
17
+		t.Fatal(err)
18
+	}
19
+	return db, p
20
+}
21
+
22
+func destroyTestDb(dbPath string) {
23
+	os.Remove(dbPath)
24
+}
25
+
26
+func TestNewDatabase(t *testing.T) {
27
+	db, dbpath := newTestDb(t)
28
+	if db == nil {
29
+		t.Fatal("Database should not be nil")
30
+	}
31
+	db.Close()
32
+	defer destroyTestDb(dbpath)
33
+}
34
+
35
+func TestCreateRootEnity(t *testing.T) {
36
+	db, dbpath := newTestDb(t)
37
+	defer destroyTestDb(dbpath)
38
+	root := db.RootEntity()
39
+	if root == nil {
40
+		t.Fatal("Root entity should not be nil")
41
+	}
42
+}
43
+
44
+func TestGetRootEntity(t *testing.T) {
45
+	db, dbpath := newTestDb(t)
46
+	defer destroyTestDb(dbpath)
47
+
48
+	e := db.Get("/")
49
+	if e == nil {
50
+		t.Fatal("Entity should not be nil")
51
+	}
52
+	if e.ID() != "0" {
53
+		t.Fatalf("Enity id should be 0, got %s", e.ID())
54
+	}
55
+}
56
+
57
+func TestSetEntityWithDifferentName(t *testing.T) {
58
+	db, dbpath := newTestDb(t)
59
+	defer destroyTestDb(dbpath)
60
+
61
+	db.Set("/test", "1")
62
+	if _, err := db.Set("/other", "1"); err != nil {
63
+		t.Fatal(err)
64
+	}
65
+}
66
+
67
+func TestSetDuplicateEntity(t *testing.T) {
68
+	db, dbpath := newTestDb(t)
69
+	defer destroyTestDb(dbpath)
70
+
71
+	if _, err := db.Set("/foo", "42"); err != nil {
72
+		t.Fatal(err)
73
+	}
74
+	if _, err := db.Set("/foo", "43"); err == nil {
75
+		t.Fatalf("Creating an entry with a duplciate path did not cause an error")
76
+	}
77
+}
78
+
79
+func TestCreateChild(t *testing.T) {
80
+	db, dbpath := newTestDb(t)
81
+	defer destroyTestDb(dbpath)
82
+
83
+	child, err := db.Set("/db", "1")
84
+	if err != nil {
85
+		t.Fatal(err)
86
+	}
87
+	if child == nil {
88
+		t.Fatal("Child should not be nil")
89
+	}
90
+	if child.ID() != "1" {
91
+		t.Fail()
92
+	}
93
+}
94
+
95
+func TestListAllRootChildren(t *testing.T) {
96
+	db, dbpath := newTestDb(t)
97
+	defer destroyTestDb(dbpath)
98
+
99
+	for i := 1; i < 6; i++ {
100
+		a := strconv.Itoa(i)
101
+		if _, err := db.Set("/"+a, a); err != nil {
102
+			t.Fatal(err)
103
+		}
104
+	}
105
+	entries := db.List("/", -1)
106
+	if len(entries) != 5 {
107
+		t.Fatalf("Expect 5 entries for / got %d", len(entries))
108
+	}
109
+}
110
+
111
+func TestListAllSubChildren(t *testing.T) {
112
+	db, dbpath := newTestDb(t)
113
+	defer destroyTestDb(dbpath)
114
+
115
+	_, err := db.Set("/webapp", "1")
116
+	if err != nil {
117
+		t.Fatal(err)
118
+	}
119
+	child2, err := db.Set("/db", "2")
120
+	if err != nil {
121
+		t.Fatal(err)
122
+	}
123
+	child4, err := db.Set("/logs", "4")
124
+	if err != nil {
125
+		t.Fatal(err)
126
+	}
127
+	if _, err := db.Set("/db/logs", child4.ID()); err != nil {
128
+		t.Fatal(err)
129
+	}
130
+
131
+	child3, err := db.Set("/sentry", "3")
132
+	if err != nil {
133
+		t.Fatal(err)
134
+	}
135
+	if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
136
+		t.Fatal(err)
137
+	}
138
+	if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
139
+		t.Fatal(err)
140
+	}
141
+
142
+	entries := db.List("/webapp", 1)
143
+	if len(entries) != 3 {
144
+		t.Fatalf("Expect 3 entries for / got %d", len(entries))
145
+	}
146
+
147
+	entries = db.List("/webapp", 0)
148
+	if len(entries) != 2 {
149
+		t.Fatalf("Expect 2 entries for / got %d", len(entries))
150
+	}
151
+}
152
+
153
+func TestAddSelfAsChild(t *testing.T) {
154
+	db, dbpath := newTestDb(t)
155
+	defer destroyTestDb(dbpath)
156
+
157
+	child, err := db.Set("/test", "1")
158
+	if err != nil {
159
+		t.Fatal(err)
160
+	}
161
+	if _, err := db.Set("/test/other", child.ID()); err == nil {
162
+		t.Fatal("Error should not be nil")
163
+	}
164
+}
165
+
166
+func TestAddChildToNonExistantRoot(t *testing.T) {
167
+	db, dbpath := newTestDb(t)
168
+	defer destroyTestDb(dbpath)
169
+
170
+	if _, err := db.Set("/myapp", "1"); err != nil {
171
+		t.Fatal(err)
172
+	}
173
+
174
+	if _, err := db.Set("/myapp/proxy/db", "2"); err == nil {
175
+		t.Fatal("Error should not be nil")
176
+	}
177
+}
178
+
179
+func TestWalkAll(t *testing.T) {
180
+	db, dbpath := newTestDb(t)
181
+	defer destroyTestDb(dbpath)
182
+	_, err := db.Set("/webapp", "1")
183
+	if err != nil {
184
+		t.Fatal(err)
185
+	}
186
+	child2, err := db.Set("/db", "2")
187
+	if err != nil {
188
+		t.Fatal(err)
189
+	}
190
+	child4, err := db.Set("/db/logs", "4")
191
+	if err != nil {
192
+		t.Fatal(err)
193
+	}
194
+	if _, err := db.Set("/webapp/logs", child4.ID()); err != nil {
195
+		t.Fatal(err)
196
+	}
197
+
198
+	child3, err := db.Set("/sentry", "3")
199
+	if err != nil {
200
+		t.Fatal(err)
201
+	}
202
+	if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
203
+		t.Fatal(err)
204
+	}
205
+	if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
206
+		t.Fatal(err)
207
+	}
208
+
209
+	child5, err := db.Set("/gograph", "5")
210
+	if err != nil {
211
+		t.Fatal(err)
212
+	}
213
+	if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
214
+		t.Fatal(err)
215
+	}
216
+
217
+	if err := db.Walk("/", func(p string, e *Entity) error {
218
+		t.Logf("Path: %s Entity: %s", p, e.ID())
219
+		return nil
220
+	}, -1); err != nil {
221
+		t.Fatal(err)
222
+	}
223
+}
224
+
225
+func TestGetEntityByPath(t *testing.T) {
226
+	db, dbpath := newTestDb(t)
227
+	defer destroyTestDb(dbpath)
228
+	_, err := db.Set("/webapp", "1")
229
+	if err != nil {
230
+		t.Fatal(err)
231
+	}
232
+	child2, err := db.Set("/db", "2")
233
+	if err != nil {
234
+		t.Fatal(err)
235
+	}
236
+	child4, err := db.Set("/logs", "4")
237
+	if err != nil {
238
+		t.Fatal(err)
239
+	}
240
+	if _, err := db.Set("/db/logs", child4.ID()); err != nil {
241
+		t.Fatal(err)
242
+	}
243
+
244
+	child3, err := db.Set("/sentry", "3")
245
+	if err != nil {
246
+		t.Fatal(err)
247
+	}
248
+	if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
249
+		t.Fatal(err)
250
+	}
251
+	if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
252
+		t.Fatal(err)
253
+	}
254
+
255
+	child5, err := db.Set("/gograph", "5")
256
+	if err != nil {
257
+		t.Fatal(err)
258
+	}
259
+	if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
260
+		t.Fatal(err)
261
+	}
262
+
263
+	entity := db.Get("/webapp/db/logs")
264
+	if entity == nil {
265
+		t.Fatal("Entity should not be nil")
266
+	}
267
+	if entity.ID() != "4" {
268
+		t.Fatalf("Expected to get entity with id 4, got %s", entity.ID())
269
+	}
270
+}
271
+
272
+func TestEnitiesPaths(t *testing.T) {
273
+	db, dbpath := newTestDb(t)
274
+	defer destroyTestDb(dbpath)
275
+	_, err := db.Set("/webapp", "1")
276
+	if err != nil {
277
+		t.Fatal(err)
278
+	}
279
+	child2, err := db.Set("/db", "2")
280
+	if err != nil {
281
+		t.Fatal(err)
282
+	}
283
+	child4, err := db.Set("/logs", "4")
284
+	if err != nil {
285
+		t.Fatal(err)
286
+	}
287
+	if _, err := db.Set("/db/logs", child4.ID()); err != nil {
288
+		t.Fatal(err)
289
+	}
290
+
291
+	child3, err := db.Set("/sentry", "3")
292
+	if err != nil {
293
+		t.Fatal(err)
294
+	}
295
+	if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
296
+		t.Fatal(err)
297
+	}
298
+	if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
299
+		t.Fatal(err)
300
+	}
301
+
302
+	child5, err := db.Set("/gograph", "5")
303
+	if err != nil {
304
+		t.Fatal(err)
305
+	}
306
+	if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
307
+		t.Fatal(err)
308
+	}
309
+
310
+	out := db.List("/", -1)
311
+	for _, p := range out.Paths() {
312
+		t.Log(p)
313
+	}
314
+}
315
+
316
+func TestDeleteRootEntity(t *testing.T) {
317
+	db, dbpath := newTestDb(t)
318
+	defer destroyTestDb(dbpath)
319
+
320
+	if err := db.Delete("/"); err == nil {
321
+		t.Fatal("Error should not be nil")
322
+	}
323
+}
324
+
325
+func TestDeleteEntity(t *testing.T) {
326
+	db, dbpath := newTestDb(t)
327
+	defer destroyTestDb(dbpath)
328
+	_, err := db.Set("/webapp", "1")
329
+	if err != nil {
330
+		t.Fatal(err)
331
+	}
332
+	child2, err := db.Set("/db", "2")
333
+	if err != nil {
334
+		t.Fatal(err)
335
+	}
336
+	child4, err := db.Set("/logs", "4")
337
+	if err != nil {
338
+		t.Fatal(err)
339
+	}
340
+	if _, err := db.Set("/db/logs", child4.ID()); err != nil {
341
+		t.Fatal(err)
342
+	}
343
+
344
+	child3, err := db.Set("/sentry", "3")
345
+	if err != nil {
346
+		t.Fatal(err)
347
+	}
348
+	if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
349
+		t.Fatal(err)
350
+	}
351
+	if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
352
+		t.Fatal(err)
353
+	}
354
+
355
+	child5, err := db.Set("/gograph", "5")
356
+	if err != nil {
357
+		t.Fatal(err)
358
+	}
359
+	if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
360
+		t.Fatal(err)
361
+	}
362
+
363
+	if err := db.Delete("/webapp/sentry"); err != nil {
364
+		t.Fatal(err)
365
+	}
366
+	entity := db.Get("/webapp/sentry")
367
+	if entity != nil {
368
+		t.Fatal("Entity /webapp/sentry should be nil")
369
+	}
370
+}
371
+
372
+func TestCountRefs(t *testing.T) {
373
+	db, dbpath := newTestDb(t)
374
+	defer destroyTestDb(dbpath)
375
+
376
+	db.Set("/webapp", "1")
377
+
378
+	if db.Refs("1") != 1 {
379
+		t.Fatal("Expect reference count to be 1")
380
+	}
381
+
382
+	db.Set("/db", "2")
383
+	db.Set("/webapp/db", "2")
384
+	if db.Refs("2") != 2 {
385
+		t.Fatal("Expect reference count to be 2")
386
+	}
387
+}
388
+
389
+func TestPurgeId(t *testing.T) {
390
+	db, dbpath := newTestDb(t)
391
+	defer destroyTestDb(dbpath)
392
+
393
+	db.Set("/webapp", "1")
394
+
395
+	if db.Refs("1") != 1 {
396
+		t.Fatal("Expect reference count to be 1")
397
+	}
398
+
399
+	db.Set("/db", "2")
400
+	db.Set("/webapp/db", "2")
401
+
402
+	count, err := db.Purge("2")
403
+	if err != nil {
404
+		t.Fatal(err)
405
+	}
406
+	if count != 2 {
407
+		t.Fatal("Expected 2 references to be removed")
408
+	}
409
+}
410
+
411
+func TestRename(t *testing.T) {
412
+	db, dbpath := newTestDb(t)
413
+	defer destroyTestDb(dbpath)
414
+
415
+	db.Set("/webapp", "1")
416
+
417
+	if db.Refs("1") != 1 {
418
+		t.Fatal("Expect reference count to be 1")
419
+	}
420
+
421
+	db.Set("/db", "2")
422
+	db.Set("/webapp/db", "2")
423
+
424
+	if db.Get("/webapp/db") == nil {
425
+		t.Fatal("Cannot find entity at path /webapp/db")
426
+	}
427
+
428
+	if err := db.Rename("/webapp/db", "/webapp/newdb"); err != nil {
429
+		t.Fatal(err)
430
+	}
431
+	if db.Get("/webapp/db") != nil {
432
+		t.Fatal("Entity should not exist at /webapp/db")
433
+	}
434
+	if db.Get("/webapp/newdb") == nil {
435
+		t.Fatal("Cannot find entity at path /webapp/newdb")
436
+	}
437
+
438
+}
439
+
440
+func TestCreateMultipleNames(t *testing.T) {
441
+	db, dbpath := newTestDb(t)
442
+	defer destroyTestDb(dbpath)
443
+
444
+	db.Set("/db", "1")
445
+	if _, err := db.Set("/myapp", "1"); err != nil {
446
+		t.Fatal(err)
447
+	}
448
+
449
+	db.Walk("/", func(p string, e *Entity) error {
450
+		t.Logf("%s\n", p)
451
+		return nil
452
+	}, -1)
453
+}
454
+
455
+func TestRefPaths(t *testing.T) {
456
+	db, dbpath := newTestDb(t)
457
+	defer destroyTestDb(dbpath)
458
+
459
+	db.Set("/webapp", "1")
460
+
461
+	db.Set("/db", "2")
462
+	db.Set("/webapp/db", "2")
463
+
464
+	refs := db.RefPaths("2")
465
+	if len(refs) != 2 {
466
+		t.Fatalf("Expected reference count to be 2, got %d", len(refs))
467
+	}
468
+}
469
+
470
+func TestExistsTrue(t *testing.T) {
471
+	db, dbpath := newTestDb(t)
472
+	defer destroyTestDb(dbpath)
473
+
474
+	db.Set("/testing", "1")
475
+
476
+	if !db.Exists("/testing") {
477
+		t.Fatalf("/tesing should exist")
478
+	}
479
+}
480
+
481
+func TestExistsFalse(t *testing.T) {
482
+	db, dbpath := newTestDb(t)
483
+	defer destroyTestDb(dbpath)
484
+
485
+	db.Set("/toerhe", "1")
486
+
487
+	if db.Exists("/testing") {
488
+		t.Fatalf("/tesing should not exist")
489
+	}
490
+
491
+}
492
+
493
+func TestGetNameWithTrailingSlash(t *testing.T) {
494
+	db, dbpath := newTestDb(t)
495
+	defer destroyTestDb(dbpath)
496
+
497
+	db.Set("/todo", "1")
498
+
499
+	e := db.Get("/todo/")
500
+	if e == nil {
501
+		t.Fatalf("Entity should not be nil")
502
+	}
503
+}
504
+
505
+func TestConcurrentWrites(t *testing.T) {
506
+	db, dbpath := newTestDb(t)
507
+	defer destroyTestDb(dbpath)
508
+
509
+	errs := make(chan error, 2)
510
+
511
+	save := func(name string, id string) {
512
+		if _, err := db.Set(fmt.Sprintf("/%s", name), id); err != nil {
513
+			errs <- err
514
+		}
515
+		errs <- nil
516
+	}
517
+	purge := func(id string) {
518
+		if _, err := db.Purge(id); err != nil {
519
+			errs <- err
520
+		}
521
+		errs <- nil
522
+	}
523
+
524
+	save("/1", "1")
525
+
526
+	go purge("1")
527
+	go save("/2", "2")
528
+
529
+	any := false
530
+	for i := 0; i < 2; i++ {
531
+		if err := <-errs; err != nil {
532
+			any = true
533
+			t.Log(err)
534
+		}
535
+	}
536
+	if any {
537
+		t.Fatal()
538
+	}
539
+}
0 540
new file mode 100644
... ...
@@ -0,0 +1,27 @@
0
+package graphdb
1
+
2
+import "sort"
3
+
4
+type pathSorter struct {
5
+	paths []string
6
+	by    func(i, j string) bool
7
+}
8
+
9
+func sortByDepth(paths []string) {
10
+	s := &pathSorter{paths, func(i, j string) bool {
11
+		return PathDepth(i) > PathDepth(j)
12
+	}}
13
+	sort.Sort(s)
14
+}
15
+
16
+func (s *pathSorter) Len() int {
17
+	return len(s.paths)
18
+}
19
+
20
+func (s *pathSorter) Swap(i, j int) {
21
+	s.paths[i], s.paths[j] = s.paths[j], s.paths[i]
22
+}
23
+
24
+func (s *pathSorter) Less(i, j int) bool {
25
+	return s.by(s.paths[i], s.paths[j])
26
+}
0 27
new file mode 100644
... ...
@@ -0,0 +1,29 @@
0
+package graphdb
1
+
2
+import (
3
+	"testing"
4
+)
5
+
6
+func TestSort(t *testing.T) {
7
+	paths := []string{
8
+		"/",
9
+		"/myreallylongname",
10
+		"/app/db",
11
+	}
12
+
13
+	sortByDepth(paths)
14
+
15
+	if len(paths) != 3 {
16
+		t.Fatalf("Expected 3 parts got %d", len(paths))
17
+	}
18
+
19
+	if paths[0] != "/app/db" {
20
+		t.Fatalf("Expected /app/db got %s", paths[0])
21
+	}
22
+	if paths[1] != "/myreallylongname" {
23
+		t.Fatalf("Expected /myreallylongname got %s", paths[1])
24
+	}
25
+	if paths[2] != "/" {
26
+		t.Fatalf("Expected / got %s", paths[2])
27
+	}
28
+}
0 29
new file mode 100644
... ...
@@ -0,0 +1,32 @@
0
+package graphdb
1
+
2
+import (
3
+	"path"
4
+	"strings"
5
+)
6
+
7
+// Split p on /
8
+func split(p string) []string {
9
+	return strings.Split(p, "/")
10
+}
11
+
12
+// Returns the depth or number of / in a given path
13
+func PathDepth(p string) int {
14
+	parts := split(p)
15
+	if len(parts) == 2 && parts[1] == "" {
16
+		return 1
17
+	}
18
+	return len(parts)
19
+}
20
+
21
+func splitPath(p string) (parent, name string) {
22
+	if p[0] != '/' {
23
+		p = "/" + p
24
+	}
25
+	parent, name = path.Split(p)
26
+	l := len(parent)
27
+	if parent[l-1] == '/' {
28
+		parent = parent[:l-1]
29
+	}
30
+	return
31
+}
... ...
@@ -4,7 +4,7 @@ import (
4 4
 	"container/list"
5 5
 	"fmt"
6 6
 	"github.com/dotcloud/docker/archive"
7
-	"github.com/dotcloud/docker/graphdb"
7
+	"github.com/dotcloud/docker/pkg/graphdb"
8 8
 	"github.com/dotcloud/docker/graphdriver"
9 9
 	"github.com/dotcloud/docker/graphdriver/aufs"
10 10
 	_ "github.com/dotcloud/docker/graphdriver/devmapper"
... ...
@@ -7,7 +7,7 @@ import (
7 7
 	"github.com/dotcloud/docker/archive"
8 8
 	"github.com/dotcloud/docker/auth"
9 9
 	"github.com/dotcloud/docker/engine"
10
-	"github.com/dotcloud/docker/graphdb"
10
+	"github.com/dotcloud/docker/pkg/graphdb"
11 11
 	"github.com/dotcloud/docker/registry"
12 12
 	"github.com/dotcloud/docker/utils"
13 13
 	"io"