Add build steps to compile docker statically
with CGO enabled
... | ... |
@@ -33,16 +33,14 @@ run apt-get update |
33 | 33 |
run apt-get install -y -q curl |
34 | 34 |
run apt-get install -y -q git |
35 | 35 |
run apt-get install -y -q mercurial |
36 |
-run apt-get install -y -q build-essential |
|
36 |
+run apt-get install -y -q build-essential libsqlite3-dev |
|
37 | 37 |
|
38 |
-# Install Go from source (for eventual cross-compiling) |
|
39 |
-env CGO_ENABLED 0 |
|
40 |
-run curl -s https://go.googlecode.com/files/go1.1.2.src.tar.gz | tar -v -C / -xz && mv /go /goroot |
|
41 |
-run cd /goroot/src && ./make.bash |
|
42 |
-env GOROOT /goroot |
|
43 |
-env PATH $PATH:/goroot/bin |
|
38 |
+# Install Go |
|
39 |
+run curl -s https://go.googlecode.com/files/go1.2rc1.src.tar.gz | tar -v -C /usr/local -xz |
|
40 |
+run cd /usr/local/go/src && ./make.bash && go install -ldflags '-w -linkmode external -extldflags "-static -Wl,--unresolved-symbols=ignore-in-shared-libs"' -tags netgo -a std |
|
41 |
+env PATH /usr/local/go/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin |
|
44 | 42 |
env GOPATH /go:/go/src/github.com/dotcloud/docker/vendor |
45 |
- |
|
43 |
+run cd /tmp && echo 'package main' > t.go && go test -a -i -v |
|
46 | 44 |
# Ubuntu stuff |
47 | 45 |
run apt-get install -y -q ruby1.9.3 rubygems libffi-dev |
48 | 46 |
run gem install --no-rdoc --no-ri fpm |
... | ... |
@@ -1153,7 +1153,7 @@ func (cli *DockerCli) CmdLink(args ...string) error { |
1153 | 1153 |
} |
1154 | 1154 |
body := map[string]string{ |
1155 | 1155 |
"currentName": cmd.Arg(0), |
1156 |
- "newName": cmd.Arg(10), |
|
1156 |
+ "newName": cmd.Arg(1), |
|
1157 | 1157 |
} |
1158 | 1158 |
|
1159 | 1159 |
_, _, err := cli.call("POST", "/containers/link", body) |
... | ... |
@@ -1,15 +1,35 @@ |
1 | 1 |
package gograph |
2 | 2 |
|
3 | 3 |
import ( |
4 |
+ _ "code.google.com/p/gosqlite/sqlite3" |
|
5 |
+ "database/sql" |
|
4 | 6 |
"fmt" |
7 |
+ "os" |
|
5 | 8 |
"path" |
6 |
- "sync" |
|
7 | 9 |
) |
8 | 10 |
|
9 |
-// Entity with a unique id and user defined value |
|
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 |
+ CREATE UNIQUE INDEX "name_parent_ix" ON "edge" (parent_id, name); |
|
27 |
+ ` |
|
28 |
+) |
|
29 |
+ |
|
30 |
+// Entity with a unique id |
|
10 | 31 |
type Entity struct { |
11 |
- id string |
|
12 |
- Value interface{} |
|
32 |
+ id string |
|
13 | 33 |
} |
14 | 34 |
|
15 | 35 |
// An Edge connects two entities together |
... | ... |
@@ -26,111 +46,173 @@ type WalkFunc func(fullPath string, entity *Entity) error |
26 | 26 |
|
27 | 27 |
// Graph database for storing entities and their relationships |
28 | 28 |
type Database struct { |
29 |
- entities Entities |
|
30 |
- edges Edges |
|
31 |
- mux sync.Mutex |
|
32 |
- |
|
29 |
+ dbPath string |
|
33 | 30 |
rootID string |
34 | 31 |
} |
35 | 32 |
|
36 | 33 |
// Create a new graph database initialized with a root entity |
37 |
-func NewDatabase(rootPath, rootId string) (*Database, error) { |
|
38 |
- db := &Database{Entities{}, Edges{}, sync.Mutex{}, rootId} |
|
39 |
- e := &Entity{ |
|
40 |
- id: rootId, |
|
34 |
+func NewDatabase(dbPath, rootId string) (*Database, error) { |
|
35 |
+ db := &Database{dbPath, rootId} |
|
36 |
+ if _, err := os.Stat(dbPath); err == nil { |
|
37 |
+ return db, nil |
|
38 |
+ } |
|
39 |
+ conn, err := db.openConn() |
|
40 |
+ if err != nil { |
|
41 |
+ return nil, err |
|
42 |
+ } |
|
43 |
+ defer conn.Close() |
|
44 |
+ |
|
45 |
+ if _, err := conn.Exec(createEntityTable); err != nil { |
|
46 |
+ return nil, err |
|
47 |
+ } |
|
48 |
+ if _, err := conn.Exec(createEdgeTable); err != nil { |
|
49 |
+ return nil, err |
|
41 | 50 |
} |
42 |
- db.entities[rootId] = e |
|
43 | 51 |
|
44 |
- edge := &Edge{ |
|
45 |
- EntityID: rootId, |
|
46 |
- Name: "/", |
|
52 |
+ rollback := func() { |
|
53 |
+ conn.Exec("ROLLBACK") |
|
47 | 54 |
} |
48 |
- db.edges = append(db.edges, edge) |
|
49 | 55 |
|
56 |
+ // Create root entities |
|
57 |
+ if _, err := conn.Exec("BEGIN"); err != nil { |
|
58 |
+ return nil, err |
|
59 |
+ } |
|
60 |
+ if _, err := conn.Exec("INSERT INTO entity (id) VALUES (?);", rootId); err != nil { |
|
61 |
+ rollback() |
|
62 |
+ return nil, err |
|
63 |
+ } |
|
64 |
+ |
|
65 |
+ if _, err := conn.Exec("INSERT INTO edge (entity_id, name) VALUES(?,?);", rootId, "/"); err != nil { |
|
66 |
+ rollback() |
|
67 |
+ return nil, err |
|
68 |
+ } |
|
69 |
+ |
|
70 |
+ if _, err := conn.Exec("COMMIT"); err != nil { |
|
71 |
+ return nil, err |
|
72 |
+ } |
|
50 | 73 |
return db, nil |
51 | 74 |
} |
52 | 75 |
|
53 | 76 |
// Set the entity id for a given path |
54 | 77 |
func (db *Database) Set(fullPath, id string) (*Entity, error) { |
55 |
- db.mux.Lock() |
|
56 |
- defer db.mux.Unlock() |
|
57 |
- |
|
58 |
- e, exists := db.entities[id] |
|
59 |
- if !exists { |
|
60 |
- e = &Entity{ |
|
61 |
- id: id, |
|
78 |
+ conn, err := db.openConn() |
|
79 |
+ if err != nil { |
|
80 |
+ return nil, err |
|
81 |
+ } |
|
82 |
+ defer conn.Close() |
|
83 |
+ rollback := func() { |
|
84 |
+ conn.Exec("ROLLBACK") |
|
85 |
+ } |
|
86 |
+ if _, err := conn.Exec("BEGIN"); err != nil { |
|
87 |
+ return nil, err |
|
88 |
+ } |
|
89 |
+ var entityId string |
|
90 |
+ if err := conn.QueryRow("SELECT id FROM entity WHERE id = ?;", id).Scan(&entityId); err != nil { |
|
91 |
+ if err == sql.ErrNoRows { |
|
92 |
+ if _, err := conn.Exec("INSERT INTO entity (id) VALUES(?);", id); err != nil { |
|
93 |
+ rollback() |
|
94 |
+ return nil, err |
|
95 |
+ } |
|
96 |
+ } else { |
|
97 |
+ rollback() |
|
98 |
+ return nil, err |
|
62 | 99 |
} |
63 |
- db.entities[id] = e |
|
64 | 100 |
} |
101 |
+ e := &Entity{id} |
|
65 | 102 |
|
66 | 103 |
parentPath, name := splitPath(fullPath) |
67 |
- if err := db.setEdge(parentPath, name, e); err != nil { |
|
104 |
+ if err := db.setEdge(conn, parentPath, name, e); err != nil { |
|
105 |
+ rollback() |
|
106 |
+ return nil, err |
|
107 |
+ } |
|
108 |
+ |
|
109 |
+ if _, err := conn.Exec("COMMIT"); err != nil { |
|
68 | 110 |
return nil, err |
69 | 111 |
} |
70 | 112 |
return e, nil |
71 | 113 |
} |
72 | 114 |
|
73 |
-func (db *Database) setEdge(parentPath, name string, e *Entity) error { |
|
74 |
- parent := db.Get(parentPath) |
|
75 |
- if parent == nil { |
|
76 |
- return fmt.Errorf("Parent does not exist for path: %s", parentPath) |
|
115 |
+func (db *Database) setEdge(conn *sql.DB, parentPath, name string, e *Entity) error { |
|
116 |
+ parent, err := db.get(conn, parentPath) |
|
117 |
+ if err != nil { |
|
118 |
+ return err |
|
77 | 119 |
} |
78 | 120 |
if parent.id == e.id { |
79 | 121 |
return fmt.Errorf("Cannot set self as child") |
80 | 122 |
} |
81 | 123 |
|
82 |
- edge := &Edge{ |
|
83 |
- ParentID: parent.id, |
|
84 |
- EntityID: e.id, |
|
85 |
- Name: name, |
|
86 |
- } |
|
87 |
- if db.edges.Exists(parent.id, name) { |
|
88 |
- return fmt.Errorf("Relationship already exists for %s/%s", parentPath, name) |
|
124 |
+ if _, err := conn.Exec("INSERT INTO edge (parent_id, name, entity_id) VALUES (?,?,?);", parent.id, name, e.id); err != nil { |
|
125 |
+ return err |
|
89 | 126 |
} |
90 |
- db.edges = append(db.edges, edge) |
|
91 | 127 |
return nil |
92 | 128 |
} |
93 | 129 |
|
94 | 130 |
// Return the root "/" entity for the database |
95 | 131 |
func (db *Database) RootEntity() *Entity { |
96 |
- return db.entities[db.rootID] |
|
132 |
+ return &Entity{ |
|
133 |
+ id: db.rootID, |
|
134 |
+ } |
|
97 | 135 |
} |
98 | 136 |
|
99 | 137 |
// Return the entity for a given path |
100 | 138 |
func (db *Database) Get(name string) *Entity { |
139 |
+ conn, err := db.openConn() |
|
140 |
+ if err != nil { |
|
141 |
+ return nil |
|
142 |
+ } |
|
143 |
+ e, err := db.get(conn, name) |
|
144 |
+ if err != nil { |
|
145 |
+ return nil |
|
146 |
+ } |
|
147 |
+ return e |
|
148 |
+} |
|
149 |
+ |
|
150 |
+func (db *Database) get(conn *sql.DB, name string) (*Entity, error) { |
|
101 | 151 |
e := db.RootEntity() |
102 | 152 |
// We always know the root name so return it if |
103 | 153 |
// it is requested |
104 | 154 |
if name == "/" { |
105 |
- return e |
|
155 |
+ return e, nil |
|
106 | 156 |
} |
107 | 157 |
|
108 | 158 |
parts := split(name) |
109 | 159 |
for i := 1; i < len(parts); i++ { |
110 | 160 |
p := parts[i] |
111 | 161 |
|
112 |
- next := db.child(e, p) |
|
162 |
+ next := db.child(conn, e, p) |
|
113 | 163 |
if next == nil { |
114 |
- return nil |
|
164 |
+ return nil, fmt.Errorf("Cannot find child") |
|
115 | 165 |
} |
116 | 166 |
e = next |
117 |
- |
|
118 | 167 |
} |
119 |
- return e |
|
168 |
+ return e, nil |
|
169 |
+ |
|
120 | 170 |
} |
121 | 171 |
|
122 | 172 |
// List all entities by from the name |
123 | 173 |
// The key will be the full path of the entity |
124 | 174 |
func (db *Database) List(name string, depth int) Entities { |
125 | 175 |
out := Entities{} |
126 |
- for c := range db.children(name, depth) { |
|
176 |
+ conn, err := db.openConn() |
|
177 |
+ if err != nil { |
|
178 |
+ return out |
|
179 |
+ } |
|
180 |
+ defer conn.Close() |
|
181 |
+ |
|
182 |
+ for c := range db.children(conn, name, depth) { |
|
127 | 183 |
out[c.FullPath] = c.Entity |
128 | 184 |
} |
129 | 185 |
return out |
130 | 186 |
} |
131 | 187 |
|
132 | 188 |
func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error { |
133 |
- for c := range db.children(name, depth) { |
|
189 |
+ conn, err := db.openConn() |
|
190 |
+ if err != nil { |
|
191 |
+ return err |
|
192 |
+ } |
|
193 |
+ defer conn.Close() |
|
194 |
+ |
|
195 |
+ for c := range db.children(conn, name, depth) { |
|
134 | 196 |
if err := walkFunc(c.FullPath, c.Entity); err != nil { |
135 | 197 |
return err |
136 | 198 |
} |
... | ... |
@@ -140,14 +222,46 @@ func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error { |
140 | 140 |
|
141 | 141 |
// Return the refrence count for a specified id |
142 | 142 |
func (db *Database) Refs(id string) int { |
143 |
- return len(db.RefPaths(id)) |
|
143 |
+ conn, err := db.openConn() |
|
144 |
+ if err != nil { |
|
145 |
+ return -1 |
|
146 |
+ } |
|
147 |
+ defer conn.Close() |
|
148 |
+ |
|
149 |
+ var count int |
|
150 |
+ if err := conn.QueryRow("SELECT COUNT(*) FROM edge WHERE entity_id = ?;", id).Scan(&count); err != nil { |
|
151 |
+ return 0 |
|
152 |
+ } |
|
153 |
+ return count |
|
144 | 154 |
} |
145 | 155 |
|
146 | 156 |
// Return all the id's path references |
147 | 157 |
func (db *Database) RefPaths(id string) Edges { |
148 |
- refs := db.edges.Search(func(e *Edge) bool { |
|
149 |
- return e.EntityID == id |
|
150 |
- }) |
|
158 |
+ refs := Edges{} |
|
159 |
+ conn, err := db.openConn() |
|
160 |
+ if err != nil { |
|
161 |
+ return refs |
|
162 |
+ } |
|
163 |
+ defer conn.Close() |
|
164 |
+ |
|
165 |
+ rows, err := conn.Query("SELECT name, parent_id FROM edge WHERE entity_id = ?;", id) |
|
166 |
+ if err != nil { |
|
167 |
+ return refs |
|
168 |
+ } |
|
169 |
+ defer rows.Close() |
|
170 |
+ |
|
171 |
+ for rows.Next() { |
|
172 |
+ var name string |
|
173 |
+ var parentId string |
|
174 |
+ if err := rows.Scan(&name, &parentId); err != nil { |
|
175 |
+ return refs |
|
176 |
+ } |
|
177 |
+ refs = append(refs, &Edge{ |
|
178 |
+ EntityID: id, |
|
179 |
+ Name: name, |
|
180 |
+ ParentID: parentId, |
|
181 |
+ }) |
|
182 |
+ } |
|
151 | 183 |
return refs |
152 | 184 |
} |
153 | 185 |
|
... | ... |
@@ -156,54 +270,64 @@ func (db *Database) Delete(name string) error { |
156 | 156 |
if name == "/" { |
157 | 157 |
return fmt.Errorf("Cannot delete root entity") |
158 | 158 |
} |
159 |
- db.mux.Lock() |
|
160 |
- defer db.mux.Unlock() |
|
159 |
+ conn, err := db.openConn() |
|
160 |
+ if err != nil { |
|
161 |
+ return err |
|
162 |
+ } |
|
163 |
+ defer conn.Close() |
|
161 | 164 |
|
162 | 165 |
parentPath, n := splitPath(name) |
163 |
- parent := db.Get(parentPath) |
|
164 |
- if parent == nil { |
|
165 |
- return fmt.Errorf("Cannot find parent for %s", parentPath) |
|
166 |
- } |
|
167 |
- edge, i := db.edges.Get(parent.id, n) |
|
168 |
- if edge == nil { |
|
169 |
- return fmt.Errorf("Edge does not exist at %s", name) |
|
166 |
+ parent, err := db.get(conn, parentPath) |
|
167 |
+ if err != nil { |
|
168 |
+ return err |
|
170 | 169 |
} |
171 |
- db.deleteEdgeAtIndex(i) |
|
172 | 170 |
|
171 |
+ if _, err := conn.Exec("DELETE FROM edge WHERE parent_id = ? AND name = ?;", parent.id, n); err != nil { |
|
172 |
+ return err |
|
173 |
+ } |
|
173 | 174 |
return nil |
174 | 175 |
} |
175 | 176 |
|
176 |
-func (db *Database) deleteEdgeAtIndex(i int) { |
|
177 |
- db.edges[len(db.edges)-1], db.edges[i], db.edges = nil, db.edges[len(db.edges)-1], db.edges[:len(db.edges)-1] |
|
178 |
-} |
|
179 |
- |
|
180 | 177 |
// Remove the entity with the specified id |
181 | 178 |
// Walk the graph to make sure all references to the entity |
182 | 179 |
// are removed and return the number of references removed |
183 | 180 |
func (db *Database) Purge(id string) (int, error) { |
184 |
- db.mux.Lock() |
|
185 |
- defer db.mux.Unlock() |
|
186 |
- |
|
187 |
- getIndex := func(e *Edge) int { |
|
188 |
- for i, edge := range db.edges { |
|
189 |
- if edge.EntityID == e.EntityID && |
|
190 |
- edge.Name == e.Name && |
|
191 |
- edge.ParentID == e.ParentID { |
|
192 |
- return i |
|
193 |
- } |
|
194 |
- } |
|
195 |
- return -1 |
|
181 |
+ conn, err := db.openConn() |
|
182 |
+ if err != nil { |
|
183 |
+ return -1, err |
|
196 | 184 |
} |
185 |
+ defer conn.Close() |
|
197 | 186 |
|
198 |
- refsToDelete := db.RefPaths(id) |
|
199 |
- for i, e := range refsToDelete { |
|
200 |
- index := getIndex(e) |
|
201 |
- if index == -1 { |
|
202 |
- return i + 1, fmt.Errorf("Cannot find index for %s %s", e.ParentID, e.Name) |
|
203 |
- } |
|
204 |
- db.deleteEdgeAtIndex(index) |
|
187 |
+ rollback := func() { |
|
188 |
+ conn.Exec("ROLLBACK") |
|
189 |
+ } |
|
190 |
+ |
|
191 |
+ if _, err := conn.Exec("BEGIN"); err != nil { |
|
192 |
+ return -1, err |
|
193 |
+ } |
|
194 |
+ |
|
195 |
+ // Delete all edges |
|
196 |
+ rows, err := conn.Exec("DELETE FROM edge WHERE entity_id = ?;", id) |
|
197 |
+ if err != nil { |
|
198 |
+ rollback() |
|
199 |
+ return -1, err |
|
200 |
+ } |
|
201 |
+ |
|
202 |
+ changes, err := rows.RowsAffected() |
|
203 |
+ if err != nil { |
|
204 |
+ return -1, err |
|
205 | 205 |
} |
206 |
- return len(refsToDelete), nil |
|
206 |
+ |
|
207 |
+ // Delete entity |
|
208 |
+ if _, err := conn.Exec("DELETE FROM entity where id = ?;", id); err != nil { |
|
209 |
+ rollback() |
|
210 |
+ return -1, err |
|
211 |
+ } |
|
212 |
+ |
|
213 |
+ if _, err := conn.Exec("COMMIT"); err != nil { |
|
214 |
+ return -1, err |
|
215 |
+ } |
|
216 |
+ return int(changes), nil |
|
207 | 217 |
} |
208 | 218 |
|
209 | 219 |
// Rename an edge for a given path |
... | ... |
@@ -215,19 +339,28 @@ func (db *Database) Rename(currentName, newName string) error { |
215 | 215 |
return fmt.Errorf("Cannot rename when root paths do not match %s != %s", parentPath, newParentPath) |
216 | 216 |
} |
217 | 217 |
|
218 |
- db.mux.Lock() |
|
219 |
- defer db.mux.Unlock() |
|
218 |
+ conn, err := db.openConn() |
|
219 |
+ if err != nil { |
|
220 |
+ return err |
|
221 |
+ } |
|
222 |
+ defer conn.Close() |
|
223 |
+ |
|
224 |
+ parent, err := db.get(conn, parentPath) |
|
225 |
+ if err != nil { |
|
226 |
+ return err |
|
227 |
+ } |
|
220 | 228 |
|
221 |
- parent := db.Get(parentPath) |
|
222 |
- if parent == nil { |
|
223 |
- return fmt.Errorf("Cannot locate parent for %s", currentName) |
|
229 |
+ rows, err := conn.Exec("UPDATE edge SET name = ? WHERE parent_id = ? AND name = ?;", newEdgeName, parent.id, name) |
|
230 |
+ if err != nil { |
|
231 |
+ return err |
|
232 |
+ } |
|
233 |
+ i, err := rows.RowsAffected() |
|
234 |
+ if err != nil { |
|
235 |
+ return err |
|
224 | 236 |
} |
225 |
- edge, _ := db.edges.Get(parent.id, name) |
|
226 |
- if edge == nil { |
|
237 |
+ if i == 0 { |
|
227 | 238 |
return fmt.Errorf("Cannot locate edge for %s %s", parent.id, name) |
228 | 239 |
} |
229 |
- edge.Name = newEdgeName |
|
230 |
- |
|
231 | 240 |
return nil |
232 | 241 |
} |
233 | 242 |
|
... | ... |
@@ -238,38 +371,52 @@ type WalkMeta struct { |
238 | 238 |
Edge *Edge |
239 | 239 |
} |
240 | 240 |
|
241 |
-func (db *Database) children(name string, depth int) <-chan WalkMeta { |
|
241 |
+func (db *Database) children(conn *sql.DB, name string, depth int) <-chan WalkMeta { |
|
242 | 242 |
out := make(chan WalkMeta) |
243 |
- e := db.Get(name) |
|
244 |
- |
|
245 |
- if e == nil { |
|
243 |
+ e, err := db.get(conn, name) |
|
244 |
+ if err != nil { |
|
246 | 245 |
close(out) |
247 | 246 |
return out |
248 | 247 |
} |
249 | 248 |
|
250 | 249 |
go func() { |
251 |
- for _, edge := range db.edges { |
|
252 |
- if edge.ParentID == e.id { |
|
253 |
- child := db.entities[edge.EntityID] |
|
254 |
- |
|
255 |
- meta := WalkMeta{ |
|
256 |
- Parent: e, |
|
257 |
- Entity: child, |
|
258 |
- FullPath: path.Join(name, edge.Name), |
|
259 |
- Edge: edge, |
|
260 |
- } |
|
261 |
- out <- meta |
|
262 |
- if depth == 0 { |
|
263 |
- continue |
|
264 |
- } |
|
265 |
- nDepth := depth |
|
266 |
- if depth != -1 { |
|
267 |
- nDepth -= 1 |
|
268 |
- } |
|
269 |
- sc := db.children(meta.FullPath, nDepth) |
|
270 |
- for c := range sc { |
|
271 |
- out <- c |
|
272 |
- } |
|
250 |
+ rows, err := conn.Query("SELECT entity_id, name FROM edge where parent_id = ?;", e.id) |
|
251 |
+ if err != nil { |
|
252 |
+ close(out) |
|
253 |
+ } |
|
254 |
+ defer rows.Close() |
|
255 |
+ |
|
256 |
+ for rows.Next() { |
|
257 |
+ var entityId, entityName string |
|
258 |
+ if err := rows.Scan(&entityId, &entityName); err != nil { |
|
259 |
+ // Log error |
|
260 |
+ continue |
|
261 |
+ } |
|
262 |
+ child := &Entity{entityId} |
|
263 |
+ edge := &Edge{ |
|
264 |
+ ParentID: e.id, |
|
265 |
+ Name: entityName, |
|
266 |
+ EntityID: child.id, |
|
267 |
+ } |
|
268 |
+ |
|
269 |
+ meta := WalkMeta{ |
|
270 |
+ Parent: e, |
|
271 |
+ Entity: child, |
|
272 |
+ FullPath: path.Join(name, edge.Name), |
|
273 |
+ Edge: edge, |
|
274 |
+ } |
|
275 |
+ |
|
276 |
+ out <- meta |
|
277 |
+ if depth == 0 { |
|
278 |
+ continue |
|
279 |
+ } |
|
280 |
+ nDepth := depth |
|
281 |
+ if depth != -1 { |
|
282 |
+ nDepth -= 1 |
|
283 |
+ } |
|
284 |
+ sc := db.children(conn, meta.FullPath, nDepth) |
|
285 |
+ for c := range sc { |
|
286 |
+ out <- c |
|
273 | 287 |
} |
274 | 288 |
} |
275 | 289 |
close(out) |
... | ... |
@@ -278,12 +425,16 @@ func (db *Database) children(name string, depth int) <-chan WalkMeta { |
278 | 278 |
} |
279 | 279 |
|
280 | 280 |
// Return the entity based on the parent path and name |
281 |
-func (db *Database) child(parent *Entity, name string) *Entity { |
|
282 |
- edge, _ := db.edges.Get(parent.id, name) |
|
283 |
- if edge == nil { |
|
281 |
+func (db *Database) child(conn *sql.DB, parent *Entity, name string) *Entity { |
|
282 |
+ var id string |
|
283 |
+ if err := conn.QueryRow("SELECT entity_id FROM edge WHERE parent_id = ? AND name = ?;", parent.id, name).Scan(&id); err != nil { |
|
284 | 284 |
return nil |
285 | 285 |
} |
286 |
- return db.entities[edge.EntityID] |
|
286 |
+ return &Entity{id} |
|
287 |
+} |
|
288 |
+ |
|
289 |
+func (db *Database) openConn() (*sql.DB, error) { |
|
290 |
+ return sql.Open("sqlite3", db.dbPath) |
|
287 | 291 |
} |
288 | 292 |
|
289 | 293 |
// Return the id used to reference this entity |
... | ... |
@@ -303,29 +454,3 @@ func (e Entities) Paths() []string { |
303 | 303 |
|
304 | 304 |
return out |
305 | 305 |
} |
306 |
- |
|
307 |
-// Checks if an edge with the specified parent id and name exist in the slice |
|
308 |
-func (e Edges) Exists(parendId, name string) bool { |
|
309 |
- edge, _ := e.Get(parendId, name) |
|
310 |
- return edge != nil |
|
311 |
-} |
|
312 |
- |
|
313 |
-// Returns the edge and index in the slice with the specified parent id and name |
|
314 |
-func (e Edges) Get(parentId, name string) (*Edge, int) { |
|
315 |
- for i, edge := range e { |
|
316 |
- if edge.ParentID == parentId && edge.Name == name { |
|
317 |
- return edge, i |
|
318 |
- } |
|
319 |
- } |
|
320 |
- return nil, -1 |
|
321 |
-} |
|
322 |
- |
|
323 |
-func (e Edges) Search(predicate func(edge *Edge) bool) Edges { |
|
324 |
- out := Edges{} |
|
325 |
- for _, edge := range e { |
|
326 |
- if predicate(edge) { |
|
327 |
- out = append(out, edge) |
|
328 |
- } |
|
329 |
- } |
|
330 |
- return out |
|
331 |
-} |
... | ... |
@@ -2,27 +2,34 @@ package gograph |
2 | 2 |
|
3 | 3 |
import ( |
4 | 4 |
"os" |
5 |
+ "path" |
|
5 | 6 |
"strconv" |
6 | 7 |
"testing" |
7 | 8 |
) |
8 | 9 |
|
9 | 10 |
func newTestDb(t *testing.T) *Database { |
10 |
- db, err := NewDatabase(os.TempDir(), "0") |
|
11 |
+ db, err := NewDatabase(path.Join(os.TempDir(), "sqlite.db"), "0") |
|
11 | 12 |
if err != nil { |
12 | 13 |
t.Fatal(err) |
13 | 14 |
} |
14 | 15 |
return db |
15 | 16 |
} |
16 | 17 |
|
18 |
+func destroyTestDb(db *Database) { |
|
19 |
+ os.Remove(db.dbPath) |
|
20 |
+} |
|
21 |
+ |
|
17 | 22 |
func TestNewDatabase(t *testing.T) { |
18 | 23 |
db := newTestDb(t) |
19 | 24 |
if db == nil { |
20 | 25 |
t.Fatal("Datbase should not be nil") |
21 | 26 |
} |
27 |
+ defer destroyTestDb(db) |
|
22 | 28 |
} |
23 | 29 |
|
24 | 30 |
func TestCreateRootEnity(t *testing.T) { |
25 | 31 |
db := newTestDb(t) |
32 |
+ defer destroyTestDb(db) |
|
26 | 33 |
root := db.RootEntity() |
27 | 34 |
if root == nil { |
28 | 35 |
t.Fatal("Root entity should not be nil") |
... | ... |
@@ -31,6 +38,7 @@ func TestCreateRootEnity(t *testing.T) { |
31 | 31 |
|
32 | 32 |
func TestGetRootEntity(t *testing.T) { |
33 | 33 |
db := newTestDb(t) |
34 |
+ defer destroyTestDb(db) |
|
34 | 35 |
|
35 | 36 |
e := db.Get("/") |
36 | 37 |
if e == nil { |
... | ... |
@@ -43,6 +51,7 @@ func TestGetRootEntity(t *testing.T) { |
43 | 43 |
|
44 | 44 |
func TestSetEntityWithDifferentName(t *testing.T) { |
45 | 45 |
db := newTestDb(t) |
46 |
+ defer destroyTestDb(db) |
|
46 | 47 |
|
47 | 48 |
db.Set("/test", "1") |
48 | 49 |
if _, err := db.Set("/other", "1"); err != nil { |
... | ... |
@@ -52,6 +61,7 @@ func TestSetEntityWithDifferentName(t *testing.T) { |
52 | 52 |
|
53 | 53 |
func TestCreateChild(t *testing.T) { |
54 | 54 |
db := newTestDb(t) |
55 |
+ defer destroyTestDb(db) |
|
55 | 56 |
|
56 | 57 |
child, err := db.Set("/db", "1") |
57 | 58 |
if err != nil { |
... | ... |
@@ -67,6 +77,7 @@ func TestCreateChild(t *testing.T) { |
67 | 67 |
|
68 | 68 |
func TestListAllRootChildren(t *testing.T) { |
69 | 69 |
db := newTestDb(t) |
70 |
+ defer destroyTestDb(db) |
|
70 | 71 |
|
71 | 72 |
for i := 1; i < 6; i++ { |
72 | 73 |
a := strconv.Itoa(i) |
... | ... |
@@ -82,6 +93,7 @@ func TestListAllRootChildren(t *testing.T) { |
82 | 82 |
|
83 | 83 |
func TestListAllSubChildren(t *testing.T) { |
84 | 84 |
db := newTestDb(t) |
85 |
+ defer destroyTestDb(db) |
|
85 | 86 |
|
86 | 87 |
_, err := db.Set("/webapp", "1") |
87 | 88 |
if err != nil { |
... | ... |
@@ -123,6 +135,7 @@ func TestListAllSubChildren(t *testing.T) { |
123 | 123 |
|
124 | 124 |
func TestAddSelfAsChild(t *testing.T) { |
125 | 125 |
db := newTestDb(t) |
126 |
+ defer destroyTestDb(db) |
|
126 | 127 |
|
127 | 128 |
child, err := db.Set("/test", "1") |
128 | 129 |
if err != nil { |
... | ... |
@@ -135,6 +148,7 @@ func TestAddSelfAsChild(t *testing.T) { |
135 | 135 |
|
136 | 136 |
func TestAddChildToNonExistantRoot(t *testing.T) { |
137 | 137 |
db := newTestDb(t) |
138 |
+ defer destroyTestDb(db) |
|
138 | 139 |
|
139 | 140 |
if _, err := db.Set("/myapp", "1"); err != nil { |
140 | 141 |
t.Fatal(err) |
... | ... |
@@ -147,6 +161,7 @@ func TestAddChildToNonExistantRoot(t *testing.T) { |
147 | 147 |
|
148 | 148 |
func TestWalkAll(t *testing.T) { |
149 | 149 |
db := newTestDb(t) |
150 |
+ defer destroyTestDb(db) |
|
150 | 151 |
_, err := db.Set("/webapp", "1") |
151 | 152 |
if err != nil { |
152 | 153 |
t.Fatal(err) |
... | ... |
@@ -192,6 +207,7 @@ func TestWalkAll(t *testing.T) { |
192 | 192 |
|
193 | 193 |
func TestGetEntityByPath(t *testing.T) { |
194 | 194 |
db := newTestDb(t) |
195 |
+ defer destroyTestDb(db) |
|
195 | 196 |
_, err := db.Set("/webapp", "1") |
196 | 197 |
if err != nil { |
197 | 198 |
t.Fatal(err) |
... | ... |
@@ -238,6 +254,7 @@ func TestGetEntityByPath(t *testing.T) { |
238 | 238 |
|
239 | 239 |
func TestEnitiesPaths(t *testing.T) { |
240 | 240 |
db := newTestDb(t) |
241 |
+ defer destroyTestDb(db) |
|
241 | 242 |
_, err := db.Set("/webapp", "1") |
242 | 243 |
if err != nil { |
243 | 244 |
t.Fatal(err) |
... | ... |
@@ -281,6 +298,7 @@ func TestEnitiesPaths(t *testing.T) { |
281 | 281 |
|
282 | 282 |
func TestDeleteRootEntity(t *testing.T) { |
283 | 283 |
db := newTestDb(t) |
284 |
+ defer destroyTestDb(db) |
|
284 | 285 |
|
285 | 286 |
if err := db.Delete("/"); err == nil { |
286 | 287 |
t.Fatal("Error should not be nil") |
... | ... |
@@ -289,6 +307,7 @@ func TestDeleteRootEntity(t *testing.T) { |
289 | 289 |
|
290 | 290 |
func TestDeleteEntity(t *testing.T) { |
291 | 291 |
db := newTestDb(t) |
292 |
+ defer destroyTestDb(db) |
|
292 | 293 |
_, err := db.Set("/webapp", "1") |
293 | 294 |
if err != nil { |
294 | 295 |
t.Fatal(err) |
... | ... |
@@ -335,6 +354,7 @@ func TestDeleteEntity(t *testing.T) { |
335 | 335 |
|
336 | 336 |
func TestCountRefs(t *testing.T) { |
337 | 337 |
db := newTestDb(t) |
338 |
+ defer destroyTestDb(db) |
|
338 | 339 |
|
339 | 340 |
db.Set("/webapp", "1") |
340 | 341 |
|
... | ... |
@@ -351,6 +371,7 @@ func TestCountRefs(t *testing.T) { |
351 | 351 |
|
352 | 352 |
func TestPurgeId(t *testing.T) { |
353 | 353 |
db := newTestDb(t) |
354 |
+ defer destroyTestDb(db) |
|
354 | 355 |
|
355 | 356 |
db.Set("/webapp", "1") |
356 | 357 |
|
... | ... |
@@ -372,6 +393,7 @@ func TestPurgeId(t *testing.T) { |
372 | 372 |
|
373 | 373 |
func TestRename(t *testing.T) { |
374 | 374 |
db := newTestDb(t) |
375 |
+ defer destroyTestDb(db) |
|
375 | 376 |
|
376 | 377 |
db.Set("/webapp", "1") |
377 | 378 |
|
... | ... |
@@ -400,6 +422,7 @@ func TestRename(t *testing.T) { |
400 | 400 |
|
401 | 401 |
func TestCreateMultipleNames(t *testing.T) { |
402 | 402 |
db := newTestDb(t) |
403 |
+ defer destroyTestDb(db) |
|
403 | 404 |
|
404 | 405 |
db.Set("/db", "1") |
405 | 406 |
if _, err := db.Set("/myapp", "1"); err != nil { |
... | ... |
@@ -411,3 +434,19 @@ func TestCreateMultipleNames(t *testing.T) { |
411 | 411 |
return nil |
412 | 412 |
}, -1) |
413 | 413 |
} |
414 |
+ |
|
415 |
+func TestRefPaths(t *testing.T) { |
|
416 |
+ db := newTestDb(t) |
|
417 |
+ defer destroyTestDb(db) |
|
418 |
+ |
|
419 |
+ db.Set("/webapp", "1") |
|
420 |
+ |
|
421 |
+ db.Set("/db", "2") |
|
422 |
+ db.Set("/webapp/db", "2") |
|
423 |
+ |
|
424 |
+ refs := db.RefPaths("2") |
|
425 |
+ if len(refs) != 2 { |
|
426 |
+ t.Fatalf("Expected reference count to be 2, got %d", len(refs)) |
|
427 |
+ } |
|
428 |
+ |
|
429 |
+} |
... | ... |
@@ -44,7 +44,8 @@ if [ -n "$(git status --porcelain)" ]; then |
44 | 44 |
fi |
45 | 45 |
|
46 | 46 |
# Use these flags when compiling the tests and final binary |
47 |
-LDFLAGS="-X main.GITCOMMIT $GITCOMMIT -X main.VERSION $VERSION -d -w" |
|
47 |
+LDFLAGS='-X main.GITCOMMIT "'$GITCOMMIT'" -X main.VERSION "'$VERSION'" -w -linkmode external -extldflags "-ldl -pthread -static -Wl,--unresolved-symbols=ignore-in-shared-libs"' |
|
48 |
+BUILDFLAGS='-tags netgo' |
|
48 | 49 |
|
49 | 50 |
|
50 | 51 |
bundle() { |
... | ... |
@@ -2,6 +2,6 @@ |
2 | 2 |
|
3 | 3 |
DEST=$1 |
4 | 4 |
|
5 |
-if go build -o $DEST/docker-$VERSION -ldflags "$LDFLAGS" ./docker; then |
|
6 |
- echo "Created binary: $DEST/docker-$VERSION" |
|
7 |
-fi |
|
5 |
+go build -o $DEST/docker-$VERSION -ldflags "$LDFLAGS" $BUILDFLAGS ./docker |
|
6 |
+ |
|
7 |
+echo "Created binary: $DEST/docker-$VERSION" |
... | ... |
@@ -199,6 +199,7 @@ func (runtime *Runtime) Destroy(container *Container) error { |
199 | 199 |
if err := container.Stop(3); err != nil { |
200 | 200 |
return err |
201 | 201 |
} |
202 |
+ |
|
202 | 203 |
if mounted, err := container.Mounted(); err != nil { |
203 | 204 |
return err |
204 | 205 |
} else if mounted { |
... | ... |
@@ -206,6 +207,11 @@ func (runtime *Runtime) Destroy(container *Container) error { |
206 | 206 |
return fmt.Errorf("Unable to unmount container %v: %v", container.ID, err) |
207 | 207 |
} |
208 | 208 |
} |
209 |
+ |
|
210 |
+ if _, err := runtime.containerGraph.Purge(container.ID); err != nil { |
|
211 |
+ utils.Debugf("Unable to remove container from link graph: %s", err) |
|
212 |
+ } |
|
213 |
+ |
|
209 | 214 |
// Deregister the container before removing its directory, to avoid race conditions |
210 | 215 |
runtime.idIndex.Delete(container.ID) |
211 | 216 |
runtime.containers.Remove(element) |
... | ... |
@@ -566,7 +572,7 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { |
566 | 566 |
if err != nil { |
567 | 567 |
return nil, err |
568 | 568 |
} |
569 |
- graph, err := gograph.NewDatabase("", "engine") |
|
569 |
+ graph, err := gograph.NewDatabase(path.Join(config.GraphPath, "linkgraph.db"), "engine") |
|
570 | 570 |
if err != nil { |
571 | 571 |
return nil, err |
572 | 572 |
} |
... | ... |
@@ -500,8 +500,6 @@ func TestRestore(t *testing.T) { |
500 | 500 |
} |
501 | 501 |
|
502 | 502 |
func TestReloadContainerLinks(t *testing.T) { |
503 |
- t.SkipNow() // TODO: @crosbymichael |
|
504 |
- |
|
505 | 503 |
runtime1 := mkRuntime(t) |
506 | 504 |
defer nuke(runtime1) |
507 | 505 |
// Create a container with one instance of docker |
508 | 506 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,404 @@ |
0 |
+// Copyright 2010 The Go Authors. All rights reserved. |
|
1 |
+// Use of this source code is governed by a BSD-style |
|
2 |
+// license that can be found in the LICENSE file. |
|
3 |
+ |
|
4 |
+// Package sqlite provides access to the SQLite library, version 3. |
|
5 |
+package sqlite |
|
6 |
+ |
|
7 |
+/* |
|
8 |
+#cgo LDFLAGS: -lsqlite3 |
|
9 |
+ |
|
10 |
+#include <sqlite3.h> |
|
11 |
+#include <stdlib.h> |
|
12 |
+ |
|
13 |
+// These wrappers are necessary because SQLITE_TRANSIENT |
|
14 |
+// is a pointer constant, and cgo doesn't translate them correctly. |
|
15 |
+// The definition in sqlite3.h is: |
|
16 |
+// |
|
17 |
+// typedef void (*sqlite3_destructor_type)(void*); |
|
18 |
+// #define SQLITE_STATIC ((sqlite3_destructor_type)0) |
|
19 |
+// #define SQLITE_TRANSIENT ((sqlite3_destructor_type)-1) |
|
20 |
+ |
|
21 |
+static int my_bind_text(sqlite3_stmt *stmt, int n, char *p, int np) { |
|
22 |
+ return sqlite3_bind_text(stmt, n, p, np, SQLITE_TRANSIENT); |
|
23 |
+} |
|
24 |
+static int my_bind_blob(sqlite3_stmt *stmt, int n, void *p, int np) { |
|
25 |
+ return sqlite3_bind_blob(stmt, n, p, np, SQLITE_TRANSIENT); |
|
26 |
+} |
|
27 |
+ |
|
28 |
+*/ |
|
29 |
+import "C" |
|
30 |
+ |
|
31 |
+import ( |
|
32 |
+ "errors" |
|
33 |
+ "fmt" |
|
34 |
+ "reflect" |
|
35 |
+ "strconv" |
|
36 |
+ "time" |
|
37 |
+ "unsafe" |
|
38 |
+) |
|
39 |
+ |
|
40 |
+type Errno int |
|
41 |
+ |
|
42 |
+func (e Errno) Error() string { |
|
43 |
+ s := errText[e] |
|
44 |
+ if s == "" { |
|
45 |
+ return fmt.Sprintf("errno %d", int(e)) |
|
46 |
+ } |
|
47 |
+ return s |
|
48 |
+} |
|
49 |
+ |
|
50 |
+var ( |
|
51 |
+ ErrError error = Errno(1) // /* SQL error or missing database */ |
|
52 |
+ ErrInternal error = Errno(2) // /* Internal logic error in SQLite */ |
|
53 |
+ ErrPerm error = Errno(3) // /* Access permission denied */ |
|
54 |
+ ErrAbort error = Errno(4) // /* Callback routine requested an abort */ |
|
55 |
+ ErrBusy error = Errno(5) // /* The database file is locked */ |
|
56 |
+ ErrLocked error = Errno(6) // /* A table in the database is locked */ |
|
57 |
+ ErrNoMem error = Errno(7) // /* A malloc() failed */ |
|
58 |
+ ErrReadOnly error = Errno(8) // /* Attempt to write a readonly database */ |
|
59 |
+ ErrInterrupt error = Errno(9) // /* Operation terminated by sqlite3_interrupt()*/ |
|
60 |
+ ErrIOErr error = Errno(10) // /* Some kind of disk I/O error occurred */ |
|
61 |
+ ErrCorrupt error = Errno(11) // /* The database disk image is malformed */ |
|
62 |
+ ErrFull error = Errno(13) // /* Insertion failed because database is full */ |
|
63 |
+ ErrCantOpen error = Errno(14) // /* Unable to open the database file */ |
|
64 |
+ ErrEmpty error = Errno(16) // /* Database is empty */ |
|
65 |
+ ErrSchema error = Errno(17) // /* The database schema changed */ |
|
66 |
+ ErrTooBig error = Errno(18) // /* String or BLOB exceeds size limit */ |
|
67 |
+ ErrConstraint error = Errno(19) // /* Abort due to constraint violation */ |
|
68 |
+ ErrMismatch error = Errno(20) // /* Data type mismatch */ |
|
69 |
+ ErrMisuse error = Errno(21) // /* Library used incorrectly */ |
|
70 |
+ ErrNolfs error = Errno(22) // /* Uses OS features not supported on host */ |
|
71 |
+ ErrAuth error = Errno(23) // /* Authorization denied */ |
|
72 |
+ ErrFormat error = Errno(24) // /* Auxiliary database format error */ |
|
73 |
+ ErrRange error = Errno(25) // /* 2nd parameter to sqlite3_bind out of range */ |
|
74 |
+ ErrNotDB error = Errno(26) // /* File opened that is not a database file */ |
|
75 |
+ Row = Errno(100) // /* sqlite3_step() has another row ready */ |
|
76 |
+ Done = Errno(101) // /* sqlite3_step() has finished executing */ |
|
77 |
+) |
|
78 |
+ |
|
79 |
+var errText = map[Errno]string{ |
|
80 |
+ 1: "SQL error or missing database", |
|
81 |
+ 2: "Internal logic error in SQLite", |
|
82 |
+ 3: "Access permission denied", |
|
83 |
+ 4: "Callback routine requested an abort", |
|
84 |
+ 5: "The database file is locked", |
|
85 |
+ 6: "A table in the database is locked", |
|
86 |
+ 7: "A malloc() failed", |
|
87 |
+ 8: "Attempt to write a readonly database", |
|
88 |
+ 9: "Operation terminated by sqlite3_interrupt()*/", |
|
89 |
+ 10: "Some kind of disk I/O error occurred", |
|
90 |
+ 11: "The database disk image is malformed", |
|
91 |
+ 12: "NOT USED. Table or record not found", |
|
92 |
+ 13: "Insertion failed because database is full", |
|
93 |
+ 14: "Unable to open the database file", |
|
94 |
+ 15: "NOT USED. Database lock protocol error", |
|
95 |
+ 16: "Database is empty", |
|
96 |
+ 17: "The database schema changed", |
|
97 |
+ 18: "String or BLOB exceeds size limit", |
|
98 |
+ 19: "Abort due to constraint violation", |
|
99 |
+ 20: "Data type mismatch", |
|
100 |
+ 21: "Library used incorrectly", |
|
101 |
+ 22: "Uses OS features not supported on host", |
|
102 |
+ 23: "Authorization denied", |
|
103 |
+ 24: "Auxiliary database format error", |
|
104 |
+ 25: "2nd parameter to sqlite3_bind out of range", |
|
105 |
+ 26: "File opened that is not a database file", |
|
106 |
+ 100: "sqlite3_step() has another row ready", |
|
107 |
+ 101: "sqlite3_step() has finished executing", |
|
108 |
+} |
|
109 |
+ |
|
110 |
+func (c *Conn) error(rv C.int) error { |
|
111 |
+ if c == nil || c.db == nil { |
|
112 |
+ return errors.New("nil sqlite database") |
|
113 |
+ } |
|
114 |
+ if rv == 0 { |
|
115 |
+ return nil |
|
116 |
+ } |
|
117 |
+ if rv == 21 { // misuse |
|
118 |
+ return Errno(rv) |
|
119 |
+ } |
|
120 |
+ return errors.New(Errno(rv).Error() + ": " + C.GoString(C.sqlite3_errmsg(c.db))) |
|
121 |
+} |
|
122 |
+ |
|
123 |
+type Conn struct { |
|
124 |
+ db *C.sqlite3 |
|
125 |
+} |
|
126 |
+ |
|
127 |
+func Version() string { |
|
128 |
+ p := C.sqlite3_libversion() |
|
129 |
+ return C.GoString(p) |
|
130 |
+} |
|
131 |
+ |
|
132 |
+func Open(filename string) (*Conn, error) { |
|
133 |
+ if C.sqlite3_threadsafe() == 0 { |
|
134 |
+ return nil, errors.New("sqlite library was not compiled for thread-safe operation") |
|
135 |
+ } |
|
136 |
+ |
|
137 |
+ var db *C.sqlite3 |
|
138 |
+ name := C.CString(filename) |
|
139 |
+ defer C.free(unsafe.Pointer(name)) |
|
140 |
+ rv := C.sqlite3_open_v2(name, &db, |
|
141 |
+ C.SQLITE_OPEN_FULLMUTEX| |
|
142 |
+ C.SQLITE_OPEN_READWRITE| |
|
143 |
+ C.SQLITE_OPEN_CREATE, |
|
144 |
+ nil) |
|
145 |
+ if rv != 0 { |
|
146 |
+ return nil, Errno(rv) |
|
147 |
+ } |
|
148 |
+ if db == nil { |
|
149 |
+ return nil, errors.New("sqlite succeeded without returning a database") |
|
150 |
+ } |
|
151 |
+ return &Conn{db}, nil |
|
152 |
+} |
|
153 |
+ |
|
154 |
+func NewBackup(dst *Conn, dstTable string, src *Conn, srcTable string) (*Backup, error) { |
|
155 |
+ dname := C.CString(dstTable) |
|
156 |
+ sname := C.CString(srcTable) |
|
157 |
+ defer C.free(unsafe.Pointer(dname)) |
|
158 |
+ defer C.free(unsafe.Pointer(sname)) |
|
159 |
+ |
|
160 |
+ sb := C.sqlite3_backup_init(dst.db, dname, src.db, sname) |
|
161 |
+ if sb == nil { |
|
162 |
+ return nil, dst.error(C.sqlite3_errcode(dst.db)) |
|
163 |
+ } |
|
164 |
+ return &Backup{sb, dst, src}, nil |
|
165 |
+} |
|
166 |
+ |
|
167 |
+type Backup struct { |
|
168 |
+ sb *C.sqlite3_backup |
|
169 |
+ dst, src *Conn |
|
170 |
+} |
|
171 |
+ |
|
172 |
+func (b *Backup) Step(npage int) error { |
|
173 |
+ rv := C.sqlite3_backup_step(b.sb, C.int(npage)) |
|
174 |
+ if rv == 0 || Errno(rv) == ErrBusy || Errno(rv) == ErrLocked { |
|
175 |
+ return nil |
|
176 |
+ } |
|
177 |
+ return Errno(rv) |
|
178 |
+} |
|
179 |
+ |
|
180 |
+type BackupStatus struct { |
|
181 |
+ Remaining int |
|
182 |
+ PageCount int |
|
183 |
+} |
|
184 |
+ |
|
185 |
+func (b *Backup) Status() BackupStatus { |
|
186 |
+ return BackupStatus{int(C.sqlite3_backup_remaining(b.sb)), int(C.sqlite3_backup_pagecount(b.sb))} |
|
187 |
+} |
|
188 |
+ |
|
189 |
+func (b *Backup) Run(npage int, period time.Duration, c chan<- BackupStatus) error { |
|
190 |
+ var err error |
|
191 |
+ for { |
|
192 |
+ err = b.Step(npage) |
|
193 |
+ if err != nil { |
|
194 |
+ break |
|
195 |
+ } |
|
196 |
+ if c != nil { |
|
197 |
+ c <- b.Status() |
|
198 |
+ } |
|
199 |
+ time.Sleep(period) |
|
200 |
+ } |
|
201 |
+ return b.dst.error(C.sqlite3_errcode(b.dst.db)) |
|
202 |
+} |
|
203 |
+ |
|
204 |
+func (b *Backup) Close() error { |
|
205 |
+ if b.sb == nil { |
|
206 |
+ return errors.New("backup already closed") |
|
207 |
+ } |
|
208 |
+ C.sqlite3_backup_finish(b.sb) |
|
209 |
+ b.sb = nil |
|
210 |
+ return nil |
|
211 |
+} |
|
212 |
+ |
|
213 |
+func (c *Conn) BusyTimeout(ms int) error { |
|
214 |
+ rv := C.sqlite3_busy_timeout(c.db, C.int(ms)) |
|
215 |
+ if rv == 0 { |
|
216 |
+ return nil |
|
217 |
+ } |
|
218 |
+ return Errno(rv) |
|
219 |
+} |
|
220 |
+ |
|
221 |
+func (c *Conn) Exec(cmd string, args ...interface{}) error { |
|
222 |
+ s, err := c.Prepare(cmd) |
|
223 |
+ if err != nil { |
|
224 |
+ return err |
|
225 |
+ } |
|
226 |
+ defer s.Finalize() |
|
227 |
+ err = s.Exec(args...) |
|
228 |
+ if err != nil { |
|
229 |
+ return err |
|
230 |
+ } |
|
231 |
+ rv := C.sqlite3_step(s.stmt) |
|
232 |
+ if Errno(rv) != Done { |
|
233 |
+ return c.error(rv) |
|
234 |
+ } |
|
235 |
+ return nil |
|
236 |
+} |
|
237 |
+ |
|
238 |
+type Stmt struct { |
|
239 |
+ c *Conn |
|
240 |
+ stmt *C.sqlite3_stmt |
|
241 |
+ err error |
|
242 |
+ t0 time.Time |
|
243 |
+ sql string |
|
244 |
+ args string |
|
245 |
+} |
|
246 |
+ |
|
247 |
+func (c *Conn) Prepare(cmd string) (*Stmt, error) { |
|
248 |
+ if c == nil || c.db == nil { |
|
249 |
+ return nil, errors.New("nil sqlite database") |
|
250 |
+ } |
|
251 |
+ cmdstr := C.CString(cmd) |
|
252 |
+ defer C.free(unsafe.Pointer(cmdstr)) |
|
253 |
+ var stmt *C.sqlite3_stmt |
|
254 |
+ var tail *C.char |
|
255 |
+ rv := C.sqlite3_prepare_v2(c.db, cmdstr, C.int(len(cmd)+1), &stmt, &tail) |
|
256 |
+ if rv != 0 { |
|
257 |
+ return nil, c.error(rv) |
|
258 |
+ } |
|
259 |
+ return &Stmt{c: c, stmt: stmt, sql: cmd, t0: time.Now()}, nil |
|
260 |
+} |
|
261 |
+ |
|
262 |
+func (s *Stmt) Exec(args ...interface{}) error { |
|
263 |
+ s.args = fmt.Sprintf(" %v", []interface{}(args)) |
|
264 |
+ rv := C.sqlite3_reset(s.stmt) |
|
265 |
+ if rv != 0 { |
|
266 |
+ return s.c.error(rv) |
|
267 |
+ } |
|
268 |
+ |
|
269 |
+ n := int(C.sqlite3_bind_parameter_count(s.stmt)) |
|
270 |
+ if n != len(args) { |
|
271 |
+ return errors.New(fmt.Sprintf("incorrect argument count for Stmt.Exec: have %d want %d", len(args), n)) |
|
272 |
+ } |
|
273 |
+ |
|
274 |
+ for i, v := range args { |
|
275 |
+ var str string |
|
276 |
+ switch v := v.(type) { |
|
277 |
+ case []byte: |
|
278 |
+ var p *byte |
|
279 |
+ if len(v) > 0 { |
|
280 |
+ p = &v[0] |
|
281 |
+ } |
|
282 |
+ if rv := C.my_bind_blob(s.stmt, C.int(i+1), unsafe.Pointer(p), C.int(len(v))); rv != 0 { |
|
283 |
+ return s.c.error(rv) |
|
284 |
+ } |
|
285 |
+ continue |
|
286 |
+ |
|
287 |
+ case bool: |
|
288 |
+ if v { |
|
289 |
+ str = "1" |
|
290 |
+ } else { |
|
291 |
+ str = "0" |
|
292 |
+ } |
|
293 |
+ |
|
294 |
+ default: |
|
295 |
+ str = fmt.Sprint(v) |
|
296 |
+ } |
|
297 |
+ |
|
298 |
+ cstr := C.CString(str) |
|
299 |
+ rv := C.my_bind_text(s.stmt, C.int(i+1), cstr, C.int(len(str))) |
|
300 |
+ C.free(unsafe.Pointer(cstr)) |
|
301 |
+ if rv != 0 { |
|
302 |
+ return s.c.error(rv) |
|
303 |
+ } |
|
304 |
+ } |
|
305 |
+ return nil |
|
306 |
+} |
|
307 |
+ |
|
308 |
+func (s *Stmt) Error() error { |
|
309 |
+ return s.err |
|
310 |
+} |
|
311 |
+ |
|
312 |
+func (s *Stmt) Next() bool { |
|
313 |
+ rv := C.sqlite3_step(s.stmt) |
|
314 |
+ err := Errno(rv) |
|
315 |
+ if err == Row { |
|
316 |
+ return true |
|
317 |
+ } |
|
318 |
+ if err != Done { |
|
319 |
+ s.err = s.c.error(rv) |
|
320 |
+ } |
|
321 |
+ return false |
|
322 |
+} |
|
323 |
+ |
|
324 |
+func (s *Stmt) Reset() error { |
|
325 |
+ C.sqlite3_reset(s.stmt) |
|
326 |
+ return nil |
|
327 |
+} |
|
328 |
+ |
|
329 |
+func (s *Stmt) Scan(args ...interface{}) error { |
|
330 |
+ n := int(C.sqlite3_column_count(s.stmt)) |
|
331 |
+ if n != len(args) { |
|
332 |
+ return errors.New(fmt.Sprintf("incorrect argument count for Stmt.Scan: have %d want %d", len(args), n)) |
|
333 |
+ } |
|
334 |
+ |
|
335 |
+ for i, v := range args { |
|
336 |
+ n := C.sqlite3_column_bytes(s.stmt, C.int(i)) |
|
337 |
+ p := C.sqlite3_column_blob(s.stmt, C.int(i)) |
|
338 |
+ if p == nil && n > 0 { |
|
339 |
+ return errors.New("got nil blob") |
|
340 |
+ } |
|
341 |
+ var data []byte |
|
342 |
+ if n > 0 { |
|
343 |
+ data = (*[1 << 30]byte)(unsafe.Pointer(p))[0:n] |
|
344 |
+ } |
|
345 |
+ switch v := v.(type) { |
|
346 |
+ case *[]byte: |
|
347 |
+ *v = data |
|
348 |
+ case *string: |
|
349 |
+ *v = string(data) |
|
350 |
+ case *bool: |
|
351 |
+ *v = string(data) == "1" |
|
352 |
+ case *int: |
|
353 |
+ x, err := strconv.Atoi(string(data)) |
|
354 |
+ if err != nil { |
|
355 |
+ return errors.New("arg " + strconv.Itoa(i) + " as int: " + err.Error()) |
|
356 |
+ } |
|
357 |
+ *v = x |
|
358 |
+ case *int64: |
|
359 |
+ x, err := strconv.ParseInt(string(data), 10, 64) |
|
360 |
+ if err != nil { |
|
361 |
+ return errors.New("arg " + strconv.Itoa(i) + " as int64: " + err.Error()) |
|
362 |
+ } |
|
363 |
+ *v = x |
|
364 |
+ case *float64: |
|
365 |
+ x, err := strconv.ParseFloat(string(data), 64) |
|
366 |
+ if err != nil { |
|
367 |
+ return errors.New("arg " + strconv.Itoa(i) + " as float64: " + err.Error()) |
|
368 |
+ } |
|
369 |
+ *v = x |
|
370 |
+ default: |
|
371 |
+ return errors.New("unsupported type in Scan: " + reflect.TypeOf(v).String()) |
|
372 |
+ } |
|
373 |
+ } |
|
374 |
+ return nil |
|
375 |
+} |
|
376 |
+ |
|
377 |
+func (s *Stmt) SQL() string { |
|
378 |
+ return s.sql + s.args |
|
379 |
+} |
|
380 |
+ |
|
381 |
+func (s *Stmt) Nanoseconds() int64 { |
|
382 |
+ return time.Now().Sub(s.t0).Nanoseconds() |
|
383 |
+} |
|
384 |
+ |
|
385 |
+func (s *Stmt) Finalize() error { |
|
386 |
+ rv := C.sqlite3_finalize(s.stmt) |
|
387 |
+ if rv != 0 { |
|
388 |
+ return s.c.error(rv) |
|
389 |
+ } |
|
390 |
+ return nil |
|
391 |
+} |
|
392 |
+ |
|
393 |
+func (c *Conn) Close() error { |
|
394 |
+ if c == nil || c.db == nil { |
|
395 |
+ return errors.New("nil sqlite database") |
|
396 |
+ } |
|
397 |
+ rv := C.sqlite3_close(c.db) |
|
398 |
+ if rv != 0 { |
|
399 |
+ return c.error(rv) |
|
400 |
+ } |
|
401 |
+ c.db = nil |
|
402 |
+ return nil |
|
403 |
+} |
0 | 404 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,498 @@ |
0 |
+// Copyright 2010 The Go Authors. All rights reserved. |
|
1 |
+// Use of this source code is governed by a BSD-style |
|
2 |
+// license that can be found in the LICENSE file. |
|
3 |
+ |
|
4 |
+// Package sqlite3 provides access to the SQLite library, version 3. |
|
5 |
+// |
|
6 |
+// The package has no exported API. |
|
7 |
+// It registers a driver for the standard Go database/sql package. |
|
8 |
+// |
|
9 |
+// import _ "code.google.com/p/gosqlite/sqlite3" |
|
10 |
+// |
|
11 |
+// (For an alternate, earlier API, see the code.google.com/p/gosqlite/sqlite package.) |
|
12 |
+package sqlite |
|
13 |
+ |
|
14 |
+/* |
|
15 |
+#cgo LDFLAGS: -lsqlite3 |
|
16 |
+ |
|
17 |
+#include <sqlite3.h> |
|
18 |
+#include <stdlib.h> |
|
19 |
+ |
|
20 |
+// These wrappers are necessary because SQLITE_TRANSIENT |
|
21 |
+// is a pointer constant, and cgo doesn't translate them correctly. |
|
22 |
+// The definition in sqlite3.h is: |
|
23 |
+// |
|
24 |
+// typedef void (*sqlite3_destructor_type)(void*); |
|
25 |
+// #define SQLITE_STATIC ((sqlite3_destructor_type)0) |
|
26 |
+// #define SQLITE_TRANSIENT ((sqlite3_destructor_type)-1) |
|
27 |
+ |
|
28 |
+static int my_bind_text(sqlite3_stmt *stmt, int n, char *p, int np) { |
|
29 |
+ return sqlite3_bind_text(stmt, n, p, np, SQLITE_TRANSIENT); |
|
30 |
+} |
|
31 |
+static int my_bind_blob(sqlite3_stmt *stmt, int n, void *p, int np) { |
|
32 |
+ return sqlite3_bind_blob(stmt, n, p, np, SQLITE_TRANSIENT); |
|
33 |
+} |
|
34 |
+ |
|
35 |
+*/ |
|
36 |
+import "C" |
|
37 |
+ |
|
38 |
+import ( |
|
39 |
+ "database/sql" |
|
40 |
+ "database/sql/driver" |
|
41 |
+ "errors" |
|
42 |
+ "fmt" |
|
43 |
+ "io" |
|
44 |
+ "strings" |
|
45 |
+ "time" |
|
46 |
+ "unsafe" |
|
47 |
+) |
|
48 |
+ |
|
49 |
+func init() { |
|
50 |
+ sql.Register("sqlite3", impl{}) |
|
51 |
+} |
|
52 |
+ |
|
53 |
+type errno int |
|
54 |
+ |
|
55 |
+func (e errno) Error() string { |
|
56 |
+ s := errText[e] |
|
57 |
+ if s == "" { |
|
58 |
+ return fmt.Sprintf("errno %d", int(e)) |
|
59 |
+ } |
|
60 |
+ return s |
|
61 |
+} |
|
62 |
+ |
|
63 |
+var ( |
|
64 |
+ errError error = errno(1) // /* SQL error or missing database */ |
|
65 |
+ errInternal error = errno(2) // /* Internal logic error in SQLite */ |
|
66 |
+ errPerm error = errno(3) // /* Access permission denied */ |
|
67 |
+ errAbort error = errno(4) // /* Callback routine requested an abort */ |
|
68 |
+ errBusy error = errno(5) // /* The database file is locked */ |
|
69 |
+ errLocked error = errno(6) // /* A table in the database is locked */ |
|
70 |
+ errNoMem error = errno(7) // /* A malloc() failed */ |
|
71 |
+ errReadOnly error = errno(8) // /* Attempt to write a readonly database */ |
|
72 |
+ errInterrupt error = errno(9) // /* Operation terminated by sqlite3_interrupt()*/ |
|
73 |
+ errIOErr error = errno(10) // /* Some kind of disk I/O error occurred */ |
|
74 |
+ errCorrupt error = errno(11) // /* The database disk image is malformed */ |
|
75 |
+ errFull error = errno(13) // /* Insertion failed because database is full */ |
|
76 |
+ errCantOpen error = errno(14) // /* Unable to open the database file */ |
|
77 |
+ errEmpty error = errno(16) // /* Database is empty */ |
|
78 |
+ errSchema error = errno(17) // /* The database schema changed */ |
|
79 |
+ errTooBig error = errno(18) // /* String or BLOB exceeds size limit */ |
|
80 |
+ errConstraint error = errno(19) // /* Abort due to constraint violation */ |
|
81 |
+ errMismatch error = errno(20) // /* Data type mismatch */ |
|
82 |
+ errMisuse error = errno(21) // /* Library used incorrectly */ |
|
83 |
+ errNolfs error = errno(22) // /* Uses OS features not supported on host */ |
|
84 |
+ errAuth error = errno(23) // /* Authorization denied */ |
|
85 |
+ errFormat error = errno(24) // /* Auxiliary database format error */ |
|
86 |
+ errRange error = errno(25) // /* 2nd parameter to sqlite3_bind out of range */ |
|
87 |
+ errNotDB error = errno(26) // /* File opened that is not a database file */ |
|
88 |
+ stepRow = errno(100) // /* sqlite3_step() has another row ready */ |
|
89 |
+ stepDone = errno(101) // /* sqlite3_step() has finished executing */ |
|
90 |
+) |
|
91 |
+ |
|
92 |
+var errText = map[errno]string{ |
|
93 |
+ 1: "SQL error or missing database", |
|
94 |
+ 2: "Internal logic error in SQLite", |
|
95 |
+ 3: "Access permission denied", |
|
96 |
+ 4: "Callback routine requested an abort", |
|
97 |
+ 5: "The database file is locked", |
|
98 |
+ 6: "A table in the database is locked", |
|
99 |
+ 7: "A malloc() failed", |
|
100 |
+ 8: "Attempt to write a readonly database", |
|
101 |
+ 9: "Operation terminated by sqlite3_interrupt()*/", |
|
102 |
+ 10: "Some kind of disk I/O error occurred", |
|
103 |
+ 11: "The database disk image is malformed", |
|
104 |
+ 12: "NOT USED. Table or record not found", |
|
105 |
+ 13: "Insertion failed because database is full", |
|
106 |
+ 14: "Unable to open the database file", |
|
107 |
+ 15: "NOT USED. Database lock protocol error", |
|
108 |
+ 16: "Database is empty", |
|
109 |
+ 17: "The database schema changed", |
|
110 |
+ 18: "String or BLOB exceeds size limit", |
|
111 |
+ 19: "Abort due to constraint violation", |
|
112 |
+ 20: "Data type mismatch", |
|
113 |
+ 21: "Library used incorrectly", |
|
114 |
+ 22: "Uses OS features not supported on host", |
|
115 |
+ 23: "Authorization denied", |
|
116 |
+ 24: "Auxiliary database format error", |
|
117 |
+ 25: "2nd parameter to sqlite3_bind out of range", |
|
118 |
+ 26: "File opened that is not a database file", |
|
119 |
+ 100: "sqlite3_step() has another row ready", |
|
120 |
+ 101: "sqlite3_step() has finished executing", |
|
121 |
+} |
|
122 |
+ |
|
123 |
+type impl struct{} |
|
124 |
+ |
|
125 |
+func (impl) Open(name string) (driver.Conn, error) { |
|
126 |
+ if C.sqlite3_threadsafe() == 0 { |
|
127 |
+ return nil, errors.New("sqlite library was not compiled for thread-safe operation") |
|
128 |
+ } |
|
129 |
+ |
|
130 |
+ var db *C.sqlite3 |
|
131 |
+ cname := C.CString(name) |
|
132 |
+ defer C.free(unsafe.Pointer(cname)) |
|
133 |
+ rv := C.sqlite3_open_v2(cname, &db, |
|
134 |
+ C.SQLITE_OPEN_FULLMUTEX| |
|
135 |
+ C.SQLITE_OPEN_READWRITE| |
|
136 |
+ C.SQLITE_OPEN_CREATE, |
|
137 |
+ nil) |
|
138 |
+ if rv != 0 { |
|
139 |
+ return nil, errno(rv) |
|
140 |
+ } |
|
141 |
+ if db == nil { |
|
142 |
+ return nil, errors.New("sqlite succeeded without returning a database") |
|
143 |
+ } |
|
144 |
+ return &conn{db: db}, nil |
|
145 |
+} |
|
146 |
+ |
|
147 |
+type conn struct { |
|
148 |
+ db *C.sqlite3 |
|
149 |
+ closed bool |
|
150 |
+ tx bool |
|
151 |
+} |
|
152 |
+ |
|
153 |
+func (c *conn) error(rv C.int) error { |
|
154 |
+ if rv == 0 { |
|
155 |
+ return nil |
|
156 |
+ } |
|
157 |
+ if rv == 21 || c.closed { |
|
158 |
+ return errno(rv) |
|
159 |
+ } |
|
160 |
+ return errors.New(errno(rv).Error() + ": " + C.GoString(C.sqlite3_errmsg(c.db))) |
|
161 |
+} |
|
162 |
+ |
|
163 |
+func (c *conn) Prepare(cmd string) (driver.Stmt, error) { |
|
164 |
+ if c.closed { |
|
165 |
+ panic("database/sql/driver: misuse of sqlite driver: Prepare after Close") |
|
166 |
+ } |
|
167 |
+ cmdstr := C.CString(cmd) |
|
168 |
+ defer C.free(unsafe.Pointer(cmdstr)) |
|
169 |
+ var s *C.sqlite3_stmt |
|
170 |
+ var tail *C.char |
|
171 |
+ rv := C.sqlite3_prepare_v2(c.db, cmdstr, C.int(len(cmd)+1), &s, &tail) |
|
172 |
+ if rv != 0 { |
|
173 |
+ return nil, c.error(rv) |
|
174 |
+ } |
|
175 |
+ return &stmt{c: c, stmt: s, sql: cmd, t0: time.Now()}, nil |
|
176 |
+} |
|
177 |
+ |
|
178 |
+func (c *conn) Close() error { |
|
179 |
+ if c.closed { |
|
180 |
+ panic("database/sql/driver: misuse of sqlite driver: multiple Close") |
|
181 |
+ } |
|
182 |
+ c.closed = true |
|
183 |
+ rv := C.sqlite3_close(c.db) |
|
184 |
+ c.db = nil |
|
185 |
+ return c.error(rv) |
|
186 |
+} |
|
187 |
+ |
|
188 |
+func (c *conn) exec(cmd string) error { |
|
189 |
+ cstring := C.CString(cmd) |
|
190 |
+ defer C.free(unsafe.Pointer(cstring)) |
|
191 |
+ rv := C.sqlite3_exec(c.db, cstring, nil, nil, nil) |
|
192 |
+ return c.error(rv) |
|
193 |
+} |
|
194 |
+ |
|
195 |
+func (c *conn) Begin() (driver.Tx, error) { |
|
196 |
+ if c.tx { |
|
197 |
+ panic("database/sql/driver: misuse of sqlite driver: multiple Tx") |
|
198 |
+ } |
|
199 |
+ if err := c.exec("BEGIN TRANSACTION"); err != nil { |
|
200 |
+ return nil, err |
|
201 |
+ } |
|
202 |
+ c.tx = true |
|
203 |
+ return &tx{c}, nil |
|
204 |
+} |
|
205 |
+ |
|
206 |
+type tx struct { |
|
207 |
+ c *conn |
|
208 |
+} |
|
209 |
+ |
|
210 |
+func (t *tx) Commit() error { |
|
211 |
+ if t.c == nil || !t.c.tx { |
|
212 |
+ panic("database/sql/driver: misuse of sqlite driver: extra Commit") |
|
213 |
+ } |
|
214 |
+ t.c.tx = false |
|
215 |
+ err := t.c.exec("COMMIT TRANSACTION") |
|
216 |
+ t.c = nil |
|
217 |
+ return err |
|
218 |
+} |
|
219 |
+ |
|
220 |
+func (t *tx) Rollback() error { |
|
221 |
+ if t.c == nil || !t.c.tx { |
|
222 |
+ panic("database/sql/driver: misuse of sqlite driver: extra Rollback") |
|
223 |
+ } |
|
224 |
+ t.c.tx = false |
|
225 |
+ err := t.c.exec("ROLLBACK") |
|
226 |
+ t.c = nil |
|
227 |
+ return err |
|
228 |
+} |
|
229 |
+ |
|
230 |
+type stmt struct { |
|
231 |
+ c *conn |
|
232 |
+ stmt *C.sqlite3_stmt |
|
233 |
+ err error |
|
234 |
+ t0 time.Time |
|
235 |
+ sql string |
|
236 |
+ args string |
|
237 |
+ closed bool |
|
238 |
+ rows bool |
|
239 |
+ colnames []string |
|
240 |
+ coltypes []string |
|
241 |
+} |
|
242 |
+ |
|
243 |
+func (s *stmt) Close() error { |
|
244 |
+ if s.rows { |
|
245 |
+ panic("database/sql/driver: misuse of sqlite driver: Close with active Rows") |
|
246 |
+ } |
|
247 |
+ if s.closed { |
|
248 |
+ panic("database/sql/driver: misuse of sqlite driver: double Close of Stmt") |
|
249 |
+ } |
|
250 |
+ s.closed = true |
|
251 |
+ rv := C.sqlite3_finalize(s.stmt) |
|
252 |
+ if rv != 0 { |
|
253 |
+ return s.c.error(rv) |
|
254 |
+ } |
|
255 |
+ return nil |
|
256 |
+} |
|
257 |
+ |
|
258 |
+func (s *stmt) NumInput() int { |
|
259 |
+ if s.closed { |
|
260 |
+ panic("database/sql/driver: misuse of sqlite driver: NumInput after Close") |
|
261 |
+ } |
|
262 |
+ return int(C.sqlite3_bind_parameter_count(s.stmt)) |
|
263 |
+} |
|
264 |
+ |
|
265 |
+func (s *stmt) reset() error { |
|
266 |
+ return s.c.error(C.sqlite3_reset(s.stmt)) |
|
267 |
+} |
|
268 |
+ |
|
269 |
+func (s *stmt) start(args []driver.Value) error { |
|
270 |
+ if err := s.reset(); err != nil { |
|
271 |
+ return err |
|
272 |
+ } |
|
273 |
+ |
|
274 |
+ n := int(C.sqlite3_bind_parameter_count(s.stmt)) |
|
275 |
+ if n != len(args) { |
|
276 |
+ return fmt.Errorf("incorrect argument count for command: have %d want %d", len(args), n) |
|
277 |
+ } |
|
278 |
+ |
|
279 |
+ for i, v := range args { |
|
280 |
+ var str string |
|
281 |
+ switch v := v.(type) { |
|
282 |
+ case nil: |
|
283 |
+ if rv := C.sqlite3_bind_null(s.stmt, C.int(i+1)); rv != 0 { |
|
284 |
+ return s.c.error(rv) |
|
285 |
+ } |
|
286 |
+ continue |
|
287 |
+ |
|
288 |
+ case float64: |
|
289 |
+ if rv := C.sqlite3_bind_double(s.stmt, C.int(i+1), C.double(v)); rv != 0 { |
|
290 |
+ return s.c.error(rv) |
|
291 |
+ } |
|
292 |
+ continue |
|
293 |
+ |
|
294 |
+ case int64: |
|
295 |
+ if rv := C.sqlite3_bind_int64(s.stmt, C.int(i+1), C.sqlite3_int64(v)); rv != 0 { |
|
296 |
+ return s.c.error(rv) |
|
297 |
+ } |
|
298 |
+ continue |
|
299 |
+ |
|
300 |
+ case []byte: |
|
301 |
+ var p *byte |
|
302 |
+ if len(v) > 0 { |
|
303 |
+ p = &v[0] |
|
304 |
+ } |
|
305 |
+ if rv := C.my_bind_blob(s.stmt, C.int(i+1), unsafe.Pointer(p), C.int(len(v))); rv != 0 { |
|
306 |
+ return s.c.error(rv) |
|
307 |
+ } |
|
308 |
+ continue |
|
309 |
+ |
|
310 |
+ case bool: |
|
311 |
+ var vi int64 |
|
312 |
+ if v { |
|
313 |
+ vi = 1 |
|
314 |
+ } |
|
315 |
+ if rv := C.sqlite3_bind_int64(s.stmt, C.int(i+1), C.sqlite3_int64(vi)); rv != 0 { |
|
316 |
+ return s.c.error(rv) |
|
317 |
+ } |
|
318 |
+ continue |
|
319 |
+ |
|
320 |
+ case time.Time: |
|
321 |
+ str = v.UTC().Format(timefmt[0]) |
|
322 |
+ |
|
323 |
+ case string: |
|
324 |
+ str = v |
|
325 |
+ |
|
326 |
+ default: |
|
327 |
+ str = fmt.Sprint(v) |
|
328 |
+ } |
|
329 |
+ |
|
330 |
+ cstr := C.CString(str) |
|
331 |
+ rv := C.my_bind_text(s.stmt, C.int(i+1), cstr, C.int(len(str))) |
|
332 |
+ C.free(unsafe.Pointer(cstr)) |
|
333 |
+ if rv != 0 { |
|
334 |
+ return s.c.error(rv) |
|
335 |
+ } |
|
336 |
+ } |
|
337 |
+ |
|
338 |
+ return nil |
|
339 |
+} |
|
340 |
+ |
|
341 |
+func (s *stmt) Exec(args []driver.Value) (driver.Result, error) { |
|
342 |
+ if s.closed { |
|
343 |
+ panic("database/sql/driver: misuse of sqlite driver: Exec after Close") |
|
344 |
+ } |
|
345 |
+ if s.rows { |
|
346 |
+ panic("database/sql/driver: misuse of sqlite driver: Exec with active Rows") |
|
347 |
+ } |
|
348 |
+ |
|
349 |
+ err := s.start(args) |
|
350 |
+ if err != nil { |
|
351 |
+ return nil, err |
|
352 |
+ } |
|
353 |
+ |
|
354 |
+ rv := C.sqlite3_step(s.stmt) |
|
355 |
+ if errno(rv) != stepDone { |
|
356 |
+ if rv == 0 { |
|
357 |
+ rv = 21 // errMisuse |
|
358 |
+ } |
|
359 |
+ return nil, s.c.error(rv) |
|
360 |
+ } |
|
361 |
+ |
|
362 |
+ id := int64(C.sqlite3_last_insert_rowid(s.c.db)) |
|
363 |
+ rows := int64(C.sqlite3_changes(s.c.db)) |
|
364 |
+ return &result{id, rows}, nil |
|
365 |
+} |
|
366 |
+ |
|
367 |
+func (s *stmt) Query(args []driver.Value) (driver.Rows, error) { |
|
368 |
+ if s.closed { |
|
369 |
+ panic("database/sql/driver: misuse of sqlite driver: Query after Close") |
|
370 |
+ } |
|
371 |
+ if s.rows { |
|
372 |
+ panic("database/sql/driver: misuse of sqlite driver: Query with active Rows") |
|
373 |
+ } |
|
374 |
+ |
|
375 |
+ err := s.start(args) |
|
376 |
+ if err != nil { |
|
377 |
+ return nil, err |
|
378 |
+ } |
|
379 |
+ |
|
380 |
+ s.rows = true |
|
381 |
+ if s.colnames == nil { |
|
382 |
+ n := int64(C.sqlite3_column_count(s.stmt)) |
|
383 |
+ s.colnames = make([]string, n) |
|
384 |
+ s.coltypes = make([]string, n) |
|
385 |
+ for i := range s.colnames { |
|
386 |
+ s.colnames[i] = C.GoString(C.sqlite3_column_name(s.stmt, C.int(i))) |
|
387 |
+ s.coltypes[i] = strings.ToLower(C.GoString(C.sqlite3_column_decltype(s.stmt, C.int(i)))) |
|
388 |
+ } |
|
389 |
+ } |
|
390 |
+ return &rows{s}, nil |
|
391 |
+} |
|
392 |
+ |
|
393 |
+type rows struct { |
|
394 |
+ s *stmt |
|
395 |
+} |
|
396 |
+ |
|
397 |
+func (r *rows) Columns() []string { |
|
398 |
+ if r.s == nil { |
|
399 |
+ panic("database/sql/driver: misuse of sqlite driver: Columns of closed Rows") |
|
400 |
+ } |
|
401 |
+ return r.s.colnames |
|
402 |
+} |
|
403 |
+ |
|
404 |
+const maxslice = 1<<31 - 1 |
|
405 |
+ |
|
406 |
+var timefmt = []string{ |
|
407 |
+ "2006-01-02 15:04:05.999999999", |
|
408 |
+ "2006-01-02T15:04:05.999999999", |
|
409 |
+ "2006-01-02 15:04:05", |
|
410 |
+ "2006-01-02T15:04:05", |
|
411 |
+ "2006-01-02 15:04", |
|
412 |
+ "2006-01-02T15:04", |
|
413 |
+ "2006-01-02", |
|
414 |
+} |
|
415 |
+ |
|
416 |
+func (r *rows) Next(dst []driver.Value) error { |
|
417 |
+ if r.s == nil { |
|
418 |
+ panic("database/sql/driver: misuse of sqlite driver: Next of closed Rows") |
|
419 |
+ } |
|
420 |
+ |
|
421 |
+ rv := C.sqlite3_step(r.s.stmt) |
|
422 |
+ if errno(rv) != stepRow { |
|
423 |
+ if errno(rv) == stepDone { |
|
424 |
+ return io.EOF |
|
425 |
+ } |
|
426 |
+ if rv == 0 { |
|
427 |
+ rv = 21 |
|
428 |
+ } |
|
429 |
+ return r.s.c.error(rv) |
|
430 |
+ } |
|
431 |
+ |
|
432 |
+ for i := range dst { |
|
433 |
+ switch typ := C.sqlite3_column_type(r.s.stmt, C.int(i)); typ { |
|
434 |
+ default: |
|
435 |
+ return fmt.Errorf("unexpected sqlite3 column type %d", typ) |
|
436 |
+ case C.SQLITE_INTEGER: |
|
437 |
+ val := int64(C.sqlite3_column_int64(r.s.stmt, C.int(i))) |
|
438 |
+ switch r.s.coltypes[i] { |
|
439 |
+ case "timestamp", "datetime": |
|
440 |
+ dst[i] = time.Unix(val, 0).UTC() |
|
441 |
+ case "boolean": |
|
442 |
+ dst[i] = val > 0 |
|
443 |
+ default: |
|
444 |
+ dst[i] = val |
|
445 |
+ } |
|
446 |
+ |
|
447 |
+ case C.SQLITE_FLOAT: |
|
448 |
+ dst[i] = float64(C.sqlite3_column_double(r.s.stmt, C.int(i))) |
|
449 |
+ |
|
450 |
+ case C.SQLITE_BLOB, C.SQLITE_TEXT: |
|
451 |
+ n := int(C.sqlite3_column_bytes(r.s.stmt, C.int(i))) |
|
452 |
+ var b []byte |
|
453 |
+ if n > 0 { |
|
454 |
+ p := C.sqlite3_column_blob(r.s.stmt, C.int(i)) |
|
455 |
+ b = (*[maxslice]byte)(unsafe.Pointer(p))[:n] |
|
456 |
+ } |
|
457 |
+ dst[i] = b |
|
458 |
+ switch r.s.coltypes[i] { |
|
459 |
+ case "timestamp", "datetime": |
|
460 |
+ dst[i] = time.Time{} |
|
461 |
+ s := string(b) |
|
462 |
+ for _, f := range timefmt { |
|
463 |
+ if t, err := time.Parse(f, s); err == nil { |
|
464 |
+ dst[i] = t |
|
465 |
+ break |
|
466 |
+ } |
|
467 |
+ } |
|
468 |
+ } |
|
469 |
+ |
|
470 |
+ case C.SQLITE_NULL: |
|
471 |
+ dst[i] = nil |
|
472 |
+ } |
|
473 |
+ } |
|
474 |
+ return nil |
|
475 |
+} |
|
476 |
+ |
|
477 |
+func (r *rows) Close() error { |
|
478 |
+ if r.s == nil { |
|
479 |
+ panic("database/sql/driver: misuse of sqlite driver: Close of closed Rows") |
|
480 |
+ } |
|
481 |
+ r.s.rows = false |
|
482 |
+ r.s = nil |
|
483 |
+ return nil |
|
484 |
+} |
|
485 |
+ |
|
486 |
+type result struct { |
|
487 |
+ id int64 |
|
488 |
+ rows int64 |
|
489 |
+} |
|
490 |
+ |
|
491 |
+func (r *result) LastInsertId() (int64, error) { |
|
492 |
+ return r.id, nil |
|
493 |
+} |
|
494 |
+ |
|
495 |
+func (r *result) RowsAffected() (int64, error) { |
|
496 |
+ return r.rows, nil |
|
497 |
+} |