diff --git a/Dockerfile b/Dockerfile index abb8a0282..22580d3c3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 diff --git a/commands.go b/commands.go index 0d932c06f..e0bfcea43 100644 --- a/commands.go +++ b/commands.go @@ -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) diff --git a/gograph/gograph.go b/gograph/gograph.go index 8ed5a4434..8dacb961f 100644 --- a/gograph/gograph.go +++ b/gograph/gograph.go @@ -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 -} diff --git a/gograph/gograph_test.go b/gograph/gograph_test.go index 3c6d3b685..3b7515897 100644 --- a/gograph/gograph_test.go +++ b/gograph/gograph_test.go @@ -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)) + } + +} diff --git a/hack/make.sh b/hack/make.sh index 98b62ea6a..5195098ba 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -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() { diff --git a/hack/make/binary b/hack/make/binary index cff9f5c73..4c2c24897 100644 --- a/hack/make/binary +++ b/hack/make/binary @@ -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" diff --git a/hack/make/test b/hack/make/test index 9334c8b31..9a3ce7cd0 100644 --- a/hack/make/test +++ b/hack/make/test @@ -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 } diff --git a/runtime.go b/runtime.go index f169522c2..0452cf418 100644 --- a/runtime.go +++ b/runtime.go @@ -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 } diff --git a/runtime_test.go b/runtime_test.go index ecdc96d01..87fd4a9fe 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -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 diff --git a/vendor/src/code.google.com/p/gosqlite/sqlite/sqlite.go b/vendor/src/code.google.com/p/gosqlite/sqlite/sqlite.go new file mode 100644 index 000000000..d2fbb6202 --- /dev/null +++ b/vendor/src/code.google.com/p/gosqlite/sqlite/sqlite.go @@ -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 +#include + +// 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 +} diff --git a/vendor/src/code.google.com/p/gosqlite/sqlite3/driver.go b/vendor/src/code.google.com/p/gosqlite/sqlite3/driver.go new file mode 100644 index 000000000..982e08ec0 --- /dev/null +++ b/vendor/src/code.google.com/p/gosqlite/sqlite3/driver.go @@ -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 +#include + +// 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 +}