Permalink
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...
1 parent d5c0cf5 commit 5b9e113b052f403d168716daec2313deccca5dd7 @feyeleanor committed Jan 14, 2011
Showing with 573 additions and 285 deletions.
  1. +5 −2 Makefile
  2. +52 −0 backup.go
  3. +0 −88 column.go
  4. +78 −15 database.go
  5. +79 −0 helpers_test.go
  6. +54 −0 query_parameter.go
  7. +28 −0 query_parameter_test.go
  8. +60 −0 result_column.go
  9. +19 −0 result_column_test.go
  10. +82 −155 sql_test.go
  11. +2 −2 sqlite3.go
  12. +83 −23 statement.go
  13. +31 −0 table.go
View
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
+ }
+ }
+}
Oops, something went wrong.

0 comments on commit 5b9e113

Please sign in to comment.