Browse code

Windows: Factor out sqlite

Signed-off-by: John Howard <jhoward@microsoft.com>

John Howard authored on 2016/12/01 03:59:54
Showing 22 changed files
... ...
@@ -40,7 +40,6 @@ import (
40 40
 	"github.com/docker/docker/libcontainerd"
41 41
 	"github.com/docker/docker/migrate/v1"
42 42
 	"github.com/docker/docker/pkg/fileutils"
43
-	"github.com/docker/docker/pkg/graphdb"
44 43
 	"github.com/docker/docker/pkg/idtools"
45 44
 	"github.com/docker/docker/pkg/plugingetter"
46 45
 	"github.com/docker/docker/pkg/progress"
... ...
@@ -158,7 +157,6 @@ func (daemon *Daemon) restore() error {
158 158
 		}
159 159
 	}
160 160
 
161
-	var migrateLegacyLinks bool
162 161
 	removeContainers := make(map[string]*container.Container)
163 162
 	restartContainers := make(map[*container.Container]chan struct{})
164 163
 	activeSandboxes := make(map[string]interface{})
... ...
@@ -190,6 +188,8 @@ func (daemon *Daemon) restore() error {
190 190
 			}
191 191
 		}
192 192
 	}
193
+
194
+	var migrateLegacyLinks bool // Not relevant on Windows
193 195
 	var wg sync.WaitGroup
194 196
 	var mapLock sync.Mutex
195 197
 	for _, c := range containers {
... ...
@@ -265,24 +265,15 @@ func (daemon *Daemon) restore() error {
265 265
 		return fmt.Errorf("Error initializing network controller: %v", err)
266 266
 	}
267 267
 
268
-	// migrate any legacy links from sqlite
269
-	linkdbFile := filepath.Join(daemon.root, "linkgraph.db")
270
-	var legacyLinkDB *graphdb.Database
268
+	// Perform migration of legacy sqlite links (no-op on Windows)
271 269
 	if migrateLegacyLinks {
272
-		legacyLinkDB, err = graphdb.NewSqliteConn(linkdbFile)
273
-		if err != nil {
274
-			return fmt.Errorf("error connecting to legacy link graph DB %s, container links may be lost: %v", linkdbFile, err)
270
+		if err := daemon.sqliteMigration(containers); err != nil {
271
+			return err
275 272
 		}
276
-		defer legacyLinkDB.Close()
277 273
 	}
278 274
 
279 275
 	// Now that all the containers are registered, register the links
280 276
 	for _, c := range containers {
281
-		if migrateLegacyLinks {
282
-			if err := daemon.migrateLegacySqliteLinks(legacyLinkDB, c); err != nil {
283
-				return err
284
-			}
285
-		}
286 277
 		if err := daemon.registerLinks(c, c.HostConfig); err != nil {
287 278
 			logrus.Errorf("failed to register link for container %s: %v", c.ID, err)
288 279
 		}
... ...
@@ -1,12 +1,9 @@
1 1
 package daemon
2 2
 
3 3
 import (
4
-	"strings"
5 4
 	"sync"
6 5
 
7
-	"github.com/Sirupsen/logrus"
8 6
 	"github.com/docker/docker/container"
9
-	"github.com/docker/docker/pkg/graphdb"
10 7
 )
11 8
 
12 9
 // linkIndex stores link relationships between containers, including their specified alias
... ...
@@ -88,41 +85,3 @@ func (l *linkIndex) delete(container *container.Container) {
88 88
 	delete(l.childIdx, container)
89 89
 	l.mu.Unlock()
90 90
 }
91
-
92
-// migrateLegacySqliteLinks migrates sqlite links to use links from HostConfig
93
-// when sqlite links were used, hostConfig.Links was set to nil
94
-func (daemon *Daemon) migrateLegacySqliteLinks(db *graphdb.Database, container *container.Container) error {
95
-	// if links is populated (or an empty slice), then this isn't using sqlite links and can be skipped
96
-	if container.HostConfig == nil || container.HostConfig.Links != nil {
97
-		return nil
98
-	}
99
-
100
-	logrus.Debugf("migrating legacy sqlite link info for container: %s", container.ID)
101
-
102
-	fullName := container.Name
103
-	if fullName[0] != '/' {
104
-		fullName = "/" + fullName
105
-	}
106
-
107
-	// don't use a nil slice, this ensures that the check above will skip once the migration has completed
108
-	links := []string{}
109
-	children, err := db.Children(fullName, 0)
110
-	if err != nil {
111
-		if !strings.Contains(err.Error(), "Cannot find child for") {
112
-			return err
113
-		}
114
-		// else continue... it's ok if we didn't find any children, it'll just be nil and we can continue the migration
115
-	}
116
-
117
-	for _, child := range children {
118
-		c, err := daemon.GetContainer(child.Entity.ID())
119
-		if err != nil {
120
-			return err
121
-		}
122
-
123
-		links = append(links, c.Name+":"+child.Edge.Name)
124
-	}
125
-
126
-	container.HostConfig.Links = links
127
-	return container.WriteHostConfig()
128
-}
129 91
new file mode 100644
... ...
@@ -0,0 +1,72 @@
0
+package daemon
1
+
2
+import (
3
+	"fmt"
4
+	"path/filepath"
5
+	"strings"
6
+
7
+	"github.com/Sirupsen/logrus"
8
+	"github.com/docker/docker/container"
9
+	"github.com/docker/docker/pkg/graphdb"
10
+)
11
+
12
+// migrateLegacySqliteLinks migrates sqlite links to use links from HostConfig
13
+// when sqlite links were used, hostConfig.Links was set to nil
14
+func (daemon *Daemon) migrateLegacySqliteLinks(db *graphdb.Database, container *container.Container) error {
15
+	// if links is populated (or an empty slice), then this isn't using sqlite links and can be skipped
16
+	if container.HostConfig == nil || container.HostConfig.Links != nil {
17
+		return nil
18
+	}
19
+
20
+	logrus.Debugf("migrating legacy sqlite link info for container: %s", container.ID)
21
+
22
+	fullName := container.Name
23
+	if fullName[0] != '/' {
24
+		fullName = "/" + fullName
25
+	}
26
+
27
+	// don't use a nil slice, this ensures that the check above will skip once the migration has completed
28
+	links := []string{}
29
+	children, err := db.Children(fullName, 0)
30
+	if err != nil {
31
+		if !strings.Contains(err.Error(), "Cannot find child for") {
32
+			return err
33
+		}
34
+		// else continue... it's ok if we didn't find any children, it'll just be nil and we can continue the migration
35
+	}
36
+
37
+	for _, child := range children {
38
+		c, err := daemon.GetContainer(child.Entity.ID())
39
+		if err != nil {
40
+			return err
41
+		}
42
+
43
+		links = append(links, c.Name+":"+child.Edge.Name)
44
+	}
45
+
46
+	container.HostConfig.Links = links
47
+	return container.WriteHostConfig()
48
+}
49
+
50
+// sqliteMigration performs the link graph DB migration.
51
+func (daemon *Daemon) sqliteMigration(containers map[string]*container.Container) error {
52
+	// migrate any legacy links from sqlite
53
+	linkdbFile := filepath.Join(daemon.root, "linkgraph.db")
54
+	var (
55
+		legacyLinkDB *graphdb.Database
56
+		err          error
57
+	)
58
+
59
+	legacyLinkDB, err = graphdb.NewSqliteConn(linkdbFile)
60
+	if err != nil {
61
+		return fmt.Errorf("error connecting to legacy link graph DB %s, container links may be lost: %v", linkdbFile, err)
62
+	}
63
+	defer legacyLinkDB.Close()
64
+
65
+	for _, c := range containers {
66
+		if err := daemon.migrateLegacySqliteLinks(legacyLinkDB, c); err != nil {
67
+			return err
68
+		}
69
+	}
70
+	return nil
71
+}
0 72
new file mode 100644
... ...
@@ -0,0 +1,98 @@
0
+package daemon
1
+
2
+import (
3
+	"encoding/json"
4
+	"io/ioutil"
5
+	"os"
6
+	"path"
7
+	"path/filepath"
8
+	"testing"
9
+
10
+	containertypes "github.com/docker/docker/api/types/container"
11
+	"github.com/docker/docker/container"
12
+	"github.com/docker/docker/pkg/graphdb"
13
+	"github.com/docker/docker/pkg/stringid"
14
+)
15
+
16
+func TestMigrateLegacySqliteLinks(t *testing.T) {
17
+	tmpDir, err := ioutil.TempDir("", "legacy-qlite-links-test")
18
+	if err != nil {
19
+		t.Fatal(err)
20
+	}
21
+	defer os.RemoveAll(tmpDir)
22
+
23
+	name1 := "test1"
24
+	c1 := &container.Container{
25
+		CommonContainer: container.CommonContainer{
26
+			ID:         stringid.GenerateNonCryptoID(),
27
+			Name:       name1,
28
+			HostConfig: &containertypes.HostConfig{},
29
+		},
30
+	}
31
+	c1.Root = tmpDir
32
+
33
+	name2 := "test2"
34
+	c2 := &container.Container{
35
+		CommonContainer: container.CommonContainer{
36
+			ID:   stringid.GenerateNonCryptoID(),
37
+			Name: name2,
38
+		},
39
+	}
40
+
41
+	store := container.NewMemoryStore()
42
+	store.Add(c1.ID, c1)
43
+	store.Add(c2.ID, c2)
44
+
45
+	d := &Daemon{root: tmpDir, containers: store}
46
+	db, err := graphdb.NewSqliteConn(filepath.Join(d.root, "linkgraph.db"))
47
+	if err != nil {
48
+		t.Fatal(err)
49
+	}
50
+
51
+	if _, err := db.Set("/"+name1, c1.ID); err != nil {
52
+		t.Fatal(err)
53
+	}
54
+
55
+	if _, err := db.Set("/"+name2, c2.ID); err != nil {
56
+		t.Fatal(err)
57
+	}
58
+
59
+	alias := "hello"
60
+	if _, err := db.Set(path.Join(c1.Name, alias), c2.ID); err != nil {
61
+		t.Fatal(err)
62
+	}
63
+
64
+	if err := d.migrateLegacySqliteLinks(db, c1); err != nil {
65
+		t.Fatal(err)
66
+	}
67
+
68
+	if len(c1.HostConfig.Links) != 1 {
69
+		t.Fatal("expected links to be populated but is empty")
70
+	}
71
+
72
+	expected := name2 + ":" + alias
73
+	actual := c1.HostConfig.Links[0]
74
+	if actual != expected {
75
+		t.Fatalf("got wrong link value, expected: %q, got: %q", expected, actual)
76
+	}
77
+
78
+	// ensure this is persisted
79
+	b, err := ioutil.ReadFile(filepath.Join(c1.Root, "hostconfig.json"))
80
+	if err != nil {
81
+		t.Fatal(err)
82
+	}
83
+	type hc struct {
84
+		Links []string
85
+	}
86
+	var cfg hc
87
+	if err := json.Unmarshal(b, &cfg); err != nil {
88
+		t.Fatal(err)
89
+	}
90
+
91
+	if len(cfg.Links) != 1 {
92
+		t.Fatalf("expected one entry in links, got: %d", len(cfg.Links))
93
+	}
94
+	if cfg.Links[0] != expected { // same expected as above
95
+		t.Fatalf("got wrong link value, expected: %q, got: %q", expected, cfg.Links[0])
96
+	}
97
+}
0 98
new file mode 100644
... ...
@@ -0,0 +1,10 @@
0
+// +build !linux
1
+
2
+package daemon
3
+
4
+import "github.com/docker/docker/container"
5
+
6
+// sqliteMigration performs the link graph DB migration. No-op on platforms other than Linux
7
+func (daemon *Daemon) sqliteMigration(_ map[string]*container.Container) error {
8
+	return nil
9
+}
0 10
deleted file mode 100644
... ...
@@ -1,98 +0,0 @@
1
-package daemon
2
-
3
-import (
4
-	"encoding/json"
5
-	"io/ioutil"
6
-	"os"
7
-	"path"
8
-	"path/filepath"
9
-	"testing"
10
-
11
-	containertypes "github.com/docker/docker/api/types/container"
12
-	"github.com/docker/docker/container"
13
-	"github.com/docker/docker/pkg/graphdb"
14
-	"github.com/docker/docker/pkg/stringid"
15
-)
16
-
17
-func TestMigrateLegacySqliteLinks(t *testing.T) {
18
-	tmpDir, err := ioutil.TempDir("", "legacy-qlite-links-test")
19
-	if err != nil {
20
-		t.Fatal(err)
21
-	}
22
-	defer os.RemoveAll(tmpDir)
23
-
24
-	name1 := "test1"
25
-	c1 := &container.Container{
26
-		CommonContainer: container.CommonContainer{
27
-			ID:         stringid.GenerateNonCryptoID(),
28
-			Name:       name1,
29
-			HostConfig: &containertypes.HostConfig{},
30
-		},
31
-	}
32
-	c1.Root = tmpDir
33
-
34
-	name2 := "test2"
35
-	c2 := &container.Container{
36
-		CommonContainer: container.CommonContainer{
37
-			ID:   stringid.GenerateNonCryptoID(),
38
-			Name: name2,
39
-		},
40
-	}
41
-
42
-	store := container.NewMemoryStore()
43
-	store.Add(c1.ID, c1)
44
-	store.Add(c2.ID, c2)
45
-
46
-	d := &Daemon{root: tmpDir, containers: store}
47
-	db, err := graphdb.NewSqliteConn(filepath.Join(d.root, "linkgraph.db"))
48
-	if err != nil {
49
-		t.Fatal(err)
50
-	}
51
-
52
-	if _, err := db.Set("/"+name1, c1.ID); err != nil {
53
-		t.Fatal(err)
54
-	}
55
-
56
-	if _, err := db.Set("/"+name2, c2.ID); err != nil {
57
-		t.Fatal(err)
58
-	}
59
-
60
-	alias := "hello"
61
-	if _, err := db.Set(path.Join(c1.Name, alias), c2.ID); err != nil {
62
-		t.Fatal(err)
63
-	}
64
-
65
-	if err := d.migrateLegacySqliteLinks(db, c1); err != nil {
66
-		t.Fatal(err)
67
-	}
68
-
69
-	if len(c1.HostConfig.Links) != 1 {
70
-		t.Fatal("expected links to be populated but is empty")
71
-	}
72
-
73
-	expected := name2 + ":" + alias
74
-	actual := c1.HostConfig.Links[0]
75
-	if actual != expected {
76
-		t.Fatalf("got wrong link value, expected: %q, got: %q", expected, actual)
77
-	}
78
-
79
-	// ensure this is persisted
80
-	b, err := ioutil.ReadFile(filepath.Join(c1.Root, "hostconfig.json"))
81
-	if err != nil {
82
-		t.Fatal(err)
83
-	}
84
-	type hc struct {
85
-		Links []string
86
-	}
87
-	var cfg hc
88
-	if err := json.Unmarshal(b, &cfg); err != nil {
89
-		t.Fatal(err)
90
-	}
91
-
92
-	if len(cfg.Links) != 1 {
93
-		t.Fatalf("expected one entry in links, got: %d", len(cfg.Links))
94
-	}
95
-	if cfg.Links[0] != expected { // same expected as above
96
-		t.Fatalf("got wrong link value, expected: %q, got: %q", expected, cfg.Links[0])
97
-	}
98
-}
99 1
deleted file mode 100644
... ...
@@ -1,15 +0,0 @@
1
-// +build cgo
2
-
3
-package graphdb
4
-
5
-import "database/sql"
6
-
7
-// NewSqliteConn opens a connection to a sqlite
8
-// database.
9
-func NewSqliteConn(root string) (*Database, error) {
10
-	conn, err := sql.Open("sqlite3", root)
11
-	if err != nil {
12
-		return nil, err
13
-	}
14
-	return NewDatabase(conn)
15
-}
16 1
new file mode 100644
... ...
@@ -0,0 +1,19 @@
0
+// +build cgo
1
+
2
+package graphdb
3
+
4
+import (
5
+	"database/sql"
6
+
7
+	_ "github.com/mattn/go-sqlite3" // registers sqlite
8
+)
9
+
10
+// NewSqliteConn opens a connection to a sqlite
11
+// database.
12
+func NewSqliteConn(root string) (*Database, error) {
13
+	conn, err := sql.Open("sqlite3", root)
14
+	if err != nil {
15
+		return nil, err
16
+	}
17
+	return NewDatabase(conn)
18
+}
0 19
deleted file mode 100644
... ...
@@ -1,7 +0,0 @@
1
-// +build cgo,!windows
2
-
3
-package graphdb
4
-
5
-import (
6
-	_ "github.com/mattn/go-sqlite3" // registers sqlite
7
-)
8 1
deleted file mode 100644
... ...
@@ -1,7 +0,0 @@
1
-// +build cgo,windows
2
-
3
-package graphdb
4
-
5
-import (
6
-	_ "github.com/mattn/go-sqlite3" // registers sqlite
7
-)
8 1
deleted file mode 100644
... ...
@@ -1,8 +0,0 @@
1
-// +build !cgo
2
-
3
-package graphdb
4
-
5
-// NewSqliteConn return a new sqlite connection.
6
-func NewSqliteConn(root string) (*Database, error) {
7
-	panic("Not implemented")
8
-}
9 1
deleted file mode 100644
... ...
@@ -1,551 +0,0 @@
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
-}
552 1
new file mode 100644
... ...
@@ -0,0 +1,551 @@
0
+package graphdb
1
+
2
+import (
3
+	"database/sql"
4
+	"fmt"
5
+	"path"
6
+	"strings"
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
+// Entities stores the list of entities.
44
+type Entities map[string]*Entity
45
+
46
+// Edges stores the relationships between entities.
47
+type Edges []*Edge
48
+
49
+// WalkFunc is a function invoked to process an individual entity.
50
+type WalkFunc func(fullPath string, entity *Entity) error
51
+
52
+// Database is a graph database for storing entities and their relationships.
53
+type Database struct {
54
+	conn *sql.DB
55
+	mux  sync.RWMutex
56
+}
57
+
58
+// IsNonUniqueNameError processes the error to check if it's caused by
59
+// a constraint violation.
60
+// This is necessary because the error isn't the same across various
61
+// sqlite versions.
62
+func IsNonUniqueNameError(err error) bool {
63
+	str := err.Error()
64
+	// sqlite 3.7.17-1ubuntu1 returns:
65
+	// Set failure: Abort due to constraint violation: columns parent_id, name are not unique
66
+	if strings.HasSuffix(str, "name are not unique") {
67
+		return true
68
+	}
69
+	// sqlite-3.8.3-1.fc20 returns:
70
+	// Set failure: Abort due to constraint violation: UNIQUE constraint failed: edge.parent_id, edge.name
71
+	if strings.Contains(str, "UNIQUE constraint failed") && strings.Contains(str, "edge.name") {
72
+		return true
73
+	}
74
+	// sqlite-3.6.20-1.el6 returns:
75
+	// Set failure: Abort due to constraint violation: constraint failed
76
+	if strings.HasSuffix(str, "constraint failed") {
77
+		return true
78
+	}
79
+	return false
80
+}
81
+
82
+// NewDatabase creates a new graph database initialized with a root entity.
83
+func NewDatabase(conn *sql.DB) (*Database, error) {
84
+	if conn == nil {
85
+		return nil, fmt.Errorf("Database connection cannot be nil")
86
+	}
87
+	db := &Database{conn: conn}
88
+
89
+	// Create root entities
90
+	tx, err := conn.Begin()
91
+	if err != nil {
92
+		return nil, err
93
+	}
94
+
95
+	if _, err := tx.Exec(createEntityTable); err != nil {
96
+		return nil, err
97
+	}
98
+	if _, err := tx.Exec(createEdgeTable); err != nil {
99
+		return nil, err
100
+	}
101
+	if _, err := tx.Exec(createEdgeIndices); err != nil {
102
+		return nil, err
103
+	}
104
+
105
+	if _, err := tx.Exec("DELETE FROM entity where id = ?", "0"); err != nil {
106
+		tx.Rollback()
107
+		return nil, err
108
+	}
109
+
110
+	if _, err := tx.Exec("INSERT INTO entity (id) VALUES (?);", "0"); err != nil {
111
+		tx.Rollback()
112
+		return nil, err
113
+	}
114
+
115
+	if _, err := tx.Exec("DELETE FROM edge where entity_id=? and name=?", "0", "/"); err != nil {
116
+		tx.Rollback()
117
+		return nil, err
118
+	}
119
+
120
+	if _, err := tx.Exec("INSERT INTO edge (entity_id, name) VALUES(?,?);", "0", "/"); err != nil {
121
+		tx.Rollback()
122
+		return nil, err
123
+	}
124
+
125
+	if err := tx.Commit(); err != nil {
126
+		return nil, err
127
+	}
128
+
129
+	return db, nil
130
+}
131
+
132
+// Close the underlying connection to the database.
133
+func (db *Database) Close() error {
134
+	return db.conn.Close()
135
+}
136
+
137
+// Set the entity id for a given path.
138
+func (db *Database) Set(fullPath, id string) (*Entity, error) {
139
+	db.mux.Lock()
140
+	defer db.mux.Unlock()
141
+
142
+	tx, err := db.conn.Begin()
143
+	if err != nil {
144
+		return nil, err
145
+	}
146
+
147
+	var entityID string
148
+	if err := tx.QueryRow("SELECT id FROM entity WHERE id = ?;", id).Scan(&entityID); err != nil {
149
+		if err == sql.ErrNoRows {
150
+			if _, err := tx.Exec("INSERT INTO entity (id) VALUES(?);", id); err != nil {
151
+				tx.Rollback()
152
+				return nil, err
153
+			}
154
+		} else {
155
+			tx.Rollback()
156
+			return nil, err
157
+		}
158
+	}
159
+	e := &Entity{id}
160
+
161
+	parentPath, name := splitPath(fullPath)
162
+	if err := db.setEdge(parentPath, name, e, tx); err != nil {
163
+		tx.Rollback()
164
+		return nil, err
165
+	}
166
+
167
+	if err := tx.Commit(); err != nil {
168
+		return nil, err
169
+	}
170
+	return e, nil
171
+}
172
+
173
+// Exists returns true if a name already exists in the database.
174
+func (db *Database) Exists(name string) bool {
175
+	db.mux.RLock()
176
+	defer db.mux.RUnlock()
177
+
178
+	e, err := db.get(name)
179
+	if err != nil {
180
+		return false
181
+	}
182
+	return e != nil
183
+}
184
+
185
+func (db *Database) setEdge(parentPath, name string, e *Entity, tx *sql.Tx) error {
186
+	parent, err := db.get(parentPath)
187
+	if err != nil {
188
+		return err
189
+	}
190
+	if parent.id == e.id {
191
+		return fmt.Errorf("Cannot set self as child")
192
+	}
193
+
194
+	if _, err := tx.Exec("INSERT INTO edge (parent_id, name, entity_id) VALUES (?,?,?);", parent.id, name, e.id); err != nil {
195
+		return err
196
+	}
197
+	return nil
198
+}
199
+
200
+// RootEntity returns the root "/" entity for the database.
201
+func (db *Database) RootEntity() *Entity {
202
+	return &Entity{
203
+		id: "0",
204
+	}
205
+}
206
+
207
+// Get returns the entity for a given path.
208
+func (db *Database) Get(name string) *Entity {
209
+	db.mux.RLock()
210
+	defer db.mux.RUnlock()
211
+
212
+	e, err := db.get(name)
213
+	if err != nil {
214
+		return nil
215
+	}
216
+	return e
217
+}
218
+
219
+func (db *Database) get(name string) (*Entity, error) {
220
+	e := db.RootEntity()
221
+	// We always know the root name so return it if
222
+	// it is requested
223
+	if name == "/" {
224
+		return e, nil
225
+	}
226
+
227
+	parts := split(name)
228
+	for i := 1; i < len(parts); i++ {
229
+		p := parts[i]
230
+		if p == "" {
231
+			continue
232
+		}
233
+
234
+		next := db.child(e, p)
235
+		if next == nil {
236
+			return nil, fmt.Errorf("Cannot find child for %s", name)
237
+		}
238
+		e = next
239
+	}
240
+	return e, nil
241
+
242
+}
243
+
244
+// List all entities by from the name.
245
+// The key will be the full path of the entity.
246
+func (db *Database) List(name string, depth int) Entities {
247
+	db.mux.RLock()
248
+	defer db.mux.RUnlock()
249
+
250
+	out := Entities{}
251
+	e, err := db.get(name)
252
+	if err != nil {
253
+		return out
254
+	}
255
+
256
+	children, err := db.children(e, name, depth, nil)
257
+	if err != nil {
258
+		return out
259
+	}
260
+
261
+	for _, c := range children {
262
+		out[c.FullPath] = c.Entity
263
+	}
264
+	return out
265
+}
266
+
267
+// Walk through the child graph of an entity, calling walkFunc for each child entity.
268
+// It is safe for walkFunc to call graph functions.
269
+func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error {
270
+	children, err := db.Children(name, depth)
271
+	if err != nil {
272
+		return err
273
+	}
274
+
275
+	// Note: the database lock must not be held while calling walkFunc
276
+	for _, c := range children {
277
+		if err := walkFunc(c.FullPath, c.Entity); err != nil {
278
+			return err
279
+		}
280
+	}
281
+	return nil
282
+}
283
+
284
+// Children returns the children of the specified entity.
285
+func (db *Database) Children(name string, depth int) ([]WalkMeta, error) {
286
+	db.mux.RLock()
287
+	defer db.mux.RUnlock()
288
+
289
+	e, err := db.get(name)
290
+	if err != nil {
291
+		return nil, err
292
+	}
293
+
294
+	return db.children(e, name, depth, nil)
295
+}
296
+
297
+// Parents returns the parents of a specified entity.
298
+func (db *Database) Parents(name string) ([]string, error) {
299
+	db.mux.RLock()
300
+	defer db.mux.RUnlock()
301
+
302
+	e, err := db.get(name)
303
+	if err != nil {
304
+		return nil, err
305
+	}
306
+	return db.parents(e)
307
+}
308
+
309
+// Refs returns the reference count for a specified id.
310
+func (db *Database) Refs(id string) int {
311
+	db.mux.RLock()
312
+	defer db.mux.RUnlock()
313
+
314
+	var count int
315
+	if err := db.conn.QueryRow("SELECT COUNT(*) FROM edge WHERE entity_id = ?;", id).Scan(&count); err != nil {
316
+		return 0
317
+	}
318
+	return count
319
+}
320
+
321
+// RefPaths returns all the id's path references.
322
+func (db *Database) RefPaths(id string) Edges {
323
+	db.mux.RLock()
324
+	defer db.mux.RUnlock()
325
+
326
+	refs := Edges{}
327
+
328
+	rows, err := db.conn.Query("SELECT name, parent_id FROM edge WHERE entity_id = ?;", id)
329
+	if err != nil {
330
+		return refs
331
+	}
332
+	defer rows.Close()
333
+
334
+	for rows.Next() {
335
+		var name string
336
+		var parentID string
337
+		if err := rows.Scan(&name, &parentID); err != nil {
338
+			return refs
339
+		}
340
+		refs = append(refs, &Edge{
341
+			EntityID: id,
342
+			Name:     name,
343
+			ParentID: parentID,
344
+		})
345
+	}
346
+	return refs
347
+}
348
+
349
+// Delete the reference to an entity at a given path.
350
+func (db *Database) Delete(name string) error {
351
+	db.mux.Lock()
352
+	defer db.mux.Unlock()
353
+
354
+	if name == "/" {
355
+		return fmt.Errorf("Cannot delete root entity")
356
+	}
357
+
358
+	parentPath, n := splitPath(name)
359
+	parent, err := db.get(parentPath)
360
+	if err != nil {
361
+		return err
362
+	}
363
+
364
+	if _, err := db.conn.Exec("DELETE FROM edge WHERE parent_id = ? AND name = ?;", parent.id, n); err != nil {
365
+		return err
366
+	}
367
+	return nil
368
+}
369
+
370
+// Purge removes the entity with the specified id
371
+// Walk the graph to make sure all references to the entity
372
+// are removed and return the number of references removed
373
+func (db *Database) Purge(id string) (int, error) {
374
+	db.mux.Lock()
375
+	defer db.mux.Unlock()
376
+
377
+	tx, err := db.conn.Begin()
378
+	if err != nil {
379
+		return -1, err
380
+	}
381
+
382
+	// Delete all edges
383
+	rows, err := tx.Exec("DELETE FROM edge WHERE entity_id = ?;", id)
384
+	if err != nil {
385
+		tx.Rollback()
386
+		return -1, err
387
+	}
388
+	changes, err := rows.RowsAffected()
389
+	if err != nil {
390
+		return -1, err
391
+	}
392
+
393
+	// Clear who's using this id as parent
394
+	refs, err := tx.Exec("DELETE FROM edge WHERE parent_id = ?;", id)
395
+	if err != nil {
396
+		tx.Rollback()
397
+		return -1, err
398
+	}
399
+	refsCount, err := refs.RowsAffected()
400
+	if err != nil {
401
+		return -1, err
402
+	}
403
+
404
+	// Delete entity
405
+	if _, err := tx.Exec("DELETE FROM entity where id = ?;", id); err != nil {
406
+		tx.Rollback()
407
+		return -1, err
408
+	}
409
+
410
+	if err := tx.Commit(); err != nil {
411
+		return -1, err
412
+	}
413
+
414
+	return int(changes + refsCount), nil
415
+}
416
+
417
+// Rename an edge for a given path
418
+func (db *Database) Rename(currentName, newName string) error {
419
+	db.mux.Lock()
420
+	defer db.mux.Unlock()
421
+
422
+	parentPath, name := splitPath(currentName)
423
+	newParentPath, newEdgeName := splitPath(newName)
424
+
425
+	if parentPath != newParentPath {
426
+		return fmt.Errorf("Cannot rename when root paths do not match %s != %s", parentPath, newParentPath)
427
+	}
428
+
429
+	parent, err := db.get(parentPath)
430
+	if err != nil {
431
+		return err
432
+	}
433
+
434
+	rows, err := db.conn.Exec("UPDATE edge SET name = ? WHERE parent_id = ? AND name = ?;", newEdgeName, parent.id, name)
435
+	if err != nil {
436
+		return err
437
+	}
438
+	i, err := rows.RowsAffected()
439
+	if err != nil {
440
+		return err
441
+	}
442
+	if i == 0 {
443
+		return fmt.Errorf("Cannot locate edge for %s %s", parent.id, name)
444
+	}
445
+	return nil
446
+}
447
+
448
+// WalkMeta stores the walk metadata.
449
+type WalkMeta struct {
450
+	Parent   *Entity
451
+	Entity   *Entity
452
+	FullPath string
453
+	Edge     *Edge
454
+}
455
+
456
+func (db *Database) children(e *Entity, name string, depth int, entities []WalkMeta) ([]WalkMeta, error) {
457
+	if e == nil {
458
+		return entities, nil
459
+	}
460
+
461
+	rows, err := db.conn.Query("SELECT entity_id, name FROM edge where parent_id = ?;", e.id)
462
+	if err != nil {
463
+		return nil, err
464
+	}
465
+	defer rows.Close()
466
+
467
+	for rows.Next() {
468
+		var entityID, entityName string
469
+		if err := rows.Scan(&entityID, &entityName); err != nil {
470
+			return nil, err
471
+		}
472
+		child := &Entity{entityID}
473
+		edge := &Edge{
474
+			ParentID: e.id,
475
+			Name:     entityName,
476
+			EntityID: child.id,
477
+		}
478
+
479
+		meta := WalkMeta{
480
+			Parent:   e,
481
+			Entity:   child,
482
+			FullPath: path.Join(name, edge.Name),
483
+			Edge:     edge,
484
+		}
485
+
486
+		entities = append(entities, meta)
487
+
488
+		if depth != 0 {
489
+			nDepth := depth
490
+			if depth != -1 {
491
+				nDepth--
492
+			}
493
+			entities, err = db.children(child, meta.FullPath, nDepth, entities)
494
+			if err != nil {
495
+				return nil, err
496
+			}
497
+		}
498
+	}
499
+
500
+	return entities, nil
501
+}
502
+
503
+func (db *Database) parents(e *Entity) (parents []string, err error) {
504
+	if e == nil {
505
+		return parents, nil
506
+	}
507
+
508
+	rows, err := db.conn.Query("SELECT parent_id FROM edge where entity_id = ?;", e.id)
509
+	if err != nil {
510
+		return nil, err
511
+	}
512
+	defer rows.Close()
513
+
514
+	for rows.Next() {
515
+		var parentID string
516
+		if err := rows.Scan(&parentID); err != nil {
517
+			return nil, err
518
+		}
519
+		parents = append(parents, parentID)
520
+	}
521
+
522
+	return parents, nil
523
+}
524
+
525
+// Return the entity based on the parent path and name.
526
+func (db *Database) child(parent *Entity, name string) *Entity {
527
+	var id string
528
+	if err := db.conn.QueryRow("SELECT entity_id FROM edge WHERE parent_id = ? AND name = ?;", parent.id, name).Scan(&id); err != nil {
529
+		return nil
530
+	}
531
+	return &Entity{id}
532
+}
533
+
534
+// ID returns the id used to reference this entity.
535
+func (e *Entity) ID() string {
536
+	return e.id
537
+}
538
+
539
+// Paths returns the paths sorted by depth.
540
+func (e Entities) Paths() []string {
541
+	out := make([]string, len(e))
542
+	var i int
543
+	for k := range e {
544
+		out[i] = k
545
+		i++
546
+	}
547
+	sortByDepth(out)
548
+
549
+	return out
550
+}
0 551
new file mode 100644
... ...
@@ -0,0 +1,721 @@
0
+package graphdb
1
+
2
+import (
3
+	"database/sql"
4
+	"fmt"
5
+	"os"
6
+	"path"
7
+	"runtime"
8
+	"strconv"
9
+	"testing"
10
+
11
+	_ "github.com/mattn/go-sqlite3"
12
+)
13
+
14
+func newTestDb(t *testing.T) (*Database, string) {
15
+	p := path.Join(os.TempDir(), "sqlite.db")
16
+	conn, err := sql.Open("sqlite3", p)
17
+	db, err := NewDatabase(conn)
18
+	if err != nil {
19
+		t.Fatal(err)
20
+	}
21
+	return db, p
22
+}
23
+
24
+func destroyTestDb(dbPath string) {
25
+	os.Remove(dbPath)
26
+}
27
+
28
+func TestNewDatabase(t *testing.T) {
29
+	db, dbpath := newTestDb(t)
30
+	if db == nil {
31
+		t.Fatal("Database should not be nil")
32
+	}
33
+	db.Close()
34
+	defer destroyTestDb(dbpath)
35
+}
36
+
37
+func TestCreateRootEntity(t *testing.T) {
38
+	db, dbpath := newTestDb(t)
39
+	defer destroyTestDb(dbpath)
40
+	root := db.RootEntity()
41
+	if root == nil {
42
+		t.Fatal("Root entity should not be nil")
43
+	}
44
+}
45
+
46
+func TestGetRootEntity(t *testing.T) {
47
+	db, dbpath := newTestDb(t)
48
+	defer destroyTestDb(dbpath)
49
+
50
+	e := db.Get("/")
51
+	if e == nil {
52
+		t.Fatal("Entity should not be nil")
53
+	}
54
+	if e.ID() != "0" {
55
+		t.Fatalf("Entity id should be 0, got %s", e.ID())
56
+	}
57
+}
58
+
59
+func TestSetEntityWithDifferentName(t *testing.T) {
60
+	db, dbpath := newTestDb(t)
61
+	defer destroyTestDb(dbpath)
62
+
63
+	db.Set("/test", "1")
64
+	if _, err := db.Set("/other", "1"); err != nil {
65
+		t.Fatal(err)
66
+	}
67
+}
68
+
69
+func TestSetDuplicateEntity(t *testing.T) {
70
+	db, dbpath := newTestDb(t)
71
+	defer destroyTestDb(dbpath)
72
+
73
+	if _, err := db.Set("/foo", "42"); err != nil {
74
+		t.Fatal(err)
75
+	}
76
+	if _, err := db.Set("/foo", "43"); err == nil {
77
+		t.Fatalf("Creating an entry with a duplicate path did not cause an error")
78
+	}
79
+}
80
+
81
+func TestCreateChild(t *testing.T) {
82
+	db, dbpath := newTestDb(t)
83
+	defer destroyTestDb(dbpath)
84
+
85
+	child, err := db.Set("/db", "1")
86
+	if err != nil {
87
+		t.Fatal(err)
88
+	}
89
+	if child == nil {
90
+		t.Fatal("Child should not be nil")
91
+	}
92
+	if child.ID() != "1" {
93
+		t.Fail()
94
+	}
95
+}
96
+
97
+func TestParents(t *testing.T) {
98
+	db, dbpath := newTestDb(t)
99
+	defer destroyTestDb(dbpath)
100
+
101
+	for i := 1; i < 6; i++ {
102
+		a := strconv.Itoa(i)
103
+		if _, err := db.Set("/"+a, a); err != nil {
104
+			t.Fatal(err)
105
+		}
106
+	}
107
+
108
+	for i := 6; i < 11; i++ {
109
+		a := strconv.Itoa(i)
110
+		p := strconv.Itoa(i - 5)
111
+
112
+		key := fmt.Sprintf("/%s/%s", p, a)
113
+
114
+		if _, err := db.Set(key, a); err != nil {
115
+			t.Fatal(err)
116
+		}
117
+
118
+		parents, err := db.Parents(key)
119
+		if err != nil {
120
+			t.Fatal(err)
121
+		}
122
+
123
+		if len(parents) != 1 {
124
+			t.Fatalf("Expected 1 entry for %s got %d", key, len(parents))
125
+		}
126
+
127
+		if parents[0] != p {
128
+			t.Fatalf("ID %s received, %s expected", parents[0], p)
129
+		}
130
+	}
131
+}
132
+
133
+func TestChildren(t *testing.T) {
134
+	// TODO Windows: Port this test
135
+	if runtime.GOOS == "windows" {
136
+		t.Skip("Needs porting to Windows")
137
+	}
138
+	db, dbpath := newTestDb(t)
139
+	defer destroyTestDb(dbpath)
140
+
141
+	str := "/"
142
+	for i := 1; i < 6; i++ {
143
+		a := strconv.Itoa(i)
144
+		if _, err := db.Set(str+a, a); err != nil {
145
+			t.Fatal(err)
146
+		}
147
+
148
+		str = str + a + "/"
149
+	}
150
+
151
+	str = "/"
152
+	for i := 10; i < 30; i++ { // 20 entities
153
+		a := strconv.Itoa(i)
154
+		if _, err := db.Set(str+a, a); err != nil {
155
+			t.Fatal(err)
156
+		}
157
+
158
+		str = str + a + "/"
159
+	}
160
+	entries, err := db.Children("/", 5)
161
+	if err != nil {
162
+		t.Fatal(err)
163
+	}
164
+
165
+	if len(entries) != 11 {
166
+		t.Fatalf("Expect 11 entries for / got %d", len(entries))
167
+	}
168
+
169
+	entries, err = db.Children("/", 20)
170
+	if err != nil {
171
+		t.Fatal(err)
172
+	}
173
+
174
+	if len(entries) != 25 {
175
+		t.Fatalf("Expect 25 entries for / got %d", len(entries))
176
+	}
177
+}
178
+
179
+func TestListAllRootChildren(t *testing.T) {
180
+	// TODO Windows: Port this test
181
+	if runtime.GOOS == "windows" {
182
+		t.Skip("Needs porting to Windows")
183
+	}
184
+
185
+	db, dbpath := newTestDb(t)
186
+	defer destroyTestDb(dbpath)
187
+
188
+	for i := 1; i < 6; i++ {
189
+		a := strconv.Itoa(i)
190
+		if _, err := db.Set("/"+a, a); err != nil {
191
+			t.Fatal(err)
192
+		}
193
+	}
194
+	entries := db.List("/", -1)
195
+	if len(entries) != 5 {
196
+		t.Fatalf("Expect 5 entries for / got %d", len(entries))
197
+	}
198
+}
199
+
200
+func TestListAllSubChildren(t *testing.T) {
201
+	// TODO Windows: Port this test
202
+	if runtime.GOOS == "windows" {
203
+		t.Skip("Needs porting to Windows")
204
+	}
205
+	db, dbpath := newTestDb(t)
206
+	defer destroyTestDb(dbpath)
207
+
208
+	_, err := db.Set("/webapp", "1")
209
+	if err != nil {
210
+		t.Fatal(err)
211
+	}
212
+	child2, err := db.Set("/db", "2")
213
+	if err != nil {
214
+		t.Fatal(err)
215
+	}
216
+	child4, err := db.Set("/logs", "4")
217
+	if err != nil {
218
+		t.Fatal(err)
219
+	}
220
+	if _, err := db.Set("/db/logs", child4.ID()); err != nil {
221
+		t.Fatal(err)
222
+	}
223
+
224
+	child3, err := db.Set("/sentry", "3")
225
+	if err != nil {
226
+		t.Fatal(err)
227
+	}
228
+	if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
229
+		t.Fatal(err)
230
+	}
231
+	if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
232
+		t.Fatal(err)
233
+	}
234
+
235
+	entries := db.List("/webapp", 1)
236
+	if len(entries) != 3 {
237
+		t.Fatalf("Expect 3 entries for / got %d", len(entries))
238
+	}
239
+
240
+	entries = db.List("/webapp", 0)
241
+	if len(entries) != 2 {
242
+		t.Fatalf("Expect 2 entries for / got %d", len(entries))
243
+	}
244
+}
245
+
246
+func TestAddSelfAsChild(t *testing.T) {
247
+	// TODO Windows: Port this test
248
+	if runtime.GOOS == "windows" {
249
+		t.Skip("Needs porting to Windows")
250
+	}
251
+	db, dbpath := newTestDb(t)
252
+	defer destroyTestDb(dbpath)
253
+
254
+	child, err := db.Set("/test", "1")
255
+	if err != nil {
256
+		t.Fatal(err)
257
+	}
258
+	if _, err := db.Set("/test/other", child.ID()); err == nil {
259
+		t.Fatal("Error should not be nil")
260
+	}
261
+}
262
+
263
+func TestAddChildToNonExistentRoot(t *testing.T) {
264
+	db, dbpath := newTestDb(t)
265
+	defer destroyTestDb(dbpath)
266
+
267
+	if _, err := db.Set("/myapp", "1"); err != nil {
268
+		t.Fatal(err)
269
+	}
270
+
271
+	if _, err := db.Set("/myapp/proxy/db", "2"); err == nil {
272
+		t.Fatal("Error should not be nil")
273
+	}
274
+}
275
+
276
+func TestWalkAll(t *testing.T) {
277
+	// TODO Windows: Port this test
278
+	if runtime.GOOS == "windows" {
279
+		t.Skip("Needs porting to Windows")
280
+	}
281
+	db, dbpath := newTestDb(t)
282
+	defer destroyTestDb(dbpath)
283
+	_, err := db.Set("/webapp", "1")
284
+	if err != nil {
285
+		t.Fatal(err)
286
+	}
287
+	child2, err := db.Set("/db", "2")
288
+	if err != nil {
289
+		t.Fatal(err)
290
+	}
291
+	child4, err := db.Set("/db/logs", "4")
292
+	if err != nil {
293
+		t.Fatal(err)
294
+	}
295
+	if _, err := db.Set("/webapp/logs", child4.ID()); err != nil {
296
+		t.Fatal(err)
297
+	}
298
+
299
+	child3, err := db.Set("/sentry", "3")
300
+	if err != nil {
301
+		t.Fatal(err)
302
+	}
303
+	if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
304
+		t.Fatal(err)
305
+	}
306
+	if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
307
+		t.Fatal(err)
308
+	}
309
+
310
+	child5, err := db.Set("/gograph", "5")
311
+	if err != nil {
312
+		t.Fatal(err)
313
+	}
314
+	if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
315
+		t.Fatal(err)
316
+	}
317
+
318
+	if err := db.Walk("/", func(p string, e *Entity) error {
319
+		t.Logf("Path: %s Entity: %s", p, e.ID())
320
+		return nil
321
+	}, -1); err != nil {
322
+		t.Fatal(err)
323
+	}
324
+}
325
+
326
+func TestGetEntityByPath(t *testing.T) {
327
+	// TODO Windows: Port this test
328
+	if runtime.GOOS == "windows" {
329
+		t.Skip("Needs porting to Windows")
330
+	}
331
+	db, dbpath := newTestDb(t)
332
+	defer destroyTestDb(dbpath)
333
+	_, err := db.Set("/webapp", "1")
334
+	if err != nil {
335
+		t.Fatal(err)
336
+	}
337
+	child2, err := db.Set("/db", "2")
338
+	if err != nil {
339
+		t.Fatal(err)
340
+	}
341
+	child4, err := db.Set("/logs", "4")
342
+	if err != nil {
343
+		t.Fatal(err)
344
+	}
345
+	if _, err := db.Set("/db/logs", child4.ID()); err != nil {
346
+		t.Fatal(err)
347
+	}
348
+
349
+	child3, err := db.Set("/sentry", "3")
350
+	if err != nil {
351
+		t.Fatal(err)
352
+	}
353
+	if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
354
+		t.Fatal(err)
355
+	}
356
+	if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
357
+		t.Fatal(err)
358
+	}
359
+
360
+	child5, err := db.Set("/gograph", "5")
361
+	if err != nil {
362
+		t.Fatal(err)
363
+	}
364
+	if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
365
+		t.Fatal(err)
366
+	}
367
+
368
+	entity := db.Get("/webapp/db/logs")
369
+	if entity == nil {
370
+		t.Fatal("Entity should not be nil")
371
+	}
372
+	if entity.ID() != "4" {
373
+		t.Fatalf("Expected to get entity with id 4, got %s", entity.ID())
374
+	}
375
+}
376
+
377
+func TestEnitiesPaths(t *testing.T) {
378
+	// TODO Windows: Port this test
379
+	if runtime.GOOS == "windows" {
380
+		t.Skip("Needs porting to Windows")
381
+	}
382
+	db, dbpath := newTestDb(t)
383
+	defer destroyTestDb(dbpath)
384
+	_, err := db.Set("/webapp", "1")
385
+	if err != nil {
386
+		t.Fatal(err)
387
+	}
388
+	child2, err := db.Set("/db", "2")
389
+	if err != nil {
390
+		t.Fatal(err)
391
+	}
392
+	child4, err := db.Set("/logs", "4")
393
+	if err != nil {
394
+		t.Fatal(err)
395
+	}
396
+	if _, err := db.Set("/db/logs", child4.ID()); err != nil {
397
+		t.Fatal(err)
398
+	}
399
+
400
+	child3, err := db.Set("/sentry", "3")
401
+	if err != nil {
402
+		t.Fatal(err)
403
+	}
404
+	if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
405
+		t.Fatal(err)
406
+	}
407
+	if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
408
+		t.Fatal(err)
409
+	}
410
+
411
+	child5, err := db.Set("/gograph", "5")
412
+	if err != nil {
413
+		t.Fatal(err)
414
+	}
415
+	if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
416
+		t.Fatal(err)
417
+	}
418
+
419
+	out := db.List("/", -1)
420
+	for _, p := range out.Paths() {
421
+		t.Log(p)
422
+	}
423
+}
424
+
425
+func TestDeleteRootEntity(t *testing.T) {
426
+	db, dbpath := newTestDb(t)
427
+	defer destroyTestDb(dbpath)
428
+
429
+	if err := db.Delete("/"); err == nil {
430
+		t.Fatal("Error should not be nil")
431
+	}
432
+}
433
+
434
+func TestDeleteEntity(t *testing.T) {
435
+	// TODO Windows: Port this test
436
+	if runtime.GOOS == "windows" {
437
+		t.Skip("Needs porting to Windows")
438
+	}
439
+	db, dbpath := newTestDb(t)
440
+	defer destroyTestDb(dbpath)
441
+	_, err := db.Set("/webapp", "1")
442
+	if err != nil {
443
+		t.Fatal(err)
444
+	}
445
+	child2, err := db.Set("/db", "2")
446
+	if err != nil {
447
+		t.Fatal(err)
448
+	}
449
+	child4, err := db.Set("/logs", "4")
450
+	if err != nil {
451
+		t.Fatal(err)
452
+	}
453
+	if _, err := db.Set("/db/logs", child4.ID()); err != nil {
454
+		t.Fatal(err)
455
+	}
456
+
457
+	child3, err := db.Set("/sentry", "3")
458
+	if err != nil {
459
+		t.Fatal(err)
460
+	}
461
+	if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
462
+		t.Fatal(err)
463
+	}
464
+	if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
465
+		t.Fatal(err)
466
+	}
467
+
468
+	child5, err := db.Set("/gograph", "5")
469
+	if err != nil {
470
+		t.Fatal(err)
471
+	}
472
+	if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
473
+		t.Fatal(err)
474
+	}
475
+
476
+	if err := db.Delete("/webapp/sentry"); err != nil {
477
+		t.Fatal(err)
478
+	}
479
+	entity := db.Get("/webapp/sentry")
480
+	if entity != nil {
481
+		t.Fatal("Entity /webapp/sentry should be nil")
482
+	}
483
+}
484
+
485
+func TestCountRefs(t *testing.T) {
486
+	// TODO Windows: Port this test
487
+	if runtime.GOOS == "windows" {
488
+		t.Skip("Needs porting to Windows")
489
+	}
490
+	db, dbpath := newTestDb(t)
491
+	defer destroyTestDb(dbpath)
492
+
493
+	db.Set("/webapp", "1")
494
+
495
+	if db.Refs("1") != 1 {
496
+		t.Fatal("Expect reference count to be 1")
497
+	}
498
+
499
+	db.Set("/db", "2")
500
+	db.Set("/webapp/db", "2")
501
+	if db.Refs("2") != 2 {
502
+		t.Fatal("Expect reference count to be 2")
503
+	}
504
+}
505
+
506
+func TestPurgeId(t *testing.T) {
507
+	// TODO Windows: Port this test
508
+	if runtime.GOOS == "windows" {
509
+		t.Skip("Needs porting to Windows")
510
+	}
511
+
512
+	db, dbpath := newTestDb(t)
513
+	defer destroyTestDb(dbpath)
514
+
515
+	db.Set("/webapp", "1")
516
+
517
+	if c := db.Refs("1"); c != 1 {
518
+		t.Fatalf("Expect reference count to be 1, got %d", c)
519
+	}
520
+
521
+	db.Set("/db", "2")
522
+	db.Set("/webapp/db", "2")
523
+
524
+	count, err := db.Purge("2")
525
+	if err != nil {
526
+		t.Fatal(err)
527
+	}
528
+	if count != 2 {
529
+		t.Fatalf("Expected 2 references to be removed, got %d", count)
530
+	}
531
+}
532
+
533
+// Regression test https://github.com/docker/docker/issues/12334
534
+func TestPurgeIdRefPaths(t *testing.T) {
535
+	// TODO Windows: Port this test
536
+	if runtime.GOOS == "windows" {
537
+		t.Skip("Needs porting to Windows")
538
+	}
539
+	db, dbpath := newTestDb(t)
540
+	defer destroyTestDb(dbpath)
541
+
542
+	db.Set("/webapp", "1")
543
+	db.Set("/db", "2")
544
+
545
+	db.Set("/db/webapp", "1")
546
+
547
+	if c := db.Refs("1"); c != 2 {
548
+		t.Fatalf("Expected 2 reference for webapp, got %d", c)
549
+	}
550
+	if c := db.Refs("2"); c != 1 {
551
+		t.Fatalf("Expected 1 reference for db, got %d", c)
552
+	}
553
+
554
+	if rp := db.RefPaths("2"); len(rp) != 1 {
555
+		t.Fatalf("Expected 1 reference path for db, got %d", len(rp))
556
+	}
557
+
558
+	count, err := db.Purge("2")
559
+	if err != nil {
560
+		t.Fatal(err)
561
+	}
562
+
563
+	if count != 2 {
564
+		t.Fatalf("Expected 2 rows to be removed, got %d", count)
565
+	}
566
+
567
+	if c := db.Refs("2"); c != 0 {
568
+		t.Fatalf("Expected 0 reference for db, got %d", c)
569
+	}
570
+	if c := db.Refs("1"); c != 1 {
571
+		t.Fatalf("Expected 1 reference for webapp, got %d", c)
572
+	}
573
+}
574
+
575
+func TestRename(t *testing.T) {
576
+	// TODO Windows: Port this test
577
+	if runtime.GOOS == "windows" {
578
+		t.Skip("Needs porting to Windows")
579
+	}
580
+	db, dbpath := newTestDb(t)
581
+	defer destroyTestDb(dbpath)
582
+
583
+	db.Set("/webapp", "1")
584
+
585
+	if db.Refs("1") != 1 {
586
+		t.Fatal("Expect reference count to be 1")
587
+	}
588
+
589
+	db.Set("/db", "2")
590
+	db.Set("/webapp/db", "2")
591
+
592
+	if db.Get("/webapp/db") == nil {
593
+		t.Fatal("Cannot find entity at path /webapp/db")
594
+	}
595
+
596
+	if err := db.Rename("/webapp/db", "/webapp/newdb"); err != nil {
597
+		t.Fatal(err)
598
+	}
599
+	if db.Get("/webapp/db") != nil {
600
+		t.Fatal("Entity should not exist at /webapp/db")
601
+	}
602
+	if db.Get("/webapp/newdb") == nil {
603
+		t.Fatal("Cannot find entity at path /webapp/newdb")
604
+	}
605
+
606
+}
607
+
608
+func TestCreateMultipleNames(t *testing.T) {
609
+	// TODO Windows: Port this test
610
+	if runtime.GOOS == "windows" {
611
+		t.Skip("Needs porting to Windows")
612
+	}
613
+
614
+	db, dbpath := newTestDb(t)
615
+	defer destroyTestDb(dbpath)
616
+
617
+	db.Set("/db", "1")
618
+	if _, err := db.Set("/myapp", "1"); err != nil {
619
+		t.Fatal(err)
620
+	}
621
+
622
+	db.Walk("/", func(p string, e *Entity) error {
623
+		t.Logf("%s\n", p)
624
+		return nil
625
+	}, -1)
626
+}
627
+
628
+func TestRefPaths(t *testing.T) {
629
+	db, dbpath := newTestDb(t)
630
+	defer destroyTestDb(dbpath)
631
+
632
+	db.Set("/webapp", "1")
633
+
634
+	db.Set("/db", "2")
635
+	db.Set("/webapp/db", "2")
636
+
637
+	refs := db.RefPaths("2")
638
+	if len(refs) != 2 {
639
+		t.Fatalf("Expected reference count to be 2, got %d", len(refs))
640
+	}
641
+}
642
+
643
+func TestExistsTrue(t *testing.T) {
644
+	db, dbpath := newTestDb(t)
645
+	defer destroyTestDb(dbpath)
646
+
647
+	db.Set("/testing", "1")
648
+
649
+	if !db.Exists("/testing") {
650
+		t.Fatalf("/tesing should exist")
651
+	}
652
+}
653
+
654
+func TestExistsFalse(t *testing.T) {
655
+	// TODO Windows: Port this test
656
+	if runtime.GOOS == "windows" {
657
+		t.Skip("Needs porting to Windows")
658
+	}
659
+	db, dbpath := newTestDb(t)
660
+	defer destroyTestDb(dbpath)
661
+
662
+	db.Set("/toerhe", "1")
663
+
664
+	if db.Exists("/testing") {
665
+		t.Fatalf("/tesing should not exist")
666
+	}
667
+
668
+}
669
+
670
+func TestGetNameWithTrailingSlash(t *testing.T) {
671
+	db, dbpath := newTestDb(t)
672
+	defer destroyTestDb(dbpath)
673
+
674
+	db.Set("/todo", "1")
675
+
676
+	e := db.Get("/todo/")
677
+	if e == nil {
678
+		t.Fatalf("Entity should not be nil")
679
+	}
680
+}
681
+
682
+func TestConcurrentWrites(t *testing.T) {
683
+	// TODO Windows: Port this test
684
+	if runtime.GOOS == "windows" {
685
+		t.Skip("Needs porting to Windows")
686
+	}
687
+	db, dbpath := newTestDb(t)
688
+	defer destroyTestDb(dbpath)
689
+
690
+	errs := make(chan error, 2)
691
+
692
+	save := func(name string, id string) {
693
+		if _, err := db.Set(fmt.Sprintf("/%s", name), id); err != nil {
694
+			errs <- err
695
+		}
696
+		errs <- nil
697
+	}
698
+	purge := func(id string) {
699
+		if _, err := db.Purge(id); err != nil {
700
+			errs <- err
701
+		}
702
+		errs <- nil
703
+	}
704
+
705
+	save("/1", "1")
706
+
707
+	go purge("1")
708
+	go save("/2", "2")
709
+
710
+	any := false
711
+	for i := 0; i < 2; i++ {
712
+		if err := <-errs; err != nil {
713
+			any = true
714
+			t.Log(err)
715
+		}
716
+	}
717
+	if any {
718
+		t.Fail()
719
+	}
720
+}
0 721
deleted file mode 100644
... ...
@@ -1,721 +0,0 @@
1
-package graphdb
2
-
3
-import (
4
-	"database/sql"
5
-	"fmt"
6
-	"os"
7
-	"path"
8
-	"runtime"
9
-	"strconv"
10
-	"testing"
11
-
12
-	_ "github.com/mattn/go-sqlite3"
13
-)
14
-
15
-func newTestDb(t *testing.T) (*Database, string) {
16
-	p := path.Join(os.TempDir(), "sqlite.db")
17
-	conn, err := sql.Open("sqlite3", p)
18
-	db, err := NewDatabase(conn)
19
-	if err != nil {
20
-		t.Fatal(err)
21
-	}
22
-	return db, p
23
-}
24
-
25
-func destroyTestDb(dbPath string) {
26
-	os.Remove(dbPath)
27
-}
28
-
29
-func TestNewDatabase(t *testing.T) {
30
-	db, dbpath := newTestDb(t)
31
-	if db == nil {
32
-		t.Fatal("Database should not be nil")
33
-	}
34
-	db.Close()
35
-	defer destroyTestDb(dbpath)
36
-}
37
-
38
-func TestCreateRootEntity(t *testing.T) {
39
-	db, dbpath := newTestDb(t)
40
-	defer destroyTestDb(dbpath)
41
-	root := db.RootEntity()
42
-	if root == nil {
43
-		t.Fatal("Root entity should not be nil")
44
-	}
45
-}
46
-
47
-func TestGetRootEntity(t *testing.T) {
48
-	db, dbpath := newTestDb(t)
49
-	defer destroyTestDb(dbpath)
50
-
51
-	e := db.Get("/")
52
-	if e == nil {
53
-		t.Fatal("Entity should not be nil")
54
-	}
55
-	if e.ID() != "0" {
56
-		t.Fatalf("Entity id should be 0, got %s", e.ID())
57
-	}
58
-}
59
-
60
-func TestSetEntityWithDifferentName(t *testing.T) {
61
-	db, dbpath := newTestDb(t)
62
-	defer destroyTestDb(dbpath)
63
-
64
-	db.Set("/test", "1")
65
-	if _, err := db.Set("/other", "1"); err != nil {
66
-		t.Fatal(err)
67
-	}
68
-}
69
-
70
-func TestSetDuplicateEntity(t *testing.T) {
71
-	db, dbpath := newTestDb(t)
72
-	defer destroyTestDb(dbpath)
73
-
74
-	if _, err := db.Set("/foo", "42"); err != nil {
75
-		t.Fatal(err)
76
-	}
77
-	if _, err := db.Set("/foo", "43"); err == nil {
78
-		t.Fatalf("Creating an entry with a duplicate path did not cause an error")
79
-	}
80
-}
81
-
82
-func TestCreateChild(t *testing.T) {
83
-	db, dbpath := newTestDb(t)
84
-	defer destroyTestDb(dbpath)
85
-
86
-	child, err := db.Set("/db", "1")
87
-	if err != nil {
88
-		t.Fatal(err)
89
-	}
90
-	if child == nil {
91
-		t.Fatal("Child should not be nil")
92
-	}
93
-	if child.ID() != "1" {
94
-		t.Fail()
95
-	}
96
-}
97
-
98
-func TestParents(t *testing.T) {
99
-	db, dbpath := newTestDb(t)
100
-	defer destroyTestDb(dbpath)
101
-
102
-	for i := 1; i < 6; i++ {
103
-		a := strconv.Itoa(i)
104
-		if _, err := db.Set("/"+a, a); err != nil {
105
-			t.Fatal(err)
106
-		}
107
-	}
108
-
109
-	for i := 6; i < 11; i++ {
110
-		a := strconv.Itoa(i)
111
-		p := strconv.Itoa(i - 5)
112
-
113
-		key := fmt.Sprintf("/%s/%s", p, a)
114
-
115
-		if _, err := db.Set(key, a); err != nil {
116
-			t.Fatal(err)
117
-		}
118
-
119
-		parents, err := db.Parents(key)
120
-		if err != nil {
121
-			t.Fatal(err)
122
-		}
123
-
124
-		if len(parents) != 1 {
125
-			t.Fatalf("Expected 1 entry for %s got %d", key, len(parents))
126
-		}
127
-
128
-		if parents[0] != p {
129
-			t.Fatalf("ID %s received, %s expected", parents[0], p)
130
-		}
131
-	}
132
-}
133
-
134
-func TestChildren(t *testing.T) {
135
-	// TODO Windows: Port this test
136
-	if runtime.GOOS == "windows" {
137
-		t.Skip("Needs porting to Windows")
138
-	}
139
-	db, dbpath := newTestDb(t)
140
-	defer destroyTestDb(dbpath)
141
-
142
-	str := "/"
143
-	for i := 1; i < 6; i++ {
144
-		a := strconv.Itoa(i)
145
-		if _, err := db.Set(str+a, a); err != nil {
146
-			t.Fatal(err)
147
-		}
148
-
149
-		str = str + a + "/"
150
-	}
151
-
152
-	str = "/"
153
-	for i := 10; i < 30; i++ { // 20 entities
154
-		a := strconv.Itoa(i)
155
-		if _, err := db.Set(str+a, a); err != nil {
156
-			t.Fatal(err)
157
-		}
158
-
159
-		str = str + a + "/"
160
-	}
161
-	entries, err := db.Children("/", 5)
162
-	if err != nil {
163
-		t.Fatal(err)
164
-	}
165
-
166
-	if len(entries) != 11 {
167
-		t.Fatalf("Expect 11 entries for / got %d", len(entries))
168
-	}
169
-
170
-	entries, err = db.Children("/", 20)
171
-	if err != nil {
172
-		t.Fatal(err)
173
-	}
174
-
175
-	if len(entries) != 25 {
176
-		t.Fatalf("Expect 25 entries for / got %d", len(entries))
177
-	}
178
-}
179
-
180
-func TestListAllRootChildren(t *testing.T) {
181
-	// TODO Windows: Port this test
182
-	if runtime.GOOS == "windows" {
183
-		t.Skip("Needs porting to Windows")
184
-	}
185
-
186
-	db, dbpath := newTestDb(t)
187
-	defer destroyTestDb(dbpath)
188
-
189
-	for i := 1; i < 6; i++ {
190
-		a := strconv.Itoa(i)
191
-		if _, err := db.Set("/"+a, a); err != nil {
192
-			t.Fatal(err)
193
-		}
194
-	}
195
-	entries := db.List("/", -1)
196
-	if len(entries) != 5 {
197
-		t.Fatalf("Expect 5 entries for / got %d", len(entries))
198
-	}
199
-}
200
-
201
-func TestListAllSubChildren(t *testing.T) {
202
-	// TODO Windows: Port this test
203
-	if runtime.GOOS == "windows" {
204
-		t.Skip("Needs porting to Windows")
205
-	}
206
-	db, dbpath := newTestDb(t)
207
-	defer destroyTestDb(dbpath)
208
-
209
-	_, err := db.Set("/webapp", "1")
210
-	if err != nil {
211
-		t.Fatal(err)
212
-	}
213
-	child2, err := db.Set("/db", "2")
214
-	if err != nil {
215
-		t.Fatal(err)
216
-	}
217
-	child4, err := db.Set("/logs", "4")
218
-	if err != nil {
219
-		t.Fatal(err)
220
-	}
221
-	if _, err := db.Set("/db/logs", child4.ID()); err != nil {
222
-		t.Fatal(err)
223
-	}
224
-
225
-	child3, err := db.Set("/sentry", "3")
226
-	if err != nil {
227
-		t.Fatal(err)
228
-	}
229
-	if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
230
-		t.Fatal(err)
231
-	}
232
-	if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
233
-		t.Fatal(err)
234
-	}
235
-
236
-	entries := db.List("/webapp", 1)
237
-	if len(entries) != 3 {
238
-		t.Fatalf("Expect 3 entries for / got %d", len(entries))
239
-	}
240
-
241
-	entries = db.List("/webapp", 0)
242
-	if len(entries) != 2 {
243
-		t.Fatalf("Expect 2 entries for / got %d", len(entries))
244
-	}
245
-}
246
-
247
-func TestAddSelfAsChild(t *testing.T) {
248
-	// TODO Windows: Port this test
249
-	if runtime.GOOS == "windows" {
250
-		t.Skip("Needs porting to Windows")
251
-	}
252
-	db, dbpath := newTestDb(t)
253
-	defer destroyTestDb(dbpath)
254
-
255
-	child, err := db.Set("/test", "1")
256
-	if err != nil {
257
-		t.Fatal(err)
258
-	}
259
-	if _, err := db.Set("/test/other", child.ID()); err == nil {
260
-		t.Fatal("Error should not be nil")
261
-	}
262
-}
263
-
264
-func TestAddChildToNonExistentRoot(t *testing.T) {
265
-	db, dbpath := newTestDb(t)
266
-	defer destroyTestDb(dbpath)
267
-
268
-	if _, err := db.Set("/myapp", "1"); err != nil {
269
-		t.Fatal(err)
270
-	}
271
-
272
-	if _, err := db.Set("/myapp/proxy/db", "2"); err == nil {
273
-		t.Fatal("Error should not be nil")
274
-	}
275
-}
276
-
277
-func TestWalkAll(t *testing.T) {
278
-	// TODO Windows: Port this test
279
-	if runtime.GOOS == "windows" {
280
-		t.Skip("Needs porting to Windows")
281
-	}
282
-	db, dbpath := newTestDb(t)
283
-	defer destroyTestDb(dbpath)
284
-	_, err := db.Set("/webapp", "1")
285
-	if err != nil {
286
-		t.Fatal(err)
287
-	}
288
-	child2, err := db.Set("/db", "2")
289
-	if err != nil {
290
-		t.Fatal(err)
291
-	}
292
-	child4, err := db.Set("/db/logs", "4")
293
-	if err != nil {
294
-		t.Fatal(err)
295
-	}
296
-	if _, err := db.Set("/webapp/logs", child4.ID()); err != nil {
297
-		t.Fatal(err)
298
-	}
299
-
300
-	child3, err := db.Set("/sentry", "3")
301
-	if err != nil {
302
-		t.Fatal(err)
303
-	}
304
-	if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
305
-		t.Fatal(err)
306
-	}
307
-	if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
308
-		t.Fatal(err)
309
-	}
310
-
311
-	child5, err := db.Set("/gograph", "5")
312
-	if err != nil {
313
-		t.Fatal(err)
314
-	}
315
-	if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
316
-		t.Fatal(err)
317
-	}
318
-
319
-	if err := db.Walk("/", func(p string, e *Entity) error {
320
-		t.Logf("Path: %s Entity: %s", p, e.ID())
321
-		return nil
322
-	}, -1); err != nil {
323
-		t.Fatal(err)
324
-	}
325
-}
326
-
327
-func TestGetEntityByPath(t *testing.T) {
328
-	// TODO Windows: Port this test
329
-	if runtime.GOOS == "windows" {
330
-		t.Skip("Needs porting to Windows")
331
-	}
332
-	db, dbpath := newTestDb(t)
333
-	defer destroyTestDb(dbpath)
334
-	_, err := db.Set("/webapp", "1")
335
-	if err != nil {
336
-		t.Fatal(err)
337
-	}
338
-	child2, err := db.Set("/db", "2")
339
-	if err != nil {
340
-		t.Fatal(err)
341
-	}
342
-	child4, err := db.Set("/logs", "4")
343
-	if err != nil {
344
-		t.Fatal(err)
345
-	}
346
-	if _, err := db.Set("/db/logs", child4.ID()); err != nil {
347
-		t.Fatal(err)
348
-	}
349
-
350
-	child3, err := db.Set("/sentry", "3")
351
-	if err != nil {
352
-		t.Fatal(err)
353
-	}
354
-	if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
355
-		t.Fatal(err)
356
-	}
357
-	if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
358
-		t.Fatal(err)
359
-	}
360
-
361
-	child5, err := db.Set("/gograph", "5")
362
-	if err != nil {
363
-		t.Fatal(err)
364
-	}
365
-	if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
366
-		t.Fatal(err)
367
-	}
368
-
369
-	entity := db.Get("/webapp/db/logs")
370
-	if entity == nil {
371
-		t.Fatal("Entity should not be nil")
372
-	}
373
-	if entity.ID() != "4" {
374
-		t.Fatalf("Expected to get entity with id 4, got %s", entity.ID())
375
-	}
376
-}
377
-
378
-func TestEnitiesPaths(t *testing.T) {
379
-	// TODO Windows: Port this test
380
-	if runtime.GOOS == "windows" {
381
-		t.Skip("Needs porting to Windows")
382
-	}
383
-	db, dbpath := newTestDb(t)
384
-	defer destroyTestDb(dbpath)
385
-	_, err := db.Set("/webapp", "1")
386
-	if err != nil {
387
-		t.Fatal(err)
388
-	}
389
-	child2, err := db.Set("/db", "2")
390
-	if err != nil {
391
-		t.Fatal(err)
392
-	}
393
-	child4, err := db.Set("/logs", "4")
394
-	if err != nil {
395
-		t.Fatal(err)
396
-	}
397
-	if _, err := db.Set("/db/logs", child4.ID()); err != nil {
398
-		t.Fatal(err)
399
-	}
400
-
401
-	child3, err := db.Set("/sentry", "3")
402
-	if err != nil {
403
-		t.Fatal(err)
404
-	}
405
-	if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
406
-		t.Fatal(err)
407
-	}
408
-	if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
409
-		t.Fatal(err)
410
-	}
411
-
412
-	child5, err := db.Set("/gograph", "5")
413
-	if err != nil {
414
-		t.Fatal(err)
415
-	}
416
-	if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
417
-		t.Fatal(err)
418
-	}
419
-
420
-	out := db.List("/", -1)
421
-	for _, p := range out.Paths() {
422
-		t.Log(p)
423
-	}
424
-}
425
-
426
-func TestDeleteRootEntity(t *testing.T) {
427
-	db, dbpath := newTestDb(t)
428
-	defer destroyTestDb(dbpath)
429
-
430
-	if err := db.Delete("/"); err == nil {
431
-		t.Fatal("Error should not be nil")
432
-	}
433
-}
434
-
435
-func TestDeleteEntity(t *testing.T) {
436
-	// TODO Windows: Port this test
437
-	if runtime.GOOS == "windows" {
438
-		t.Skip("Needs porting to Windows")
439
-	}
440
-	db, dbpath := newTestDb(t)
441
-	defer destroyTestDb(dbpath)
442
-	_, err := db.Set("/webapp", "1")
443
-	if err != nil {
444
-		t.Fatal(err)
445
-	}
446
-	child2, err := db.Set("/db", "2")
447
-	if err != nil {
448
-		t.Fatal(err)
449
-	}
450
-	child4, err := db.Set("/logs", "4")
451
-	if err != nil {
452
-		t.Fatal(err)
453
-	}
454
-	if _, err := db.Set("/db/logs", child4.ID()); err != nil {
455
-		t.Fatal(err)
456
-	}
457
-
458
-	child3, err := db.Set("/sentry", "3")
459
-	if err != nil {
460
-		t.Fatal(err)
461
-	}
462
-	if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
463
-		t.Fatal(err)
464
-	}
465
-	if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
466
-		t.Fatal(err)
467
-	}
468
-
469
-	child5, err := db.Set("/gograph", "5")
470
-	if err != nil {
471
-		t.Fatal(err)
472
-	}
473
-	if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
474
-		t.Fatal(err)
475
-	}
476
-
477
-	if err := db.Delete("/webapp/sentry"); err != nil {
478
-		t.Fatal(err)
479
-	}
480
-	entity := db.Get("/webapp/sentry")
481
-	if entity != nil {
482
-		t.Fatal("Entity /webapp/sentry should be nil")
483
-	}
484
-}
485
-
486
-func TestCountRefs(t *testing.T) {
487
-	// TODO Windows: Port this test
488
-	if runtime.GOOS == "windows" {
489
-		t.Skip("Needs porting to Windows")
490
-	}
491
-	db, dbpath := newTestDb(t)
492
-	defer destroyTestDb(dbpath)
493
-
494
-	db.Set("/webapp", "1")
495
-
496
-	if db.Refs("1") != 1 {
497
-		t.Fatal("Expect reference count to be 1")
498
-	}
499
-
500
-	db.Set("/db", "2")
501
-	db.Set("/webapp/db", "2")
502
-	if db.Refs("2") != 2 {
503
-		t.Fatal("Expect reference count to be 2")
504
-	}
505
-}
506
-
507
-func TestPurgeId(t *testing.T) {
508
-	// TODO Windows: Port this test
509
-	if runtime.GOOS == "windows" {
510
-		t.Skip("Needs porting to Windows")
511
-	}
512
-
513
-	db, dbpath := newTestDb(t)
514
-	defer destroyTestDb(dbpath)
515
-
516
-	db.Set("/webapp", "1")
517
-
518
-	if c := db.Refs("1"); c != 1 {
519
-		t.Fatalf("Expect reference count to be 1, got %d", c)
520
-	}
521
-
522
-	db.Set("/db", "2")
523
-	db.Set("/webapp/db", "2")
524
-
525
-	count, err := db.Purge("2")
526
-	if err != nil {
527
-		t.Fatal(err)
528
-	}
529
-	if count != 2 {
530
-		t.Fatalf("Expected 2 references to be removed, got %d", count)
531
-	}
532
-}
533
-
534
-// Regression test https://github.com/docker/docker/issues/12334
535
-func TestPurgeIdRefPaths(t *testing.T) {
536
-	// TODO Windows: Port this test
537
-	if runtime.GOOS == "windows" {
538
-		t.Skip("Needs porting to Windows")
539
-	}
540
-	db, dbpath := newTestDb(t)
541
-	defer destroyTestDb(dbpath)
542
-
543
-	db.Set("/webapp", "1")
544
-	db.Set("/db", "2")
545
-
546
-	db.Set("/db/webapp", "1")
547
-
548
-	if c := db.Refs("1"); c != 2 {
549
-		t.Fatalf("Expected 2 reference for webapp, got %d", c)
550
-	}
551
-	if c := db.Refs("2"); c != 1 {
552
-		t.Fatalf("Expected 1 reference for db, got %d", c)
553
-	}
554
-
555
-	if rp := db.RefPaths("2"); len(rp) != 1 {
556
-		t.Fatalf("Expected 1 reference path for db, got %d", len(rp))
557
-	}
558
-
559
-	count, err := db.Purge("2")
560
-	if err != nil {
561
-		t.Fatal(err)
562
-	}
563
-
564
-	if count != 2 {
565
-		t.Fatalf("Expected 2 rows to be removed, got %d", count)
566
-	}
567
-
568
-	if c := db.Refs("2"); c != 0 {
569
-		t.Fatalf("Expected 0 reference for db, got %d", c)
570
-	}
571
-	if c := db.Refs("1"); c != 1 {
572
-		t.Fatalf("Expected 1 reference for webapp, got %d", c)
573
-	}
574
-}
575
-
576
-func TestRename(t *testing.T) {
577
-	// TODO Windows: Port this test
578
-	if runtime.GOOS == "windows" {
579
-		t.Skip("Needs porting to Windows")
580
-	}
581
-	db, dbpath := newTestDb(t)
582
-	defer destroyTestDb(dbpath)
583
-
584
-	db.Set("/webapp", "1")
585
-
586
-	if db.Refs("1") != 1 {
587
-		t.Fatal("Expect reference count to be 1")
588
-	}
589
-
590
-	db.Set("/db", "2")
591
-	db.Set("/webapp/db", "2")
592
-
593
-	if db.Get("/webapp/db") == nil {
594
-		t.Fatal("Cannot find entity at path /webapp/db")
595
-	}
596
-
597
-	if err := db.Rename("/webapp/db", "/webapp/newdb"); err != nil {
598
-		t.Fatal(err)
599
-	}
600
-	if db.Get("/webapp/db") != nil {
601
-		t.Fatal("Entity should not exist at /webapp/db")
602
-	}
603
-	if db.Get("/webapp/newdb") == nil {
604
-		t.Fatal("Cannot find entity at path /webapp/newdb")
605
-	}
606
-
607
-}
608
-
609
-func TestCreateMultipleNames(t *testing.T) {
610
-	// TODO Windows: Port this test
611
-	if runtime.GOOS == "windows" {
612
-		t.Skip("Needs porting to Windows")
613
-	}
614
-
615
-	db, dbpath := newTestDb(t)
616
-	defer destroyTestDb(dbpath)
617
-
618
-	db.Set("/db", "1")
619
-	if _, err := db.Set("/myapp", "1"); err != nil {
620
-		t.Fatal(err)
621
-	}
622
-
623
-	db.Walk("/", func(p string, e *Entity) error {
624
-		t.Logf("%s\n", p)
625
-		return nil
626
-	}, -1)
627
-}
628
-
629
-func TestRefPaths(t *testing.T) {
630
-	db, dbpath := newTestDb(t)
631
-	defer destroyTestDb(dbpath)
632
-
633
-	db.Set("/webapp", "1")
634
-
635
-	db.Set("/db", "2")
636
-	db.Set("/webapp/db", "2")
637
-
638
-	refs := db.RefPaths("2")
639
-	if len(refs) != 2 {
640
-		t.Fatalf("Expected reference count to be 2, got %d", len(refs))
641
-	}
642
-}
643
-
644
-func TestExistsTrue(t *testing.T) {
645
-	db, dbpath := newTestDb(t)
646
-	defer destroyTestDb(dbpath)
647
-
648
-	db.Set("/testing", "1")
649
-
650
-	if !db.Exists("/testing") {
651
-		t.Fatalf("/tesing should exist")
652
-	}
653
-}
654
-
655
-func TestExistsFalse(t *testing.T) {
656
-	// TODO Windows: Port this test
657
-	if runtime.GOOS == "windows" {
658
-		t.Skip("Needs porting to Windows")
659
-	}
660
-	db, dbpath := newTestDb(t)
661
-	defer destroyTestDb(dbpath)
662
-
663
-	db.Set("/toerhe", "1")
664
-
665
-	if db.Exists("/testing") {
666
-		t.Fatalf("/tesing should not exist")
667
-	}
668
-
669
-}
670
-
671
-func TestGetNameWithTrailingSlash(t *testing.T) {
672
-	db, dbpath := newTestDb(t)
673
-	defer destroyTestDb(dbpath)
674
-
675
-	db.Set("/todo", "1")
676
-
677
-	e := db.Get("/todo/")
678
-	if e == nil {
679
-		t.Fatalf("Entity should not be nil")
680
-	}
681
-}
682
-
683
-func TestConcurrentWrites(t *testing.T) {
684
-	// TODO Windows: Port this test
685
-	if runtime.GOOS == "windows" {
686
-		t.Skip("Needs porting to Windows")
687
-	}
688
-	db, dbpath := newTestDb(t)
689
-	defer destroyTestDb(dbpath)
690
-
691
-	errs := make(chan error, 2)
692
-
693
-	save := func(name string, id string) {
694
-		if _, err := db.Set(fmt.Sprintf("/%s", name), id); err != nil {
695
-			errs <- err
696
-		}
697
-		errs <- nil
698
-	}
699
-	purge := func(id string) {
700
-		if _, err := db.Purge(id); err != nil {
701
-			errs <- err
702
-		}
703
-		errs <- nil
704
-	}
705
-
706
-	save("/1", "1")
707
-
708
-	go purge("1")
709
-	go save("/2", "2")
710
-
711
-	any := false
712
-	for i := 0; i < 2; i++ {
713
-		if err := <-errs; err != nil {
714
-			any = true
715
-			t.Log(err)
716
-		}
717
-	}
718
-	if any {
719
-		t.Fail()
720
-	}
721
-}
722 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
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
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
new file mode 100644
... ...
@@ -0,0 +1,3 @@
0
+// +build !cgo !linux
1
+
2
+package graphdb
0 3
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
-// PathDepth 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,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
+// PathDepth 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
+}