mirror of
https://github.com/clearlinux/docker.git
synced 2026-08-21 05:07:22 +00:00
Add sqlite as graphdb backend
Add build steps to compile docker statically with CGO enabled
This commit is contained in:
+6
-8
@@ -33,16 +33,14 @@ run apt-get update
|
||||
run apt-get install -y -q curl
|
||||
run apt-get install -y -q git
|
||||
run apt-get install -y -q mercurial
|
||||
run apt-get install -y -q build-essential
|
||||
run apt-get install -y -q build-essential libsqlite3-dev
|
||||
|
||||
# Install Go from source (for eventual cross-compiling)
|
||||
env CGO_ENABLED 0
|
||||
run curl -s https://go.googlecode.com/files/go1.1.2.src.tar.gz | tar -v -C / -xz && mv /go /goroot
|
||||
run cd /goroot/src && ./make.bash
|
||||
env GOROOT /goroot
|
||||
env PATH $PATH:/goroot/bin
|
||||
# Install Go
|
||||
run curl -s https://go.googlecode.com/files/go1.2rc1.src.tar.gz | tar -v -C /usr/local -xz
|
||||
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
|
||||
env PATH /usr/local/go/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin
|
||||
env GOPATH /go:/go/src/github.com/dotcloud/docker/vendor
|
||||
|
||||
run cd /tmp && echo 'package main' > t.go && go test -a -i -v
|
||||
# Ubuntu stuff
|
||||
run apt-get install -y -q ruby1.9.3 rubygems libffi-dev
|
||||
run gem install --no-rdoc --no-ri fpm
|
||||
|
||||
+1
-1
@@ -1153,7 +1153,7 @@ func (cli *DockerCli) CmdLink(args ...string) error {
|
||||
}
|
||||
body := map[string]string{
|
||||
"currentName": cmd.Arg(0),
|
||||
"newName": cmd.Arg(10),
|
||||
"newName": cmd.Arg(1),
|
||||
}
|
||||
|
||||
_, _, err := cli.call("POST", "/containers/link", body)
|
||||
|
||||
+275
-150
@@ -1,15 +1,35 @@
|
||||
package gograph
|
||||
|
||||
import (
|
||||
_ "code.google.com/p/gosqlite/sqlite3"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Entity with a unique id and user defined value
|
||||
const (
|
||||
createEntityTable = `
|
||||
CREATE TABLE IF NOT EXISTS entity (
|
||||
id text NOT NULL PRIMARY KEY
|
||||
);`
|
||||
|
||||
createEdgeTable = `
|
||||
CREATE TABLE IF NOT EXISTS edge (
|
||||
"entity_id" text NOT NULL,
|
||||
"parent_id" text NULL,
|
||||
"name" text NOT NULL,
|
||||
CONSTRAINT "parent_fk" FOREIGN KEY ("parent_id") REFERENCES "entity" ("id"),
|
||||
CONSTRAINT "entity_fk" FOREIGN KEY ("entity_id") REFERENCES "entity" ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "name_parent_ix" ON "edge" (parent_id, name);
|
||||
`
|
||||
)
|
||||
|
||||
// Entity with a unique id
|
||||
type Entity struct {
|
||||
id string
|
||||
Value interface{}
|
||||
id string
|
||||
}
|
||||
|
||||
// An Edge connects two entities together
|
||||
@@ -26,111 +46,173 @@ type WalkFunc func(fullPath string, entity *Entity) error
|
||||
|
||||
// Graph database for storing entities and their relationships
|
||||
type Database struct {
|
||||
entities Entities
|
||||
edges Edges
|
||||
mux sync.Mutex
|
||||
|
||||
dbPath string
|
||||
rootID string
|
||||
}
|
||||
|
||||
// Create a new graph database initialized with a root entity
|
||||
func NewDatabase(rootPath, rootId string) (*Database, error) {
|
||||
db := &Database{Entities{}, Edges{}, sync.Mutex{}, rootId}
|
||||
e := &Entity{
|
||||
id: rootId,
|
||||
func NewDatabase(dbPath, rootId string) (*Database, error) {
|
||||
db := &Database{dbPath, rootId}
|
||||
if _, err := os.Stat(dbPath); err == nil {
|
||||
return db, nil
|
||||
}
|
||||
db.entities[rootId] = e
|
||||
|
||||
edge := &Edge{
|
||||
EntityID: rootId,
|
||||
Name: "/",
|
||||
conn, err := db.openConn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
db.edges = append(db.edges, edge)
|
||||
defer conn.Close()
|
||||
|
||||
if _, err := conn.Exec(createEntityTable); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := conn.Exec(createEdgeTable); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rollback := func() {
|
||||
conn.Exec("ROLLBACK")
|
||||
}
|
||||
|
||||
// Create root entities
|
||||
if _, err := conn.Exec("BEGIN"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := conn.Exec("INSERT INTO entity (id) VALUES (?);", rootId); err != nil {
|
||||
rollback()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := conn.Exec("INSERT INTO edge (entity_id, name) VALUES(?,?);", rootId, "/"); err != nil {
|
||||
rollback()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := conn.Exec("COMMIT"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// Set the entity id for a given path
|
||||
func (db *Database) Set(fullPath, id string) (*Entity, error) {
|
||||
db.mux.Lock()
|
||||
defer db.mux.Unlock()
|
||||
|
||||
e, exists := db.entities[id]
|
||||
if !exists {
|
||||
e = &Entity{
|
||||
id: id,
|
||||
}
|
||||
db.entities[id] = e
|
||||
conn, err := db.openConn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
rollback := func() {
|
||||
conn.Exec("ROLLBACK")
|
||||
}
|
||||
if _, err := conn.Exec("BEGIN"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var entityId string
|
||||
if err := conn.QueryRow("SELECT id FROM entity WHERE id = ?;", id).Scan(&entityId); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
if _, err := conn.Exec("INSERT INTO entity (id) VALUES(?);", id); err != nil {
|
||||
rollback()
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
rollback()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
e := &Entity{id}
|
||||
|
||||
parentPath, name := splitPath(fullPath)
|
||||
if err := db.setEdge(parentPath, name, e); err != nil {
|
||||
if err := db.setEdge(conn, parentPath, name, e); err != nil {
|
||||
rollback()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := conn.Exec("COMMIT"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (db *Database) setEdge(parentPath, name string, e *Entity) error {
|
||||
parent := db.Get(parentPath)
|
||||
if parent == nil {
|
||||
return fmt.Errorf("Parent does not exist for path: %s", parentPath)
|
||||
func (db *Database) setEdge(conn *sql.DB, parentPath, name string, e *Entity) error {
|
||||
parent, err := db.get(conn, parentPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if parent.id == e.id {
|
||||
return fmt.Errorf("Cannot set self as child")
|
||||
}
|
||||
|
||||
edge := &Edge{
|
||||
ParentID: parent.id,
|
||||
EntityID: e.id,
|
||||
Name: name,
|
||||
if _, err := conn.Exec("INSERT INTO edge (parent_id, name, entity_id) VALUES (?,?,?);", parent.id, name, e.id); err != nil {
|
||||
return err
|
||||
}
|
||||
if db.edges.Exists(parent.id, name) {
|
||||
return fmt.Errorf("Relationship already exists for %s/%s", parentPath, name)
|
||||
}
|
||||
db.edges = append(db.edges, edge)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return the root "/" entity for the database
|
||||
func (db *Database) RootEntity() *Entity {
|
||||
return db.entities[db.rootID]
|
||||
return &Entity{
|
||||
id: db.rootID,
|
||||
}
|
||||
}
|
||||
|
||||
// Return the entity for a given path
|
||||
func (db *Database) Get(name string) *Entity {
|
||||
conn, err := db.openConn()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
e, err := db.get(conn, name)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
func (db *Database) get(conn *sql.DB, name string) (*Entity, error) {
|
||||
e := db.RootEntity()
|
||||
// We always know the root name so return it if
|
||||
// it is requested
|
||||
if name == "/" {
|
||||
return e
|
||||
return e, nil
|
||||
}
|
||||
|
||||
parts := split(name)
|
||||
for i := 1; i < len(parts); i++ {
|
||||
p := parts[i]
|
||||
|
||||
next := db.child(e, p)
|
||||
next := db.child(conn, e, p)
|
||||
if next == nil {
|
||||
return nil
|
||||
return nil, fmt.Errorf("Cannot find child")
|
||||
}
|
||||
e = next
|
||||
|
||||
}
|
||||
return e
|
||||
return e, nil
|
||||
|
||||
}
|
||||
|
||||
// List all entities by from the name
|
||||
// The key will be the full path of the entity
|
||||
func (db *Database) List(name string, depth int) Entities {
|
||||
out := Entities{}
|
||||
for c := range db.children(name, depth) {
|
||||
conn, err := db.openConn()
|
||||
if err != nil {
|
||||
return out
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
for c := range db.children(conn, name, depth) {
|
||||
out[c.FullPath] = c.Entity
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error {
|
||||
for c := range db.children(name, depth) {
|
||||
conn, err := db.openConn()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
for c := range db.children(conn, name, depth) {
|
||||
if err := walkFunc(c.FullPath, c.Entity); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -140,14 +222,46 @@ func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error {
|
||||
|
||||
// Return the refrence count for a specified id
|
||||
func (db *Database) Refs(id string) int {
|
||||
return len(db.RefPaths(id))
|
||||
conn, err := db.openConn()
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
var count int
|
||||
if err := conn.QueryRow("SELECT COUNT(*) FROM edge WHERE entity_id = ?;", id).Scan(&count); err != nil {
|
||||
return 0
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// Return all the id's path references
|
||||
func (db *Database) RefPaths(id string) Edges {
|
||||
refs := db.edges.Search(func(e *Edge) bool {
|
||||
return e.EntityID == id
|
||||
})
|
||||
refs := Edges{}
|
||||
conn, err := db.openConn()
|
||||
if err != nil {
|
||||
return refs
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
rows, err := conn.Query("SELECT name, parent_id FROM edge WHERE entity_id = ?;", id)
|
||||
if err != nil {
|
||||
return refs
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var name string
|
||||
var parentId string
|
||||
if err := rows.Scan(&name, &parentId); err != nil {
|
||||
return refs
|
||||
}
|
||||
refs = append(refs, &Edge{
|
||||
EntityID: id,
|
||||
Name: name,
|
||||
ParentID: parentId,
|
||||
})
|
||||
}
|
||||
return refs
|
||||
}
|
||||
|
||||
@@ -156,54 +270,64 @@ func (db *Database) Delete(name string) error {
|
||||
if name == "/" {
|
||||
return fmt.Errorf("Cannot delete root entity")
|
||||
}
|
||||
db.mux.Lock()
|
||||
defer db.mux.Unlock()
|
||||
conn, err := db.openConn()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
parentPath, n := splitPath(name)
|
||||
parent := db.Get(parentPath)
|
||||
if parent == nil {
|
||||
return fmt.Errorf("Cannot find parent for %s", parentPath)
|
||||
parent, err := db.get(conn, parentPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
edge, i := db.edges.Get(parent.id, n)
|
||||
if edge == nil {
|
||||
return fmt.Errorf("Edge does not exist at %s", name)
|
||||
}
|
||||
db.deleteEdgeAtIndex(i)
|
||||
|
||||
if _, err := conn.Exec("DELETE FROM edge WHERE parent_id = ? AND name = ?;", parent.id, n); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) deleteEdgeAtIndex(i int) {
|
||||
db.edges[len(db.edges)-1], db.edges[i], db.edges = nil, db.edges[len(db.edges)-1], db.edges[:len(db.edges)-1]
|
||||
}
|
||||
|
||||
// Remove the entity with the specified id
|
||||
// Walk the graph to make sure all references to the entity
|
||||
// are removed and return the number of references removed
|
||||
func (db *Database) Purge(id string) (int, error) {
|
||||
db.mux.Lock()
|
||||
defer db.mux.Unlock()
|
||||
conn, err := db.openConn()
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
getIndex := func(e *Edge) int {
|
||||
for i, edge := range db.edges {
|
||||
if edge.EntityID == e.EntityID &&
|
||||
edge.Name == e.Name &&
|
||||
edge.ParentID == e.ParentID {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
rollback := func() {
|
||||
conn.Exec("ROLLBACK")
|
||||
}
|
||||
|
||||
refsToDelete := db.RefPaths(id)
|
||||
for i, e := range refsToDelete {
|
||||
index := getIndex(e)
|
||||
if index == -1 {
|
||||
return i + 1, fmt.Errorf("Cannot find index for %s %s", e.ParentID, e.Name)
|
||||
}
|
||||
db.deleteEdgeAtIndex(index)
|
||||
if _, err := conn.Exec("BEGIN"); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
return len(refsToDelete), nil
|
||||
|
||||
// Delete all edges
|
||||
rows, err := conn.Exec("DELETE FROM edge WHERE entity_id = ?;", id)
|
||||
if err != nil {
|
||||
rollback()
|
||||
return -1, err
|
||||
}
|
||||
|
||||
changes, err := rows.RowsAffected()
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
// Delete entity
|
||||
if _, err := conn.Exec("DELETE FROM entity where id = ?;", id); err != nil {
|
||||
rollback()
|
||||
return -1, err
|
||||
}
|
||||
|
||||
if _, err := conn.Exec("COMMIT"); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
return int(changes), nil
|
||||
}
|
||||
|
||||
// Rename an edge for a given path
|
||||
@@ -215,19 +339,28 @@ func (db *Database) Rename(currentName, newName string) error {
|
||||
return fmt.Errorf("Cannot rename when root paths do not match %s != %s", parentPath, newParentPath)
|
||||
}
|
||||
|
||||
db.mux.Lock()
|
||||
defer db.mux.Unlock()
|
||||
|
||||
parent := db.Get(parentPath)
|
||||
if parent == nil {
|
||||
return fmt.Errorf("Cannot locate parent for %s", currentName)
|
||||
conn, err := db.openConn()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
edge, _ := db.edges.Get(parent.id, name)
|
||||
if edge == nil {
|
||||
defer conn.Close()
|
||||
|
||||
parent, err := db.get(conn, parentPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rows, err := conn.Exec("UPDATE edge SET name = ? WHERE parent_id = ? AND name = ?;", newEdgeName, parent.id, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i, err := rows.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if i == 0 {
|
||||
return fmt.Errorf("Cannot locate edge for %s %s", parent.id, name)
|
||||
}
|
||||
edge.Name = newEdgeName
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -238,38 +371,52 @@ type WalkMeta struct {
|
||||
Edge *Edge
|
||||
}
|
||||
|
||||
func (db *Database) children(name string, depth int) <-chan WalkMeta {
|
||||
func (db *Database) children(conn *sql.DB, name string, depth int) <-chan WalkMeta {
|
||||
out := make(chan WalkMeta)
|
||||
e := db.Get(name)
|
||||
|
||||
if e == nil {
|
||||
e, err := db.get(conn, name)
|
||||
if err != nil {
|
||||
close(out)
|
||||
return out
|
||||
}
|
||||
|
||||
go func() {
|
||||
for _, edge := range db.edges {
|
||||
if edge.ParentID == e.id {
|
||||
child := db.entities[edge.EntityID]
|
||||
rows, err := conn.Query("SELECT entity_id, name FROM edge where parent_id = ?;", e.id)
|
||||
if err != nil {
|
||||
close(out)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
meta := WalkMeta{
|
||||
Parent: e,
|
||||
Entity: child,
|
||||
FullPath: path.Join(name, edge.Name),
|
||||
Edge: edge,
|
||||
}
|
||||
out <- meta
|
||||
if depth == 0 {
|
||||
continue
|
||||
}
|
||||
nDepth := depth
|
||||
if depth != -1 {
|
||||
nDepth -= 1
|
||||
}
|
||||
sc := db.children(meta.FullPath, nDepth)
|
||||
for c := range sc {
|
||||
out <- c
|
||||
}
|
||||
for rows.Next() {
|
||||
var entityId, entityName string
|
||||
if err := rows.Scan(&entityId, &entityName); err != nil {
|
||||
// Log error
|
||||
continue
|
||||
}
|
||||
child := &Entity{entityId}
|
||||
edge := &Edge{
|
||||
ParentID: e.id,
|
||||
Name: entityName,
|
||||
EntityID: child.id,
|
||||
}
|
||||
|
||||
meta := WalkMeta{
|
||||
Parent: e,
|
||||
Entity: child,
|
||||
FullPath: path.Join(name, edge.Name),
|
||||
Edge: edge,
|
||||
}
|
||||
|
||||
out <- meta
|
||||
if depth == 0 {
|
||||
continue
|
||||
}
|
||||
nDepth := depth
|
||||
if depth != -1 {
|
||||
nDepth -= 1
|
||||
}
|
||||
sc := db.children(conn, meta.FullPath, nDepth)
|
||||
for c := range sc {
|
||||
out <- c
|
||||
}
|
||||
}
|
||||
close(out)
|
||||
@@ -278,12 +425,16 @@ func (db *Database) children(name string, depth int) <-chan WalkMeta {
|
||||
}
|
||||
|
||||
// Return the entity based on the parent path and name
|
||||
func (db *Database) child(parent *Entity, name string) *Entity {
|
||||
edge, _ := db.edges.Get(parent.id, name)
|
||||
if edge == nil {
|
||||
func (db *Database) child(conn *sql.DB, parent *Entity, name string) *Entity {
|
||||
var id string
|
||||
if err := conn.QueryRow("SELECT entity_id FROM edge WHERE parent_id = ? AND name = ?;", parent.id, name).Scan(&id); err != nil {
|
||||
return nil
|
||||
}
|
||||
return db.entities[edge.EntityID]
|
||||
return &Entity{id}
|
||||
}
|
||||
|
||||
func (db *Database) openConn() (*sql.DB, error) {
|
||||
return sql.Open("sqlite3", db.dbPath)
|
||||
}
|
||||
|
||||
// Return the id used to reference this entity
|
||||
@@ -303,29 +454,3 @@ func (e Entities) Paths() []string {
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// Checks if an edge with the specified parent id and name exist in the slice
|
||||
func (e Edges) Exists(parendId, name string) bool {
|
||||
edge, _ := e.Get(parendId, name)
|
||||
return edge != nil
|
||||
}
|
||||
|
||||
// Returns the edge and index in the slice with the specified parent id and name
|
||||
func (e Edges) Get(parentId, name string) (*Edge, int) {
|
||||
for i, edge := range e {
|
||||
if edge.ParentID == parentId && edge.Name == name {
|
||||
return edge, i
|
||||
}
|
||||
}
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
func (e Edges) Search(predicate func(edge *Edge) bool) Edges {
|
||||
out := Edges{}
|
||||
for _, edge := range e {
|
||||
if predicate(edge) {
|
||||
out = append(out, edge)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
+40
-1
@@ -2,27 +2,34 @@ package gograph
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func newTestDb(t *testing.T) *Database {
|
||||
db, err := NewDatabase(os.TempDir(), "0")
|
||||
db, err := NewDatabase(path.Join(os.TempDir(), "sqlite.db"), "0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
func destroyTestDb(db *Database) {
|
||||
os.Remove(db.dbPath)
|
||||
}
|
||||
|
||||
func TestNewDatabase(t *testing.T) {
|
||||
db := newTestDb(t)
|
||||
if db == nil {
|
||||
t.Fatal("Datbase should not be nil")
|
||||
}
|
||||
defer destroyTestDb(db)
|
||||
}
|
||||
|
||||
func TestCreateRootEnity(t *testing.T) {
|
||||
db := newTestDb(t)
|
||||
defer destroyTestDb(db)
|
||||
root := db.RootEntity()
|
||||
if root == nil {
|
||||
t.Fatal("Root entity should not be nil")
|
||||
@@ -31,6 +38,7 @@ func TestCreateRootEnity(t *testing.T) {
|
||||
|
||||
func TestGetRootEntity(t *testing.T) {
|
||||
db := newTestDb(t)
|
||||
defer destroyTestDb(db)
|
||||
|
||||
e := db.Get("/")
|
||||
if e == nil {
|
||||
@@ -43,6 +51,7 @@ func TestGetRootEntity(t *testing.T) {
|
||||
|
||||
func TestSetEntityWithDifferentName(t *testing.T) {
|
||||
db := newTestDb(t)
|
||||
defer destroyTestDb(db)
|
||||
|
||||
db.Set("/test", "1")
|
||||
if _, err := db.Set("/other", "1"); err != nil {
|
||||
@@ -52,6 +61,7 @@ func TestSetEntityWithDifferentName(t *testing.T) {
|
||||
|
||||
func TestCreateChild(t *testing.T) {
|
||||
db := newTestDb(t)
|
||||
defer destroyTestDb(db)
|
||||
|
||||
child, err := db.Set("/db", "1")
|
||||
if err != nil {
|
||||
@@ -67,6 +77,7 @@ func TestCreateChild(t *testing.T) {
|
||||
|
||||
func TestListAllRootChildren(t *testing.T) {
|
||||
db := newTestDb(t)
|
||||
defer destroyTestDb(db)
|
||||
|
||||
for i := 1; i < 6; i++ {
|
||||
a := strconv.Itoa(i)
|
||||
@@ -82,6 +93,7 @@ func TestListAllRootChildren(t *testing.T) {
|
||||
|
||||
func TestListAllSubChildren(t *testing.T) {
|
||||
db := newTestDb(t)
|
||||
defer destroyTestDb(db)
|
||||
|
||||
_, err := db.Set("/webapp", "1")
|
||||
if err != nil {
|
||||
@@ -123,6 +135,7 @@ func TestListAllSubChildren(t *testing.T) {
|
||||
|
||||
func TestAddSelfAsChild(t *testing.T) {
|
||||
db := newTestDb(t)
|
||||
defer destroyTestDb(db)
|
||||
|
||||
child, err := db.Set("/test", "1")
|
||||
if err != nil {
|
||||
@@ -135,6 +148,7 @@ func TestAddSelfAsChild(t *testing.T) {
|
||||
|
||||
func TestAddChildToNonExistantRoot(t *testing.T) {
|
||||
db := newTestDb(t)
|
||||
defer destroyTestDb(db)
|
||||
|
||||
if _, err := db.Set("/myapp", "1"); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -147,6 +161,7 @@ func TestAddChildToNonExistantRoot(t *testing.T) {
|
||||
|
||||
func TestWalkAll(t *testing.T) {
|
||||
db := newTestDb(t)
|
||||
defer destroyTestDb(db)
|
||||
_, err := db.Set("/webapp", "1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -192,6 +207,7 @@ func TestWalkAll(t *testing.T) {
|
||||
|
||||
func TestGetEntityByPath(t *testing.T) {
|
||||
db := newTestDb(t)
|
||||
defer destroyTestDb(db)
|
||||
_, err := db.Set("/webapp", "1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -238,6 +254,7 @@ func TestGetEntityByPath(t *testing.T) {
|
||||
|
||||
func TestEnitiesPaths(t *testing.T) {
|
||||
db := newTestDb(t)
|
||||
defer destroyTestDb(db)
|
||||
_, err := db.Set("/webapp", "1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -281,6 +298,7 @@ func TestEnitiesPaths(t *testing.T) {
|
||||
|
||||
func TestDeleteRootEntity(t *testing.T) {
|
||||
db := newTestDb(t)
|
||||
defer destroyTestDb(db)
|
||||
|
||||
if err := db.Delete("/"); err == nil {
|
||||
t.Fatal("Error should not be nil")
|
||||
@@ -289,6 +307,7 @@ func TestDeleteRootEntity(t *testing.T) {
|
||||
|
||||
func TestDeleteEntity(t *testing.T) {
|
||||
db := newTestDb(t)
|
||||
defer destroyTestDb(db)
|
||||
_, err := db.Set("/webapp", "1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -335,6 +354,7 @@ func TestDeleteEntity(t *testing.T) {
|
||||
|
||||
func TestCountRefs(t *testing.T) {
|
||||
db := newTestDb(t)
|
||||
defer destroyTestDb(db)
|
||||
|
||||
db.Set("/webapp", "1")
|
||||
|
||||
@@ -351,6 +371,7 @@ func TestCountRefs(t *testing.T) {
|
||||
|
||||
func TestPurgeId(t *testing.T) {
|
||||
db := newTestDb(t)
|
||||
defer destroyTestDb(db)
|
||||
|
||||
db.Set("/webapp", "1")
|
||||
|
||||
@@ -372,6 +393,7 @@ func TestPurgeId(t *testing.T) {
|
||||
|
||||
func TestRename(t *testing.T) {
|
||||
db := newTestDb(t)
|
||||
defer destroyTestDb(db)
|
||||
|
||||
db.Set("/webapp", "1")
|
||||
|
||||
@@ -400,6 +422,7 @@ func TestRename(t *testing.T) {
|
||||
|
||||
func TestCreateMultipleNames(t *testing.T) {
|
||||
db := newTestDb(t)
|
||||
defer destroyTestDb(db)
|
||||
|
||||
db.Set("/db", "1")
|
||||
if _, err := db.Set("/myapp", "1"); err != nil {
|
||||
@@ -411,3 +434,19 @@ func TestCreateMultipleNames(t *testing.T) {
|
||||
return nil
|
||||
}, -1)
|
||||
}
|
||||
|
||||
func TestRefPaths(t *testing.T) {
|
||||
db := newTestDb(t)
|
||||
defer destroyTestDb(db)
|
||||
|
||||
db.Set("/webapp", "1")
|
||||
|
||||
db.Set("/db", "2")
|
||||
db.Set("/webapp/db", "2")
|
||||
|
||||
refs := db.RefPaths("2")
|
||||
if len(refs) != 2 {
|
||||
t.Fatalf("Expected reference count to be 2, got %d", len(refs))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-1
@@ -44,7 +44,8 @@ if [ -n "$(git status --porcelain)" ]; then
|
||||
fi
|
||||
|
||||
# Use these flags when compiling the tests and final binary
|
||||
LDFLAGS="-X main.GITCOMMIT $GITCOMMIT -X main.VERSION $VERSION -d -w"
|
||||
LDFLAGS='-X main.GITCOMMIT "'$GITCOMMIT'" -X main.VERSION "'$VERSION'" -w -linkmode external -extldflags "-ldl -pthread -static -Wl,--unresolved-symbols=ignore-in-shared-libs"'
|
||||
BUILDFLAGS='-tags netgo'
|
||||
|
||||
|
||||
bundle() {
|
||||
|
||||
+3
-3
@@ -2,6 +2,6 @@
|
||||
|
||||
DEST=$1
|
||||
|
||||
if go build -o $DEST/docker-$VERSION -ldflags "$LDFLAGS" ./docker; then
|
||||
echo "Created binary: $DEST/docker-$VERSION"
|
||||
fi
|
||||
go build -o $DEST/docker-$VERSION -ldflags "$LDFLAGS" $BUILDFLAGS ./docker
|
||||
|
||||
echo "Created binary: $DEST/docker-$VERSION"
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ bundle_test() {
|
||||
for test_dir in $(find_test_dirs); do (
|
||||
set -x
|
||||
cd $test_dir
|
||||
go test -v -ldflags "$LDFLAGS"
|
||||
go test -v -ldflags "$LDFLAGS" $BUILDFLAGS
|
||||
) done
|
||||
} 2>&1 | tee $DEST/test.log
|
||||
}
|
||||
|
||||
+7
-1
@@ -199,6 +199,7 @@ func (runtime *Runtime) Destroy(container *Container) error {
|
||||
if err := container.Stop(3); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if mounted, err := container.Mounted(); err != nil {
|
||||
return err
|
||||
} else if mounted {
|
||||
@@ -206,6 +207,11 @@ func (runtime *Runtime) Destroy(container *Container) error {
|
||||
return fmt.Errorf("Unable to unmount container %v: %v", container.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := runtime.containerGraph.Purge(container.ID); err != nil {
|
||||
utils.Debugf("Unable to remove container from link graph: %s", err)
|
||||
}
|
||||
|
||||
// Deregister the container before removing its directory, to avoid race conditions
|
||||
runtime.idIndex.Delete(container.ID)
|
||||
runtime.containers.Remove(element)
|
||||
@@ -566,7 +572,7 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
graph, err := gograph.NewDatabase("", "engine")
|
||||
graph, err := gograph.NewDatabase(path.Join(config.GraphPath, "linkgraph.db"), "engine")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -500,8 +500,6 @@ func TestRestore(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestReloadContainerLinks(t *testing.T) {
|
||||
t.SkipNow() // TODO: @crosbymichael
|
||||
|
||||
runtime1 := mkRuntime(t)
|
||||
defer nuke(runtime1)
|
||||
// Create a container with one instance of docker
|
||||
|
||||
@@ -0,0 +1,404 @@
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package sqlite provides access to the SQLite library, version 3.
|
||||
package sqlite
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -lsqlite3
|
||||
|
||||
#include <sqlite3.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// These wrappers are necessary because SQLITE_TRANSIENT
|
||||
// is a pointer constant, and cgo doesn't translate them correctly.
|
||||
// The definition in sqlite3.h is:
|
||||
//
|
||||
// typedef void (*sqlite3_destructor_type)(void*);
|
||||
// #define SQLITE_STATIC ((sqlite3_destructor_type)0)
|
||||
// #define SQLITE_TRANSIENT ((sqlite3_destructor_type)-1)
|
||||
|
||||
static int my_bind_text(sqlite3_stmt *stmt, int n, char *p, int np) {
|
||||
return sqlite3_bind_text(stmt, n, p, np, SQLITE_TRANSIENT);
|
||||
}
|
||||
static int my_bind_blob(sqlite3_stmt *stmt, int n, void *p, int np) {
|
||||
return sqlite3_bind_blob(stmt, n, p, np, SQLITE_TRANSIENT);
|
||||
}
|
||||
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type Errno int
|
||||
|
||||
func (e Errno) Error() string {
|
||||
s := errText[e]
|
||||
if s == "" {
|
||||
return fmt.Sprintf("errno %d", int(e))
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
var (
|
||||
ErrError error = Errno(1) // /* SQL error or missing database */
|
||||
ErrInternal error = Errno(2) // /* Internal logic error in SQLite */
|
||||
ErrPerm error = Errno(3) // /* Access permission denied */
|
||||
ErrAbort error = Errno(4) // /* Callback routine requested an abort */
|
||||
ErrBusy error = Errno(5) // /* The database file is locked */
|
||||
ErrLocked error = Errno(6) // /* A table in the database is locked */
|
||||
ErrNoMem error = Errno(7) // /* A malloc() failed */
|
||||
ErrReadOnly error = Errno(8) // /* Attempt to write a readonly database */
|
||||
ErrInterrupt error = Errno(9) // /* Operation terminated by sqlite3_interrupt()*/
|
||||
ErrIOErr error = Errno(10) // /* Some kind of disk I/O error occurred */
|
||||
ErrCorrupt error = Errno(11) // /* The database disk image is malformed */
|
||||
ErrFull error = Errno(13) // /* Insertion failed because database is full */
|
||||
ErrCantOpen error = Errno(14) // /* Unable to open the database file */
|
||||
ErrEmpty error = Errno(16) // /* Database is empty */
|
||||
ErrSchema error = Errno(17) // /* The database schema changed */
|
||||
ErrTooBig error = Errno(18) // /* String or BLOB exceeds size limit */
|
||||
ErrConstraint error = Errno(19) // /* Abort due to constraint violation */
|
||||
ErrMismatch error = Errno(20) // /* Data type mismatch */
|
||||
ErrMisuse error = Errno(21) // /* Library used incorrectly */
|
||||
ErrNolfs error = Errno(22) // /* Uses OS features not supported on host */
|
||||
ErrAuth error = Errno(23) // /* Authorization denied */
|
||||
ErrFormat error = Errno(24) // /* Auxiliary database format error */
|
||||
ErrRange error = Errno(25) // /* 2nd parameter to sqlite3_bind out of range */
|
||||
ErrNotDB error = Errno(26) // /* File opened that is not a database file */
|
||||
Row = Errno(100) // /* sqlite3_step() has another row ready */
|
||||
Done = Errno(101) // /* sqlite3_step() has finished executing */
|
||||
)
|
||||
|
||||
var errText = map[Errno]string{
|
||||
1: "SQL error or missing database",
|
||||
2: "Internal logic error in SQLite",
|
||||
3: "Access permission denied",
|
||||
4: "Callback routine requested an abort",
|
||||
5: "The database file is locked",
|
||||
6: "A table in the database is locked",
|
||||
7: "A malloc() failed",
|
||||
8: "Attempt to write a readonly database",
|
||||
9: "Operation terminated by sqlite3_interrupt()*/",
|
||||
10: "Some kind of disk I/O error occurred",
|
||||
11: "The database disk image is malformed",
|
||||
12: "NOT USED. Table or record not found",
|
||||
13: "Insertion failed because database is full",
|
||||
14: "Unable to open the database file",
|
||||
15: "NOT USED. Database lock protocol error",
|
||||
16: "Database is empty",
|
||||
17: "The database schema changed",
|
||||
18: "String or BLOB exceeds size limit",
|
||||
19: "Abort due to constraint violation",
|
||||
20: "Data type mismatch",
|
||||
21: "Library used incorrectly",
|
||||
22: "Uses OS features not supported on host",
|
||||
23: "Authorization denied",
|
||||
24: "Auxiliary database format error",
|
||||
25: "2nd parameter to sqlite3_bind out of range",
|
||||
26: "File opened that is not a database file",
|
||||
100: "sqlite3_step() has another row ready",
|
||||
101: "sqlite3_step() has finished executing",
|
||||
}
|
||||
|
||||
func (c *Conn) error(rv C.int) error {
|
||||
if c == nil || c.db == nil {
|
||||
return errors.New("nil sqlite database")
|
||||
}
|
||||
if rv == 0 {
|
||||
return nil
|
||||
}
|
||||
if rv == 21 { // misuse
|
||||
return Errno(rv)
|
||||
}
|
||||
return errors.New(Errno(rv).Error() + ": " + C.GoString(C.sqlite3_errmsg(c.db)))
|
||||
}
|
||||
|
||||
type Conn struct {
|
||||
db *C.sqlite3
|
||||
}
|
||||
|
||||
func Version() string {
|
||||
p := C.sqlite3_libversion()
|
||||
return C.GoString(p)
|
||||
}
|
||||
|
||||
func Open(filename string) (*Conn, error) {
|
||||
if C.sqlite3_threadsafe() == 0 {
|
||||
return nil, errors.New("sqlite library was not compiled for thread-safe operation")
|
||||
}
|
||||
|
||||
var db *C.sqlite3
|
||||
name := C.CString(filename)
|
||||
defer C.free(unsafe.Pointer(name))
|
||||
rv := C.sqlite3_open_v2(name, &db,
|
||||
C.SQLITE_OPEN_FULLMUTEX|
|
||||
C.SQLITE_OPEN_READWRITE|
|
||||
C.SQLITE_OPEN_CREATE,
|
||||
nil)
|
||||
if rv != 0 {
|
||||
return nil, Errno(rv)
|
||||
}
|
||||
if db == nil {
|
||||
return nil, errors.New("sqlite succeeded without returning a database")
|
||||
}
|
||||
return &Conn{db}, nil
|
||||
}
|
||||
|
||||
func NewBackup(dst *Conn, dstTable string, src *Conn, srcTable string) (*Backup, error) {
|
||||
dname := C.CString(dstTable)
|
||||
sname := C.CString(srcTable)
|
||||
defer C.free(unsafe.Pointer(dname))
|
||||
defer C.free(unsafe.Pointer(sname))
|
||||
|
||||
sb := C.sqlite3_backup_init(dst.db, dname, src.db, sname)
|
||||
if sb == nil {
|
||||
return nil, dst.error(C.sqlite3_errcode(dst.db))
|
||||
}
|
||||
return &Backup{sb, dst, src}, nil
|
||||
}
|
||||
|
||||
type Backup struct {
|
||||
sb *C.sqlite3_backup
|
||||
dst, src *Conn
|
||||
}
|
||||
|
||||
func (b *Backup) Step(npage int) error {
|
||||
rv := C.sqlite3_backup_step(b.sb, C.int(npage))
|
||||
if rv == 0 || Errno(rv) == ErrBusy || Errno(rv) == ErrLocked {
|
||||
return nil
|
||||
}
|
||||
return Errno(rv)
|
||||
}
|
||||
|
||||
type BackupStatus struct {
|
||||
Remaining int
|
||||
PageCount int
|
||||
}
|
||||
|
||||
func (b *Backup) Status() BackupStatus {
|
||||
return BackupStatus{int(C.sqlite3_backup_remaining(b.sb)), int(C.sqlite3_backup_pagecount(b.sb))}
|
||||
}
|
||||
|
||||
func (b *Backup) Run(npage int, period time.Duration, c chan<- BackupStatus) error {
|
||||
var err error
|
||||
for {
|
||||
err = b.Step(npage)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if c != nil {
|
||||
c <- b.Status()
|
||||
}
|
||||
time.Sleep(period)
|
||||
}
|
||||
return b.dst.error(C.sqlite3_errcode(b.dst.db))
|
||||
}
|
||||
|
||||
func (b *Backup) Close() error {
|
||||
if b.sb == nil {
|
||||
return errors.New("backup already closed")
|
||||
}
|
||||
C.sqlite3_backup_finish(b.sb)
|
||||
b.sb = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) BusyTimeout(ms int) error {
|
||||
rv := C.sqlite3_busy_timeout(c.db, C.int(ms))
|
||||
if rv == 0 {
|
||||
return nil
|
||||
}
|
||||
return Errno(rv)
|
||||
}
|
||||
|
||||
func (c *Conn) Exec(cmd string, args ...interface{}) error {
|
||||
s, err := c.Prepare(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.Finalize()
|
||||
err = s.Exec(args...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rv := C.sqlite3_step(s.stmt)
|
||||
if Errno(rv) != Done {
|
||||
return c.error(rv)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Stmt struct {
|
||||
c *Conn
|
||||
stmt *C.sqlite3_stmt
|
||||
err error
|
||||
t0 time.Time
|
||||
sql string
|
||||
args string
|
||||
}
|
||||
|
||||
func (c *Conn) Prepare(cmd string) (*Stmt, error) {
|
||||
if c == nil || c.db == nil {
|
||||
return nil, errors.New("nil sqlite database")
|
||||
}
|
||||
cmdstr := C.CString(cmd)
|
||||
defer C.free(unsafe.Pointer(cmdstr))
|
||||
var stmt *C.sqlite3_stmt
|
||||
var tail *C.char
|
||||
rv := C.sqlite3_prepare_v2(c.db, cmdstr, C.int(len(cmd)+1), &stmt, &tail)
|
||||
if rv != 0 {
|
||||
return nil, c.error(rv)
|
||||
}
|
||||
return &Stmt{c: c, stmt: stmt, sql: cmd, t0: time.Now()}, nil
|
||||
}
|
||||
|
||||
func (s *Stmt) Exec(args ...interface{}) error {
|
||||
s.args = fmt.Sprintf(" %v", []interface{}(args))
|
||||
rv := C.sqlite3_reset(s.stmt)
|
||||
if rv != 0 {
|
||||
return s.c.error(rv)
|
||||
}
|
||||
|
||||
n := int(C.sqlite3_bind_parameter_count(s.stmt))
|
||||
if n != len(args) {
|
||||
return errors.New(fmt.Sprintf("incorrect argument count for Stmt.Exec: have %d want %d", len(args), n))
|
||||
}
|
||||
|
||||
for i, v := range args {
|
||||
var str string
|
||||
switch v := v.(type) {
|
||||
case []byte:
|
||||
var p *byte
|
||||
if len(v) > 0 {
|
||||
p = &v[0]
|
||||
}
|
||||
if rv := C.my_bind_blob(s.stmt, C.int(i+1), unsafe.Pointer(p), C.int(len(v))); rv != 0 {
|
||||
return s.c.error(rv)
|
||||
}
|
||||
continue
|
||||
|
||||
case bool:
|
||||
if v {
|
||||
str = "1"
|
||||
} else {
|
||||
str = "0"
|
||||
}
|
||||
|
||||
default:
|
||||
str = fmt.Sprint(v)
|
||||
}
|
||||
|
||||
cstr := C.CString(str)
|
||||
rv := C.my_bind_text(s.stmt, C.int(i+1), cstr, C.int(len(str)))
|
||||
C.free(unsafe.Pointer(cstr))
|
||||
if rv != 0 {
|
||||
return s.c.error(rv)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Stmt) Error() error {
|
||||
return s.err
|
||||
}
|
||||
|
||||
func (s *Stmt) Next() bool {
|
||||
rv := C.sqlite3_step(s.stmt)
|
||||
err := Errno(rv)
|
||||
if err == Row {
|
||||
return true
|
||||
}
|
||||
if err != Done {
|
||||
s.err = s.c.error(rv)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Stmt) Reset() error {
|
||||
C.sqlite3_reset(s.stmt)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Stmt) Scan(args ...interface{}) error {
|
||||
n := int(C.sqlite3_column_count(s.stmt))
|
||||
if n != len(args) {
|
||||
return errors.New(fmt.Sprintf("incorrect argument count for Stmt.Scan: have %d want %d", len(args), n))
|
||||
}
|
||||
|
||||
for i, v := range args {
|
||||
n := C.sqlite3_column_bytes(s.stmt, C.int(i))
|
||||
p := C.sqlite3_column_blob(s.stmt, C.int(i))
|
||||
if p == nil && n > 0 {
|
||||
return errors.New("got nil blob")
|
||||
}
|
||||
var data []byte
|
||||
if n > 0 {
|
||||
data = (*[1 << 30]byte)(unsafe.Pointer(p))[0:n]
|
||||
}
|
||||
switch v := v.(type) {
|
||||
case *[]byte:
|
||||
*v = data
|
||||
case *string:
|
||||
*v = string(data)
|
||||
case *bool:
|
||||
*v = string(data) == "1"
|
||||
case *int:
|
||||
x, err := strconv.Atoi(string(data))
|
||||
if err != nil {
|
||||
return errors.New("arg " + strconv.Itoa(i) + " as int: " + err.Error())
|
||||
}
|
||||
*v = x
|
||||
case *int64:
|
||||
x, err := strconv.ParseInt(string(data), 10, 64)
|
||||
if err != nil {
|
||||
return errors.New("arg " + strconv.Itoa(i) + " as int64: " + err.Error())
|
||||
}
|
||||
*v = x
|
||||
case *float64:
|
||||
x, err := strconv.ParseFloat(string(data), 64)
|
||||
if err != nil {
|
||||
return errors.New("arg " + strconv.Itoa(i) + " as float64: " + err.Error())
|
||||
}
|
||||
*v = x
|
||||
default:
|
||||
return errors.New("unsupported type in Scan: " + reflect.TypeOf(v).String())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Stmt) SQL() string {
|
||||
return s.sql + s.args
|
||||
}
|
||||
|
||||
func (s *Stmt) Nanoseconds() int64 {
|
||||
return time.Now().Sub(s.t0).Nanoseconds()
|
||||
}
|
||||
|
||||
func (s *Stmt) Finalize() error {
|
||||
rv := C.sqlite3_finalize(s.stmt)
|
||||
if rv != 0 {
|
||||
return s.c.error(rv)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) Close() error {
|
||||
if c == nil || c.db == nil {
|
||||
return errors.New("nil sqlite database")
|
||||
}
|
||||
rv := C.sqlite3_close(c.db)
|
||||
if rv != 0 {
|
||||
return c.error(rv)
|
||||
}
|
||||
c.db = nil
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,498 @@
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package sqlite3 provides access to the SQLite library, version 3.
|
||||
//
|
||||
// The package has no exported API.
|
||||
// It registers a driver for the standard Go database/sql package.
|
||||
//
|
||||
// import _ "code.google.com/p/gosqlite/sqlite3"
|
||||
//
|
||||
// (For an alternate, earlier API, see the code.google.com/p/gosqlite/sqlite package.)
|
||||
package sqlite
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -lsqlite3
|
||||
|
||||
#include <sqlite3.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// These wrappers are necessary because SQLITE_TRANSIENT
|
||||
// is a pointer constant, and cgo doesn't translate them correctly.
|
||||
// The definition in sqlite3.h is:
|
||||
//
|
||||
// typedef void (*sqlite3_destructor_type)(void*);
|
||||
// #define SQLITE_STATIC ((sqlite3_destructor_type)0)
|
||||
// #define SQLITE_TRANSIENT ((sqlite3_destructor_type)-1)
|
||||
|
||||
static int my_bind_text(sqlite3_stmt *stmt, int n, char *p, int np) {
|
||||
return sqlite3_bind_text(stmt, n, p, np, SQLITE_TRANSIENT);
|
||||
}
|
||||
static int my_bind_blob(sqlite3_stmt *stmt, int n, void *p, int np) {
|
||||
return sqlite3_bind_blob(stmt, n, p, np, SQLITE_TRANSIENT);
|
||||
}
|
||||
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func init() {
|
||||
sql.Register("sqlite3", impl{})
|
||||
}
|
||||
|
||||
type errno int
|
||||
|
||||
func (e errno) Error() string {
|
||||
s := errText[e]
|
||||
if s == "" {
|
||||
return fmt.Sprintf("errno %d", int(e))
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
var (
|
||||
errError error = errno(1) // /* SQL error or missing database */
|
||||
errInternal error = errno(2) // /* Internal logic error in SQLite */
|
||||
errPerm error = errno(3) // /* Access permission denied */
|
||||
errAbort error = errno(4) // /* Callback routine requested an abort */
|
||||
errBusy error = errno(5) // /* The database file is locked */
|
||||
errLocked error = errno(6) // /* A table in the database is locked */
|
||||
errNoMem error = errno(7) // /* A malloc() failed */
|
||||
errReadOnly error = errno(8) // /* Attempt to write a readonly database */
|
||||
errInterrupt error = errno(9) // /* Operation terminated by sqlite3_interrupt()*/
|
||||
errIOErr error = errno(10) // /* Some kind of disk I/O error occurred */
|
||||
errCorrupt error = errno(11) // /* The database disk image is malformed */
|
||||
errFull error = errno(13) // /* Insertion failed because database is full */
|
||||
errCantOpen error = errno(14) // /* Unable to open the database file */
|
||||
errEmpty error = errno(16) // /* Database is empty */
|
||||
errSchema error = errno(17) // /* The database schema changed */
|
||||
errTooBig error = errno(18) // /* String or BLOB exceeds size limit */
|
||||
errConstraint error = errno(19) // /* Abort due to constraint violation */
|
||||
errMismatch error = errno(20) // /* Data type mismatch */
|
||||
errMisuse error = errno(21) // /* Library used incorrectly */
|
||||
errNolfs error = errno(22) // /* Uses OS features not supported on host */
|
||||
errAuth error = errno(23) // /* Authorization denied */
|
||||
errFormat error = errno(24) // /* Auxiliary database format error */
|
||||
errRange error = errno(25) // /* 2nd parameter to sqlite3_bind out of range */
|
||||
errNotDB error = errno(26) // /* File opened that is not a database file */
|
||||
stepRow = errno(100) // /* sqlite3_step() has another row ready */
|
||||
stepDone = errno(101) // /* sqlite3_step() has finished executing */
|
||||
)
|
||||
|
||||
var errText = map[errno]string{
|
||||
1: "SQL error or missing database",
|
||||
2: "Internal logic error in SQLite",
|
||||
3: "Access permission denied",
|
||||
4: "Callback routine requested an abort",
|
||||
5: "The database file is locked",
|
||||
6: "A table in the database is locked",
|
||||
7: "A malloc() failed",
|
||||
8: "Attempt to write a readonly database",
|
||||
9: "Operation terminated by sqlite3_interrupt()*/",
|
||||
10: "Some kind of disk I/O error occurred",
|
||||
11: "The database disk image is malformed",
|
||||
12: "NOT USED. Table or record not found",
|
||||
13: "Insertion failed because database is full",
|
||||
14: "Unable to open the database file",
|
||||
15: "NOT USED. Database lock protocol error",
|
||||
16: "Database is empty",
|
||||
17: "The database schema changed",
|
||||
18: "String or BLOB exceeds size limit",
|
||||
19: "Abort due to constraint violation",
|
||||
20: "Data type mismatch",
|
||||
21: "Library used incorrectly",
|
||||
22: "Uses OS features not supported on host",
|
||||
23: "Authorization denied",
|
||||
24: "Auxiliary database format error",
|
||||
25: "2nd parameter to sqlite3_bind out of range",
|
||||
26: "File opened that is not a database file",
|
||||
100: "sqlite3_step() has another row ready",
|
||||
101: "sqlite3_step() has finished executing",
|
||||
}
|
||||
|
||||
type impl struct{}
|
||||
|
||||
func (impl) Open(name string) (driver.Conn, error) {
|
||||
if C.sqlite3_threadsafe() == 0 {
|
||||
return nil, errors.New("sqlite library was not compiled for thread-safe operation")
|
||||
}
|
||||
|
||||
var db *C.sqlite3
|
||||
cname := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cname))
|
||||
rv := C.sqlite3_open_v2(cname, &db,
|
||||
C.SQLITE_OPEN_FULLMUTEX|
|
||||
C.SQLITE_OPEN_READWRITE|
|
||||
C.SQLITE_OPEN_CREATE,
|
||||
nil)
|
||||
if rv != 0 {
|
||||
return nil, errno(rv)
|
||||
}
|
||||
if db == nil {
|
||||
return nil, errors.New("sqlite succeeded without returning a database")
|
||||
}
|
||||
return &conn{db: db}, nil
|
||||
}
|
||||
|
||||
type conn struct {
|
||||
db *C.sqlite3
|
||||
closed bool
|
||||
tx bool
|
||||
}
|
||||
|
||||
func (c *conn) error(rv C.int) error {
|
||||
if rv == 0 {
|
||||
return nil
|
||||
}
|
||||
if rv == 21 || c.closed {
|
||||
return errno(rv)
|
||||
}
|
||||
return errors.New(errno(rv).Error() + ": " + C.GoString(C.sqlite3_errmsg(c.db)))
|
||||
}
|
||||
|
||||
func (c *conn) Prepare(cmd string) (driver.Stmt, error) {
|
||||
if c.closed {
|
||||
panic("database/sql/driver: misuse of sqlite driver: Prepare after Close")
|
||||
}
|
||||
cmdstr := C.CString(cmd)
|
||||
defer C.free(unsafe.Pointer(cmdstr))
|
||||
var s *C.sqlite3_stmt
|
||||
var tail *C.char
|
||||
rv := C.sqlite3_prepare_v2(c.db, cmdstr, C.int(len(cmd)+1), &s, &tail)
|
||||
if rv != 0 {
|
||||
return nil, c.error(rv)
|
||||
}
|
||||
return &stmt{c: c, stmt: s, sql: cmd, t0: time.Now()}, nil
|
||||
}
|
||||
|
||||
func (c *conn) Close() error {
|
||||
if c.closed {
|
||||
panic("database/sql/driver: misuse of sqlite driver: multiple Close")
|
||||
}
|
||||
c.closed = true
|
||||
rv := C.sqlite3_close(c.db)
|
||||
c.db = nil
|
||||
return c.error(rv)
|
||||
}
|
||||
|
||||
func (c *conn) exec(cmd string) error {
|
||||
cstring := C.CString(cmd)
|
||||
defer C.free(unsafe.Pointer(cstring))
|
||||
rv := C.sqlite3_exec(c.db, cstring, nil, nil, nil)
|
||||
return c.error(rv)
|
||||
}
|
||||
|
||||
func (c *conn) Begin() (driver.Tx, error) {
|
||||
if c.tx {
|
||||
panic("database/sql/driver: misuse of sqlite driver: multiple Tx")
|
||||
}
|
||||
if err := c.exec("BEGIN TRANSACTION"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.tx = true
|
||||
return &tx{c}, nil
|
||||
}
|
||||
|
||||
type tx struct {
|
||||
c *conn
|
||||
}
|
||||
|
||||
func (t *tx) Commit() error {
|
||||
if t.c == nil || !t.c.tx {
|
||||
panic("database/sql/driver: misuse of sqlite driver: extra Commit")
|
||||
}
|
||||
t.c.tx = false
|
||||
err := t.c.exec("COMMIT TRANSACTION")
|
||||
t.c = nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (t *tx) Rollback() error {
|
||||
if t.c == nil || !t.c.tx {
|
||||
panic("database/sql/driver: misuse of sqlite driver: extra Rollback")
|
||||
}
|
||||
t.c.tx = false
|
||||
err := t.c.exec("ROLLBACK")
|
||||
t.c = nil
|
||||
return err
|
||||
}
|
||||
|
||||
type stmt struct {
|
||||
c *conn
|
||||
stmt *C.sqlite3_stmt
|
||||
err error
|
||||
t0 time.Time
|
||||
sql string
|
||||
args string
|
||||
closed bool
|
||||
rows bool
|
||||
colnames []string
|
||||
coltypes []string
|
||||
}
|
||||
|
||||
func (s *stmt) Close() error {
|
||||
if s.rows {
|
||||
panic("database/sql/driver: misuse of sqlite driver: Close with active Rows")
|
||||
}
|
||||
if s.closed {
|
||||
panic("database/sql/driver: misuse of sqlite driver: double Close of Stmt")
|
||||
}
|
||||
s.closed = true
|
||||
rv := C.sqlite3_finalize(s.stmt)
|
||||
if rv != 0 {
|
||||
return s.c.error(rv)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stmt) NumInput() int {
|
||||
if s.closed {
|
||||
panic("database/sql/driver: misuse of sqlite driver: NumInput after Close")
|
||||
}
|
||||
return int(C.sqlite3_bind_parameter_count(s.stmt))
|
||||
}
|
||||
|
||||
func (s *stmt) reset() error {
|
||||
return s.c.error(C.sqlite3_reset(s.stmt))
|
||||
}
|
||||
|
||||
func (s *stmt) start(args []driver.Value) error {
|
||||
if err := s.reset(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
n := int(C.sqlite3_bind_parameter_count(s.stmt))
|
||||
if n != len(args) {
|
||||
return fmt.Errorf("incorrect argument count for command: have %d want %d", len(args), n)
|
||||
}
|
||||
|
||||
for i, v := range args {
|
||||
var str string
|
||||
switch v := v.(type) {
|
||||
case nil:
|
||||
if rv := C.sqlite3_bind_null(s.stmt, C.int(i+1)); rv != 0 {
|
||||
return s.c.error(rv)
|
||||
}
|
||||
continue
|
||||
|
||||
case float64:
|
||||
if rv := C.sqlite3_bind_double(s.stmt, C.int(i+1), C.double(v)); rv != 0 {
|
||||
return s.c.error(rv)
|
||||
}
|
||||
continue
|
||||
|
||||
case int64:
|
||||
if rv := C.sqlite3_bind_int64(s.stmt, C.int(i+1), C.sqlite3_int64(v)); rv != 0 {
|
||||
return s.c.error(rv)
|
||||
}
|
||||
continue
|
||||
|
||||
case []byte:
|
||||
var p *byte
|
||||
if len(v) > 0 {
|
||||
p = &v[0]
|
||||
}
|
||||
if rv := C.my_bind_blob(s.stmt, C.int(i+1), unsafe.Pointer(p), C.int(len(v))); rv != 0 {
|
||||
return s.c.error(rv)
|
||||
}
|
||||
continue
|
||||
|
||||
case bool:
|
||||
var vi int64
|
||||
if v {
|
||||
vi = 1
|
||||
}
|
||||
if rv := C.sqlite3_bind_int64(s.stmt, C.int(i+1), C.sqlite3_int64(vi)); rv != 0 {
|
||||
return s.c.error(rv)
|
||||
}
|
||||
continue
|
||||
|
||||
case time.Time:
|
||||
str = v.UTC().Format(timefmt[0])
|
||||
|
||||
case string:
|
||||
str = v
|
||||
|
||||
default:
|
||||
str = fmt.Sprint(v)
|
||||
}
|
||||
|
||||
cstr := C.CString(str)
|
||||
rv := C.my_bind_text(s.stmt, C.int(i+1), cstr, C.int(len(str)))
|
||||
C.free(unsafe.Pointer(cstr))
|
||||
if rv != 0 {
|
||||
return s.c.error(rv)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stmt) Exec(args []driver.Value) (driver.Result, error) {
|
||||
if s.closed {
|
||||
panic("database/sql/driver: misuse of sqlite driver: Exec after Close")
|
||||
}
|
||||
if s.rows {
|
||||
panic("database/sql/driver: misuse of sqlite driver: Exec with active Rows")
|
||||
}
|
||||
|
||||
err := s.start(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rv := C.sqlite3_step(s.stmt)
|
||||
if errno(rv) != stepDone {
|
||||
if rv == 0 {
|
||||
rv = 21 // errMisuse
|
||||
}
|
||||
return nil, s.c.error(rv)
|
||||
}
|
||||
|
||||
id := int64(C.sqlite3_last_insert_rowid(s.c.db))
|
||||
rows := int64(C.sqlite3_changes(s.c.db))
|
||||
return &result{id, rows}, nil
|
||||
}
|
||||
|
||||
func (s *stmt) Query(args []driver.Value) (driver.Rows, error) {
|
||||
if s.closed {
|
||||
panic("database/sql/driver: misuse of sqlite driver: Query after Close")
|
||||
}
|
||||
if s.rows {
|
||||
panic("database/sql/driver: misuse of sqlite driver: Query with active Rows")
|
||||
}
|
||||
|
||||
err := s.start(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.rows = true
|
||||
if s.colnames == nil {
|
||||
n := int64(C.sqlite3_column_count(s.stmt))
|
||||
s.colnames = make([]string, n)
|
||||
s.coltypes = make([]string, n)
|
||||
for i := range s.colnames {
|
||||
s.colnames[i] = C.GoString(C.sqlite3_column_name(s.stmt, C.int(i)))
|
||||
s.coltypes[i] = strings.ToLower(C.GoString(C.sqlite3_column_decltype(s.stmt, C.int(i))))
|
||||
}
|
||||
}
|
||||
return &rows{s}, nil
|
||||
}
|
||||
|
||||
type rows struct {
|
||||
s *stmt
|
||||
}
|
||||
|
||||
func (r *rows) Columns() []string {
|
||||
if r.s == nil {
|
||||
panic("database/sql/driver: misuse of sqlite driver: Columns of closed Rows")
|
||||
}
|
||||
return r.s.colnames
|
||||
}
|
||||
|
||||
const maxslice = 1<<31 - 1
|
||||
|
||||
var timefmt = []string{
|
||||
"2006-01-02 15:04:05.999999999",
|
||||
"2006-01-02T15:04:05.999999999",
|
||||
"2006-01-02 15:04:05",
|
||||
"2006-01-02T15:04:05",
|
||||
"2006-01-02 15:04",
|
||||
"2006-01-02T15:04",
|
||||
"2006-01-02",
|
||||
}
|
||||
|
||||
func (r *rows) Next(dst []driver.Value) error {
|
||||
if r.s == nil {
|
||||
panic("database/sql/driver: misuse of sqlite driver: Next of closed Rows")
|
||||
}
|
||||
|
||||
rv := C.sqlite3_step(r.s.stmt)
|
||||
if errno(rv) != stepRow {
|
||||
if errno(rv) == stepDone {
|
||||
return io.EOF
|
||||
}
|
||||
if rv == 0 {
|
||||
rv = 21
|
||||
}
|
||||
return r.s.c.error(rv)
|
||||
}
|
||||
|
||||
for i := range dst {
|
||||
switch typ := C.sqlite3_column_type(r.s.stmt, C.int(i)); typ {
|
||||
default:
|
||||
return fmt.Errorf("unexpected sqlite3 column type %d", typ)
|
||||
case C.SQLITE_INTEGER:
|
||||
val := int64(C.sqlite3_column_int64(r.s.stmt, C.int(i)))
|
||||
switch r.s.coltypes[i] {
|
||||
case "timestamp", "datetime":
|
||||
dst[i] = time.Unix(val, 0).UTC()
|
||||
case "boolean":
|
||||
dst[i] = val > 0
|
||||
default:
|
||||
dst[i] = val
|
||||
}
|
||||
|
||||
case C.SQLITE_FLOAT:
|
||||
dst[i] = float64(C.sqlite3_column_double(r.s.stmt, C.int(i)))
|
||||
|
||||
case C.SQLITE_BLOB, C.SQLITE_TEXT:
|
||||
n := int(C.sqlite3_column_bytes(r.s.stmt, C.int(i)))
|
||||
var b []byte
|
||||
if n > 0 {
|
||||
p := C.sqlite3_column_blob(r.s.stmt, C.int(i))
|
||||
b = (*[maxslice]byte)(unsafe.Pointer(p))[:n]
|
||||
}
|
||||
dst[i] = b
|
||||
switch r.s.coltypes[i] {
|
||||
case "timestamp", "datetime":
|
||||
dst[i] = time.Time{}
|
||||
s := string(b)
|
||||
for _, f := range timefmt {
|
||||
if t, err := time.Parse(f, s); err == nil {
|
||||
dst[i] = t
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case C.SQLITE_NULL:
|
||||
dst[i] = nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *rows) Close() error {
|
||||
if r.s == nil {
|
||||
panic("database/sql/driver: misuse of sqlite driver: Close of closed Rows")
|
||||
}
|
||||
r.s.rows = false
|
||||
r.s = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
type result struct {
|
||||
id int64
|
||||
rows int64
|
||||
}
|
||||
|
||||
func (r *result) LastInsertId() (int64, error) {
|
||||
return r.id, nil
|
||||
}
|
||||
|
||||
func (r *result) RowsAffected() (int64, error) {
|
||||
return r.rows, nil
|
||||
}
|
||||
Reference in New Issue
Block a user