Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Major breaking changes in this update.

Initial support for the SQLite Backup API along with tests.
Ruby-style closure parameters for the row stepping in query results.
Disambiguation of QueryParameters and ResultColumns which were previously encapsualted as Column.
Introduction of an abstract Table type which exists independent of any given database.
Code reorganisation.

This commit is very likely a transitional step towards something else...
  • Loading branch information...
commit 5b9e113b052f403d168716daec2313deccca5dd7 1 parent d5c0cf5
@feyeleanor authored
View
7 Makefile
@@ -5,8 +5,11 @@ TARG=sqlite3
CGOFILES=\
sqlite3.go\
database.go\
- column.go\
- statement.go
+ statement.go\
+ query_parameter.go\
+ result_column.go\
+ table.go\
+ backup.go
ifeq ($(GOOS),darwin)
CGO_LDFLAGS=/usr/lib/libsqlite3.0.dylib
View
52 backup.go
@@ -0,0 +1,52 @@
+package sqlite3
+
+// #include <sqlite3.h>
+import "C"
+import "os"
+
+type Backup struct {
+ cptr *C.sqlite3_backup
+ db *Database
+}
+
+func NewBackup(d *Database, ddb string, s *Database, sdb string) (b *Backup, e os.Error) {
+ if cptr := C.sqlite3_backup_init(d.handle, C.CString(ddb), s.handle, C.CString(sdb)); cptr != nil {
+ b = &Backup{ cptr: cptr, db: d }
+ } else {
+ if e = d.Error(); e == OK {
+ e = nil
+ }
+ }
+ return
+}
+
+func (b *Backup) Step(pages int) os.Error {
+ if e := Errno(C.sqlite3_backup_step(b.cptr, C.int(pages))); e != OK {
+ return e
+ }
+ return nil
+}
+
+func (b *Backup) Remaining() int {
+ return int(C.sqlite3_backup_remaining(b.cptr))
+}
+
+func (b *Backup) PageCount() int {
+ return int(C.sqlite3_backup_pagecount(b.cptr))
+}
+
+func (b *Backup) Finish() os.Error {
+ if e := Errno(C.sqlite3_backup_finish(b.cptr)); e != OK {
+ return e
+ }
+ return nil
+}
+
+func (b *Backup) Full() os.Error {
+ b.Step(-1)
+ b.Finish()
+ if e := b.db.Error(); e != OK {
+ return e
+ }
+ return nil
+}
View
88 column.go
@@ -1,88 +0,0 @@
-package sqlite3
-
-// #include <sqlite3.h>
-// int gosqlite3_bind_text(sqlite3_stmt* s, int p, const char* q, int n) {
-// return sqlite3_bind_text(s, p, q, n, SQLITE_TRANSIENT);
-// }
-// int gosqlite3_bind_blob(sqlite3_stmt* s, int p, const void* q, int n) {
-// return sqlite3_bind_blob(s, p, q, n, SQLITE_TRANSIENT);
-// }
-import "C"
-
-import "bytes"
-import "gob"
-import "unsafe"
-
-
-type Column int
-func (c Column) bind_blob(s *Statement, v []byte) Errno {
- return Errno(C.gosqlite3_bind_blob(s.cptr, C.int(c), unsafe.Pointer(C.CString(string(v))), C.int(len(v))))
-}
-
-func (c Column) make_buffer(s *Statement, addr interface{}) (buffer string) {
- switch addr := addr.(type) {
- case *C.uchar:
- buffer = C.GoStringN((*C.char)(unsafe.Pointer(addr)), C.int(c.ByteCount(s)))
- case unsafe.Pointer:
- buffer = C.GoStringN((*C.char)(addr), C.int(c.ByteCount(s)))
- }
- return
-}
-
-func (c Column) Name(s *Statement) string {
- return C.GoString(C.sqlite3_column_name(s.cptr, C.int(c)))
-}
-
-func (c Column) Type(s *Statement) int {
- return int(C.sqlite3_column_type(s.cptr, C.int(c)))
-}
-
-func (c Column) ByteCount(s *Statement) int {
- return int(C.sqlite3_column_bytes(s.cptr, C.int(c)))
-}
-
-func (c Column) Value(s *Statement) (value interface{}) {
- switch c.Type(s) {
- case SQLITE_INTEGER:
- value = int64(C.sqlite3_int64(C.sqlite3_column_int64(s.cptr, C.int(c))))
- case SQLITE_FLOAT:
- value = float64(C.sqlite3_column_double(s.cptr, C.int(c)))
- case SQLITE3_TEXT:
- value = c.make_buffer(s, C.sqlite3_column_text(s.cptr, C.int(c)))
- case SQLITE_BLOB:
- buffer := c.make_buffer(s, C.sqlite3_column_blob(s.cptr, C.int(c)))
- value = gob.NewDecoder(bytes.NewBuffer([]byte(buffer)))
- case SQLITE_NULL:
- value = nil
- default:
- panic("unknown column type")
- }
- return
-}
-
-func (c Column) Bind(s *Statement, value interface{}) (e Errno) {
- switch v := value.(type) {
- case nil:
- e = Errno(C.sqlite3_bind_null(s.cptr, C.int(c)))
- case int:
- e = Errno(C.sqlite3_bind_int(s.cptr, C.int(c), C.int(v)))
- case string:
- e = Errno(C.gosqlite3_bind_text(s.cptr, C.int(c), C.CString(v), C.int(len(v))))
- case int64:
- e = Errno(C.sqlite3_bind_int64(s.cptr, C.int(c), C.sqlite3_int64(v)))
- case float32:
- e = Errno(C.sqlite3_bind_double(s.cptr, C.int(c), C.double(v)))
- case float64:
- e = Errno(C.sqlite3_bind_double(s.cptr, C.int(c), C.double(v)))
- default:
- buffer := new(bytes.Buffer)
- encoder := gob.NewEncoder(buffer)
- if err := encoder.Encode(value); err != nil {
- e = ENCODER
- } else {
- rawbuffer := string(buffer.Bytes())
- e = Errno(C.gosqlite3_bind_blob(s.cptr, C.int(c), unsafe.Pointer(C.CString(rawbuffer)), C.int(len(rawbuffer))))
- }
- }
- return
-}
View
93 database.go
@@ -6,14 +6,6 @@ import "fmt"
import "os"
import "time"
-const(
- SQLITE_INTEGER = 1
- SQLITE_FLOAT = 2
- SQLITE3_TEXT = 3
- SQLITE_BLOB = 4
- SQLITE_NULL = 5
-)
-
type Errno int
func (e Errno) String() (err string) {
@@ -124,7 +116,9 @@ func (db *Database) Open(flags... int) (e os.Error) {
} else {
db.Flags = 0
for _, v := range flags { db.Flags = db.Flags | C.int(v) }
- if e = Errno(C.sqlite3_open_v2(C.CString(db.Filename), &db.handle, db.Flags, nil)); e == OK && db.handle == nil {
+ if err := Errno(C.sqlite3_open_v2(C.CString(db.Filename), &db.handle, db.Flags, nil)); err != OK {
+ e = err
+ } else if db.handle == nil {
e = CANTOPEN
}
}
@@ -148,14 +142,83 @@ func (db *Database) TotalChanges() int {
return int(C.sqlite3_total_changes(db.handle))
}
-func (db *Database) Error() (e string) {
- return C.GoString(C.sqlite3_errmsg(db.handle))
+func (db *Database) Error() os.Error {
+ return Errno(C.sqlite3_errcode(db.handle))
+}
+
+func (db *Database) Prepare(sql string, f... func(*Statement)) (s *Statement, e os.Error) {
+ s = &Statement{ db: db, timestamp: time.Nanoseconds() }
+ if rv := Errno(C.sqlite3_prepare_v2(db.handle, C.CString(sql), -1, &s.cptr, nil)); rv != OK {
+ s, e = nil, rv
+ } else {
+ defer func() {
+ switch r := recover().(type) {
+ case nil: e = nil
+ case os.Error: s, e = nil, r
+ default: s, e = nil, MISUSE
+ }
+ }()
+ for _, fn := range f { fn(s) }
+ }
+ return
+}
+
+func (db *Database) Execute(sql string, f func(*Statement, ...interface{})) (c int, e os.Error) {
+ var st *Statement
+ st, e = db.Prepare(sql)
+ if e == nil {
+ c, e = st.All(f)
+ }
+ return
+}
+
+func (db *Database) Load(source *Database, dbname string) (e os.Error) {
+ if dbname == "" {
+ dbname = "main"
+ }
+ if backup, rv := NewBackup(db, dbname, source, dbname); rv == nil {
+ e = backup.Full()
+ } else {
+ e = rv
+ }
+ return
}
-func (db *Database) Prepare(sql string) (s *Statement, e os.Error) {
- s = &Statement{ db: db, SQL: sql, timestamp: time.Nanoseconds() }
- if e = Errno(C.sqlite3_prepare_v2(db.handle, C.CString(sql), -1, &s.cptr, nil)); e != OK {
- s = nil
+func (db *Database) Save(target *Database, dbname string) (e os.Error) {
+ return target.Load(db, dbname)
+}
+
+type ProgressReport struct {
+ os.Error
+ PageCount int
+ Remaining int
+}
+
+type Reporter chan *ProgressReport
+
+func (db *Database) Backup(filename string, increment int, reporter Reporter) (e os.Error) {
+ if target, e := Open(filename); e == nil {
+ if backup, e := NewBackup(target, "main", db, "main"); e == nil {
+ // If the return value of backup_step() indicates that there are still further pages to copy, sleep for 250 ms before repeating.
+ go func() {
+ defer target.Close()
+ defer backup.Finish()
+ for {
+ report := &ProgressReport{
+ Error: backup.Step(increment),
+ PageCount: backup.PageCount(),
+ Remaining: backup.Remaining(),
+ }
+ reporter <- report
+ if e, ok := report.Error.(Errno); ok && !(e == OK || e == BUSY || e == LOCKED) {
+ break
+ }
+ }
+ }()
+ } else {
+ target.Close()
+ e = target.Error()
+ }
}
return
}
View
79 helpers_test.go
@@ -0,0 +1,79 @@
+package sqlite3
+
+import "fmt"
+import "gob"
+import "os"
+import "testing"
+
+
+var FOO *Table
+var BAR *Table
+
+func init() {
+ FOO = &Table{ "foo", "number INTEGER, text VARCHAR(20)" }
+ BAR = &Table{ "bar", "number INTEGER, value BLOB" }
+}
+
+type TwoItems struct {
+ Number string
+ Text string
+}
+
+func (t *TwoItems) String() string {
+ return "[" + t.Number + " : " + t.Text + "]"
+}
+
+func fatalOnError(t *testing.T, e os.Error, message string, parameters... interface{}) {
+ if e != nil {
+ t.Fatalf("%v : %v", e, fmt.Sprintf(message, parameters...))
+ }
+}
+
+func (db *Database) stepThroughRows(t *testing.T, table *Table) (c int) {
+ var e os.Error
+ sql := fmt.Sprintf("SELECT * from %v;", table.Name)
+ c, e = db.Execute(sql, func(st *Statement, values ...interface{}) {
+ data := values[1]
+ switch data := data.(type) {
+ case *gob.Decoder:
+ blob := &TwoItems{}
+ data.Decode(blob)
+ t.Logf("BLOB => %v: %v, %v: %v\n", ResultColumn(0).Name(st), ResultColumn(0).Value(st), st.ColumnName(1), blob)
+ default:
+ t.Logf("TEXT => %v: %v, %v: %v\n", ResultColumn(0).Name(st), ResultColumn(0).Value(st), st.ColumnName(1), st.Column(1))
+ }
+ })
+ fatalOnError(t, e, "%v failed on step %v", sql, c)
+ if rows, _ := table.Rows(db); rows != c {
+ t.Fatalf("%v: %v rows expected, %v rows found", table.Name, rows, c)
+ }
+ return
+}
+
+func (db *Database) runQuery(t *testing.T, sql string, params... interface{}) {
+ st, e := db.Prepare(sql, func(s *Statement) {
+ e, j := s.Bind(0, params...)
+ fatalOnError(t, e, "column %v", j)
+ })
+ fatalOnError(t, e, st.SQLSource())
+ st.Step(nil)
+ st.Finalize()
+}
+
+func (db *Database) populate(t *testing.T, table *Table) {
+ switch table.Name {
+ case "foo":
+ db.runQuery(t, "INSERT INTO foo values (1, 'this is a test')")
+ db.runQuery(t, "INSERT INTO foo values (?, ?)", 2, "holy moly")
+ if c, _ := table.Rows(db); c != 2 {
+ t.Fatal("Failed to populate %v", table.Name)
+ }
+ case "bar":
+ db.runQuery(t, "INSERT INTO bar values (1, 'this is a test')")
+ db.runQuery(t, "INSERT INTO bar values (?, ?)", 2, "holy moly")
+ db.runQuery(t, "INSERT INTO bar values (?, ?)", 3, TwoItems{ "holy moly", "guacomole" })
+ if c, _ := table.Rows(db); c != 3 {
+ t.Fatal("Failed to populate %v", table.Name)
+ }
+ }
+}
View
54 query_parameter.go
@@ -0,0 +1,54 @@
+package sqlite3
+
+// #include <sqlite3.h>
+// int gosqlite3_bind_text(sqlite3_stmt* s, int p, const char* q, int n) {
+// return sqlite3_bind_text(s, p, q, n, SQLITE_TRANSIENT);
+// }
+// int gosqlite3_bind_blob(sqlite3_stmt* s, int p, const void* q, int n) {
+// return sqlite3_bind_blob(s, p, q, n, SQLITE_TRANSIENT);
+// }
+import "C"
+
+import "bytes"
+import "gob"
+import "os"
+import "unsafe"
+
+type QueryParameter int
+func (p QueryParameter) bind_blob(s *Statement, v []byte) os.Error {
+ if e := Errno(C.gosqlite3_bind_blob(s.cptr, C.int(p), unsafe.Pointer(C.CString(string(v))), C.int(len(v)))); e != OK {
+ return e
+ }
+ return nil
+}
+
+func (p QueryParameter) Bind(s *Statement, value interface{}) (e os.Error) {
+ var rv Errno
+ switch v := value.(type) {
+ case nil:
+ rv = Errno(C.sqlite3_bind_null(s.cptr, C.int(p)))
+ case int:
+ rv = Errno(C.sqlite3_bind_int(s.cptr, C.int(p), C.int(v)))
+ case string:
+ rv = Errno(C.gosqlite3_bind_text(s.cptr, C.int(p), C.CString(v), C.int(len(v))))
+ case int64:
+ rv = Errno(C.sqlite3_bind_int64(s.cptr, C.int(p), C.sqlite3_int64(v)))
+ case float32:
+ rv = Errno(C.sqlite3_bind_double(s.cptr, C.int(p), C.double(v)))
+ case float64:
+ rv = Errno(C.sqlite3_bind_double(s.cptr, C.int(p), C.double(v)))
+ default:
+ buffer := new(bytes.Buffer)
+ encoder := gob.NewEncoder(buffer)
+ if encoder.Encode(value) != nil {
+ rv = ENCODER
+ } else {
+ rawbuffer := string(buffer.Bytes())
+ rv = Errno(C.gosqlite3_bind_blob(s.cptr, C.int(p), unsafe.Pointer(C.CString(rawbuffer)), C.int(len(rawbuffer))))
+ }
+ }
+ if rv != OK {
+ e = rv
+ }
+ return
+}
View
28 query_parameter_test.go
@@ -0,0 +1,28 @@
+package sqlite3
+
+import "testing"
+
+func TestQueryParameterBinding(t *testing.T) {
+ Session("test.db", func(db *Database) {
+ if db == nil {
+ t.Fatal("unable to acquire DB handle")
+ }
+ BAR.Create(db)
+ sql := "INSERT INTO bar values (?, ?);"
+ st, e := db.Prepare(sql, func(s *Statement) {
+ fatalOnError(t, QueryParameter(1).Bind(s, nil), "unable to bind NULL to column 1")
+ })
+ fatalOnError(t, e, "unable to prepare query: %v", sql)
+
+ for _, v := range []interface{}{1.1, "hello", TwoItems{ "a", "b" }, []int{13, 27} } {
+ fatalOnError(t, QueryParameter(1).Bind(st, v), "erroneously bound %v to column 1", v)
+ }
+
+ fatalOnError(t, QueryParameter(1).Bind(st, 1), "unable to bind integer to column 1")
+ fatalOnError(t, QueryParameter(2).Bind(st, TwoItems{ "a", "b" }), "unable to bind blob to column 2")
+
+ st, e = db.Prepare("INSERT INTO bar values (1, ?)")
+ fatalOnError(t, e, "unable to prepare query: %v", st.SQLSource())
+ fatalOnError(t, QueryParameter(1).Bind(st, TwoItems{ "a", "b" }), "unable to bind blob to column 2")
+ })
+}
View
60 result_column.go
@@ -0,0 +1,60 @@
+package sqlite3
+
+// #include <sqlite3.h>
+import "C"
+
+import "bytes"
+import "gob"
+import "unsafe"
+
+const(
+ INTEGER = 1
+ FLOAT = 2
+ TEXT = 3
+ BLOB = 4
+ NULL = 5
+)
+
+
+type ResultColumn int
+
+func (c ResultColumn) make_buffer(s *Statement, addr interface{}) (buffer string) {
+ switch addr := addr.(type) {
+ case *C.uchar:
+ buffer = C.GoStringN((*C.char)(unsafe.Pointer(addr)), C.int(c.ByteCount(s)))
+ case unsafe.Pointer:
+ buffer = C.GoStringN((*C.char)(addr), C.int(c.ByteCount(s)))
+ }
+ return
+}
+
+func (c ResultColumn) Name(s *Statement) string {
+ return C.GoString(C.sqlite3_column_name(s.cptr, C.int(c)))
+}
+
+func (c ResultColumn) Type(s *Statement) int {
+ return int(C.sqlite3_column_type(s.cptr, C.int(c)))
+}
+
+func (c ResultColumn) ByteCount(s *Statement) int {
+ return int(C.sqlite3_column_bytes(s.cptr, C.int(c)))
+}
+
+func (c ResultColumn) Value(s *Statement) (value interface{}) {
+ switch c.Type(s) {
+ case INTEGER:
+ value = int64(C.sqlite3_int64(C.sqlite3_column_int64(s.cptr, C.int(c))))
+ case FLOAT:
+ value = float64(C.sqlite3_column_double(s.cptr, C.int(c)))
+ case TEXT:
+ value = c.make_buffer(s, C.sqlite3_column_text(s.cptr, C.int(c)))
+ case BLOB:
+ buffer := c.make_buffer(s, C.sqlite3_column_blob(s.cptr, C.int(c)))
+ value = gob.NewDecoder(bytes.NewBuffer([]byte(buffer)))
+ case NULL:
+ value = nil
+ default:
+ panic("unknown column type")
+ }
+ return
+}
View
19 result_column_test.go
@@ -0,0 +1,19 @@
+package sqlite3
+
+import "testing"
+
+func TestResultColumn(t *testing.T) {
+ Session("test.db", func(db *Database) {
+ BAR.Create(db)
+ st, e := db.Prepare("INSERT INTO bar values (?, ?)", func(s *Statement) {
+ fatalOnError(t, QueryParameter(1).Bind(s, nil), "unable to bind NULL to column 1")
+ for _, v := range []interface{}{1.1, "hello", TwoItems{ "a", "b" }, []int{13, 27} } {
+ fatalOnError(t, QueryParameter(1).Bind(s, v), "erroneously bound %v to column 1", v)
+ }
+ fatalOnError(t, QueryParameter(1).Bind(s, 1), "unable to bind integer to column 1")
+ fatalOnError(t, QueryParameter(2).Bind(s, TwoItems{ "a", "b" }), "unable to bind blob to column 2")
+ return
+ })
+ fatalOnError(t, e, "unable to prepare query: %v", st.SQLSource())
+ })
+}
View
237 sql_test.go
@@ -1,189 +1,116 @@
package sqlite3
import "bytes"
-import "fmt"
import "gob"
import "testing"
-func TestGeneral(t *testing.T) {
- filename := ":memory:"
+func TestGeneral(t *testing.T) {
Initialize()
defer Shutdown()
t.Logf("Sqlite3 Version: %v\n", LibVersion())
-
+
+ filename := ":memory:"
db, e := Open(filename)
- if e != OK {
- t.Fatalf("Open %v: %v", filename, e)
- }
+ fatalOnError(t, e, "opening %v", filename)
+
defer db.Close()
t.Logf("Database opened: %v [flags: %v]", db.Filename, int(db.Flags))
- t.Logf(fmt.Sprintf("Returning status: %v", e))
-
- st, e := db.Prepare("CREATE TABLE foo (i INTEGER, s VARCHAR(20));")
- if e != OK {
- t.Fatalf("Create Table: %v", e)
- }
- defer st.Finalize()
- st.Step()
-
- st, e = db.Prepare("DROP TABLE foo;")
- if e != OK {
- t.Fatalf("Drop Table: %v", e)
- }
- defer st.Finalize()
- st.Step()
+ t.Logf("Returning status: %v", e)
}
-func runQuery(t *testing.T, db *Database, sql string, params... interface{}) {
- if st, e := db.Prepare(sql); e == OK {
- if e, i := st.Bind(1, params...); e == OK {
- st.Step()
- } else {
- t.Errorf("Error: unable to bind column %v resulting in error %v", i , e)
- }
- st.Finalize()
- } else {
- t.Errorf("Error: failed to compile %v", st.SQLSource())
- }
-}
func TestSession(t *testing.T) {
- Session(":memory:", func(db *Database) {
- runQuery(t, db, "DROP TABLE IF EXISTS foo;")
- runQuery(t, db, "CREATE TABLE foo (number INTEGER, text VARCHAR(20));")
- runQuery(t, db, "INSERT INTO foo values (1, 'this is a test')")
- runQuery(t, db, "INSERT INTO foo values (?, ?)", 2, "holy moly")
-
- if st, e := db.Prepare("SELECT * from foo limit 5;"); e == OK {
- for i := 0; ; i++ {
- switch st.Step() {
- case DONE:
- return
- case ROW:
- text := st.Column(1)
- switch text := text.(type) {
- case *gob.Decoder:
- blob := TwoItems{}
- text.Decode(blob)
- t.Logf("%v: %v, %v: %v\n", Column(0).Name(st), Column(0).Value(st), st.ColumnName(1), blob)
- default:
- t.Logf("%v: %v, %v: %v\n", Column(0).Name(st), Column(0).Value(st), st.ColumnName(1), st.Column(1))
- }
- default:
- t.Errorf("SELECT * from foo limit 5; failed on step %v: %v", i, db.Error())
- return
- }
- }
- st.Finalize()
- } else {
- t.Errorf("SELECT * from foo limit 5; failed to return results %v", db.Error())
- }
+ Session("test.db", func(db *Database) {
+ FOO.Drop(db)
+ FOO.Create(db)
+ db.runQuery(t, "INSERT INTO foo values (1, 'this is a test')")
+ db.runQuery(t, "INSERT INTO foo values (?, ?)", 2, "holy moly")
+ db.stepThroughRows(t, FOO)
})
}
+
func TestTransientSession(t *testing.T) {
TransientSession(func(db *Database) {
- runQuery(t, db, "DROP TABLE IF EXISTS foo;")
- runQuery(t, db, "CREATE TABLE foo (number INTEGER, text VARCHAR(20));")
- runQuery(t, db, "INSERT INTO foo values (1, 'this is a test')")
- runQuery(t, db, "INSERT INTO foo values (?, ?)", 2, "holy moly")
-
- if st, e := db.Prepare("SELECT * from foo limit 5;"); e == OK {
- for i := 0; ; i++ {
- switch st.Step() {
- case DONE:
- return
- case ROW:
- text := st.Column(1)
- switch text := text.(type) {
- case *gob.Decoder:
- blob := TwoItems{}
- text.Decode(blob)
- t.Logf("%v: %v, %v: %v\n", Column(0).Name(st), Column(0).Value(st), st.ColumnName(1), blob)
- default:
- t.Logf("%v: %v, %v: %v\n", Column(0).Name(st), Column(0).Value(st), st.ColumnName(1), st.Column(1))
- }
- default:
- t.Errorf("SELECT * from foo limit 5; failed on step %v: %v", i, db.Error())
- return
- }
- }
- st.Finalize()
- } else {
- t.Errorf("SELECT * from foo limit 5; failed to return results %v", db.Error())
- }
+ FOO.Drop(db)
+ FOO.Create(db)
+ db.runQuery(t, "INSERT INTO foo values (1, 'this is a test')")
+ db.runQuery(t, "INSERT INTO foo values (?, ?)", 2, "holy moly")
+ db.stepThroughRows(t, FOO)
})
}
-type TwoItems struct {
- Number string
- Text string
-}
-func (t *TwoItems) String() string {
- return "[" + t.Number + " : " + t.Text + "]"
+func TestBlob(t *testing.T) {
+ Session("test.db", func(db *Database) {
+ BAR.Drop(db)
+ BAR.Create(db)
+
+ buffer := new(bytes.Buffer)
+ encoder := gob.NewEncoder(buffer)
+ fatalOnError(t, encoder.Encode(TwoItems{ "holy", "moly guacomole" }), "Encoding failed: buffer = %v", buffer)
+ t.Logf("Encoded data: %v", buffer.Bytes())
+
+ db.runQuery(t, "INSERT INTO bar values (?, ?)", 1, TwoItems{ "holy moly", "guacomole" })
+ db.stepThroughRows(t, BAR)
+ })
}
-func TestBlobEncoding(t *testing.T) {
- Session("test.db", func(db *Database) {
- runQuery(t, db, "DROP TABLE IF EXISTS foo;")
- runQuery(t, db, "CREATE TABLE foo (number INTEGER, value BLOB);")
-
- if st, e := db.Prepare("INSERT INTO foo values (3, ?)"); e == OK {
- buffer := new(bytes.Buffer)
- encoder := gob.NewEncoder(buffer)
- if err := encoder.Encode(TwoItems{ "holy moly", "guacomole" }); err != nil {
- t.Errorf("Encoding failed: buffer = %v", )
- } else {
- t.Logf("Encoded data: %v", buffer.Bytes())
- e = Column(1).bind_blob(st, buffer.Bytes())
+func TestTransfers(t *testing.T) {
+ TransientSession(func(source *Database) {
+ tables := []*Table{ FOO, BAR }
+ for _, table := range tables {
+ table.Drop(source)
+ table.Create(source)
+ if c, _ := table.Rows(source); c != 0 {
+ t.Fatalf("%v already contains data", table.Name)
}
- if e == OK {
- st.Step()
- } else {
- t.Errorf("Error: unable to bind column 1 resulting in error %v", e)
- }
- st.Finalize()
- } else {
- t.Errorf("Error: failed to compile %v", st.SQLSource())
}
+ source.runQuery(t, "INSERT INTO foo values (1, 'this is a test')")
+ source.runQuery(t, "INSERT INTO foo values (?, ?)", 2, "holy moly")
+ source.runQuery(t, "INSERT INTO bar values (?, ?)", 1, TwoItems{ "holy moly", "guacomole" })
+ source.stepThroughRows(t, FOO)
+ source.stepThroughRows(t, BAR)
+
+ Session("target.db", func(target *Database) {
+ t.Logf("Database opened: %v [flags: %v]", target.Filename, int(target.Flags))
+ tables := []*Table{ FOO, BAR }
+ for _, table := range tables {
+ table.Drop(target)
+ table.Create(target)
+ }
+ fatalOnError(t, target.Load(source, "main"), "loading from %v[%]", source.Filename, "main")
+ for _, table := range tables {
+ i, _ := table.Rows(target)
+ j, _ := table.Rows(source)
+ if i != j {
+ t.Fatalf("failed to load data for table %v", table.Name)
+ }
+ }
+
+ Session("backup.db", func(backup *Database) {
+ t.Logf("Database opened: %v [flags: %v]", backup.Filename, int(backup.Flags))
+ tables := []*Table{ FOO, BAR }
+ for _, table := range tables {
+ table.Drop(backup)
+ table.Create(backup)
+ }
+ fatalOnError(t, target.Save(backup, "main"), "saving to %v[%]", backup.Filename, "main")
+ for _, table := range tables {
+ i, _ := table.Rows(target)
+ j, _ := table.Rows(backup)
+ if i != j {
+ t.Fatalf("failed to load data for table %v", table.Name)
+ }
+ }
+ })
+ })
})
}
-func TestBlob(t *testing.T) {
+/*
+func TestBackup(t * testing.T) {
Session("test.db", func(db *Database) {
- runQuery(t, db, "DROP TABLE IF EXISTS foo;")
- runQuery(t, db, "CREATE TABLE foo (number INTEGER, value BLOB);")
- runQuery(t, db, "INSERT INTO foo values (1, 'this is a test')")
- runQuery(t, db, "INSERT INTO foo values (?, ?)", 2, "holy moly")
- runQuery(t, db, "INSERT INTO foo values (?, ?)", 3, TwoItems{ "holy moly", "guacomole" })
-
- if st, e := db.Prepare("SELECT * from foo limit 5;"); e == OK {
- for i := 0; ; i++ {
- switch st.Step() {
- case DONE:
- return
- case ROW:
- text := st.Column(1)
- switch text := text.(type) {
- case *gob.Decoder:
- blob := new(TwoItems)
- if e = text.Decode(blob); e == nil {
- t.Logf("BLOB => %v: %v, %v: %v\n", Column(0).Name(st), Column(0).Value(st), st.ColumnName(1), blob)
- } else {
- t.Logf("BLOB => %v: %v, %v: (decoding failed: %v)\n", Column(0).Name(st), Column(0).Value(st), st.ColumnName(1), blob)
- }
- default:
- t.Logf("TEXT => %v: %v, %v: %v\n", Column(0).Name(st), Column(0).Value(st), st.ColumnName(1), st.Column(1))
- }
- default:
- t.Errorf("SELECT * from foo limit 5; failed on step %v: %v", i, db.Error())
- return
- }
- }
- st.Finalize()
- } else {
- t.Errorf("SELECT * from foo limit 5; failed to return results %v", db.Error())
- }
})
-}
+}
+*/
View
4 sqlite3.go
@@ -14,7 +14,7 @@ func Shutdown() {
func Session(filename string, f func(db *Database)) {
Initialize()
defer Shutdown()
- if db, e := Open(filename); e == OK {
+ if db, e := Open(filename); e == nil {
defer db.Close()
f(db)
}
@@ -23,7 +23,7 @@ func Session(filename string, f func(db *Database)) {
func TransientSession(f func(db *Database)) {
Initialize()
defer Shutdown()
- if db := TransientDatabase(); db.Open() == OK {
+ if db := TransientDatabase(); db.Open() == nil {
defer db.Close()
f(db)
}
View
106 statement.go
@@ -2,31 +2,46 @@ package sqlite3
// #include <sqlite3.h>
import "C"
+import "os"
type Statement struct {
db *Database
cptr *C.sqlite3_stmt
- SQL string
timestamp int64
}
+func (s *Statement) Parameters() int {
+ return int(C.sqlite3_bind_parameter_count(s.cptr))
+}
+
+func (s *Statement) Columns() int {
+ return int(C.sqlite3_column_count(s.cptr))
+}
+
func (s *Statement) ColumnName(column int) string {
- return Column(column).Name(s)
+ return ResultColumn(column).Name(s)
}
func (s *Statement) ColumnType(column int) int {
- return Column(column).Type(s)
+ return ResultColumn(column).Type(s)
}
func (s *Statement) Column(column int) (value interface{}) {
- return Column(column).Value(s)
+ return ResultColumn(column).Value(s)
}
-func (s *Statement) Bind(start_column int, values... interface{}) (rv Errno, index int) {
- column := Column(0)
+func (s *Statement) Row() (values []interface{}) {
+ for i := 0; i < s.Columns(); i++ {
+ values = append(values, s.Column(i))
+ }
+ return
+}
+
+func (s *Statement) Bind(start_column int, values... interface{}) (e os.Error, index int) {
+ column := QueryParameter(start_column)
for i, v := range values {
column++
- if rv = column.Bind(s, v); rv != OK {
+ if e = column.Bind(s, v); e != nil {
index = i
return
}
@@ -34,30 +49,75 @@ func (s *Statement) Bind(start_column int, values... interface{}) (rv Errno, ind
return
}
-func (s *Statement) SQLSource() string {
- return C.GoString(C.sqlite3_sql(s.cptr))
-}
-
-func (s *Statement) Parameters() Errno {
- return Errno(C.sqlite3_bind_parameter_count(s.cptr))
+func (s *Statement) SQLSource() (sql string) {
+ if s.cptr != nil {
+ sql = C.GoString(C.sqlite3_sql(s.cptr))
+ }
+ return
}
-func (s *Statement) Columns() Errno {
- return Errno(C.sqlite3_column_count(s.cptr))
+func (s *Statement) Finalize() os.Error {
+ if e := Errno(C.sqlite3_finalize(s.cptr)); e != OK {
+ return e
+ }
+ return nil
}
-func (s *Statement) Finalize() Errno {
- return Errno(C.sqlite3_finalize(s.cptr))
+func (s *Statement) Step(f func(*Statement, ...interface{})) (e os.Error) {
+ r := Errno(C.sqlite3_step(s.cptr))
+ switch r {
+ case DONE:
+ e = nil
+ case ROW:
+ if f != nil {
+ defer func() {
+ switch x := recover().(type) {
+ case nil: e = ROW
+ case os.Error: e = x
+ default: e = MISUSE
+ }
+ }()
+ f(s, s.Row()...)
+ }
+ default:
+ e = r
+ }
+ return
}
-func (s *Statement) Step() Errno {
- return Errno(C.sqlite3_step(s.cptr))
+func (s *Statement) All(f func(*Statement, ...interface{})) (c int, e os.Error) {
+ for {
+ if e = s.Step(f); e != nil {
+ if r, ok := e.(Errno); ok {
+ switch r {
+ case ROW:
+ c++
+ continue
+ default:
+ e = r
+ break
+ }
+ }
+ } else {
+ break
+ }
+ }
+ if e == nil {
+ s.Finalize()
+ }
+ return
}
-func (s *Statement) Reset() Errno {
- return Errno(C.sqlite3_reset(s.cptr))
+func (s *Statement) Reset() os.Error {
+ if e := Errno(C.sqlite3_reset(s.cptr)); e != OK {
+ return e
+ }
+ return nil
}
-func (s *Statement) ClearBindings() Errno {
- return Errno(C.sqlite3_clear_bindings(s.cptr))
+func (s *Statement) ClearBindings() os.Error {
+ if e := Errno(C.sqlite3_clear_bindings(s.cptr)); e != OK {
+ return e
+ }
+ return nil
}
View
31 table.go
@@ -0,0 +1,31 @@
+package sqlite3
+
+import "C"
+
+import "fmt"
+import "os"
+
+type Table struct {
+ Name string
+ ColumnSpec string
+}
+
+func (t *Table) Create(db *Database) (e os.Error) {
+ sql := fmt.Sprintf("CREATE TABLE %v (%v);", t.Name, t.ColumnSpec)
+ _, e = db.Execute(sql, nil)
+ return
+}
+
+func (t *Table) Drop(db *Database) (e os.Error) {
+ sql := fmt.Sprintf("DROP TABLE IF EXISTS %v;", t.Name, t.ColumnSpec)
+ _, e = db.Execute(sql, nil)
+ return
+}
+
+func (t *Table) Rows(db *Database) (c int, e os.Error) {
+ sql := fmt.Sprintf("SELECT Count(*) FROM %v;", t.Name)
+ _, e = db.Execute(sql, func(s *Statement, values ...interface{}) {
+ c = int(values[0].(int64))
+ })
+ return
+}
Please sign in to comment.
Something went wrong with that request. Please try again.