Commit 0f1bd89b authored by gwenn's avatar gwenn

Refactor Scan functions.

parent 7627d184
......@@ -11,7 +11,8 @@ CGOFILES=\
backup.go\
meta.go\
trace.go\
blob.go
blob.go\
value.go
GOFILES=\
date.go
......
......@@ -13,6 +13,25 @@ Stmt#Scan uses native sqlite3_column_x methods.
Stmt#NamedScan is added. It's compliant with [go-dbi](https://github.com/thomaslee/go-dbi/) API but I think its signature should be improved/modified.
Stmt#ScanColumn/NamedScanColumn are added to test NULL value.
Currently, the weak point of the binding is the *Scan methods:
The original implementation is using this strategy:
- convert the stored value to a []byte by calling sqlite3_column_bytes,
- convert the bytes to the desired Go type with correct feedback in case of illegal conversion,
- but apparently no support for NULL value.
Using the native sqlite3_column_x implies:
- optimal conversion from the storage type to Go type (when they match),
- loosy conversion when types mismatch (select cast('M' as int); --> 0),
- NULL value cannot be returned, default value (0, false, "") is returned instead.
Maybe we should let the caller do the conversion:
- she gives the Scan method a pointer/context (interface{}) and a callback function with this signature:
func (data interface{}, c *Converter) os.Error
- the Converter gives access to:
* the number of columns,
* the type of a column (by name or index),
* the bool/byte/[]byte/int/int64/float/string value of a column (by name or index).
- for each row, the callback is invoked.
Misc:
Conn#EnableFkey/IsFKeyEnabled
Conn#Changes/TotalChanges
......@@ -25,7 +44,7 @@ Conn#EnableLoadExtension/LoadExtension
Stmt#ExecUpdate
Stmt#BindParameterCount/BindParameterIndex/BindParameterName
Stmt#ClearBindings
Stmt#ColumnCount/ColumnIndex(name)/ColumnName(index)
Stmt#ColumnCount/ColumnIndex(name)/ColumnName(index)/ColumnType(index)
Stmt#ReadOnly
Blob:
......
......@@ -529,12 +529,34 @@ func (s *Stmt) ColumnName(index int) string {
return C.GoString(C.sqlite3_column_name(s.stmt, C.int(index)))
}
type Type int
func (t Type) String() string {
return typeText[t]
}
var (
Integer Type = Type(C.SQLITE_INTEGER)
Float Type = Type(C.SQLITE_FLOAT)
Blob Type = Type(C.SQLITE_BLOB)
Null Type = Type(C.SQLITE_NULL)
Text Type = Type(C.SQLITE3_TEXT)
)
var typeText = map[Type]string{
Integer: "Integer",
Float: "Float",
Blob: "Blob",
Null: "Null",
Text: "Text",
}
// The leftmost column is number 0.
// After a type conversion, the value returned by sqlite3_column_type() is undefined.
// Calls sqlite3_column_type
// http://sqlite.org/c3ref/column_blob.html
func (s *Stmt) columnType(index int) C.int {
return C.sqlite3_column_type(s.stmt, C.int(index))
func (s *Stmt) ColumnType(index int) Type {
return Type(C.sqlite3_column_type(s.stmt, C.int(index)))
}
// NULL value is converted to 0 if arg type is *int,*int64,*float,*float64, to "" for *string, to []byte{} for *[]byte and to false for *bool.
......@@ -554,7 +576,7 @@ func (s *Stmt) NamedScan(args ...interface{}) os.Error {
return err
}
ptr := args[i+1]
_, err = s.ScanColumn(index, ptr, false)
_, err = s.ScanColumn(index, ptr /*, false*/ )
if err != nil {
return err
}
......@@ -573,42 +595,10 @@ func (s *Stmt) Scan(args ...interface{}) os.Error {
}
for i, v := range args {
_, err := s.ScanColumn(i, v, false)
if err != nil {
return err
}
}
return nil
}
// Calls sqlite3_column_count and sqlite3_column_(blob|double|int|int64|text) depending on columns type.
// http://sqlite.org/c3ref/column_blob.html
func (s *Stmt) ScanNamedValues(values ...NamedValue) os.Error {
n := s.ColumnCount()
if n != len(values) { // What happens when the number of arguments is less than the number of columns?
return os.NewError(fmt.Sprintf("incorrect argument count for Stmt.ScanValues: have %d want %d", len(values), n))
}
for _, v := range values {
index, err := s.ColumnIndex(v.Name()) // How to look up only once for one statement ?
_, err := s.ScanColumn(i, v /*, false*/ )
if err != nil {
return err
}
s.ScanValue(index, v)
}
return nil
}
// Calls sqlite3_column_count and sqlite3_column_(blob|double|int|int64|text) depending on columns type.
// http://sqlite.org/c3ref/column_blob.html
func (s *Stmt) ScanValues(values ...Value) os.Error {
n := s.ColumnCount()
if n != len(values) { // What happens when the number of arguments is less than the number of columns?
return os.NewError(fmt.Sprintf("incorrect argument count for Stmt.ScanValues: have %d want %d", len(values), n))
}
for i, v := range values {
s.ScanValue(i, v)
}
return nil
}
......@@ -637,120 +627,138 @@ func (s *Stmt) ColumnIndex(name string) (int, os.Error) {
}
// Set nullable to false to skip NULL type test.
// Returns true when nullable is true and column is null.
// Returns true when column is null.
// Calls sqlite3_column_count, sqlite3_column_name and sqlite3_column_(blob|double|int|int64|text) depending on args type.
// http://sqlite.org/c3ref/column_blob.html
func (s *Stmt) NamedScanColumn(name string, value interface{}, nullable bool) (bool, os.Error) {
func (s *Stmt) NamedScanColumn(name string, value interface{}) (bool, os.Error) {
index, err := s.ColumnIndex(name)
if err != nil {
return false, err
}
return s.ScanColumn(index, value, true)
return s.ScanColumn(index, value)
}
// The leftmost column/index is number 0.
// Set nullable to false to skip NULL type test.
// Returns true when nullable is true and column is null.
// Returns true when column is null.
// Calls sqlite3_column_(blob|double|int|int64|text) depending on args type.
// http://sqlite.org/c3ref/column_blob.html
func (s *Stmt) ScanColumn(index int, value interface{}, nullable bool) (bool, os.Error) {
func (s *Stmt) ScanColumn(index int, value interface{}) (bool, os.Error) {
var isNull bool
switch value := value.(type) {
case nil:
case *string:
p := C.sqlite3_column_text(s.stmt, C.int(index))
if p == nil {
*value = ""
isNull = true
} else {
n := C.sqlite3_column_bytes(s.stmt, C.int(index))
*value = C.GoStringN((*C.char)(unsafe.Pointer(p)), n)
}
*value, isNull = s.ScanText(index)
case *int:
if nullable && s.columnType(index) == C.SQLITE_NULL {
*value = 0
isNull = true
} else {
*value = int(C.sqlite3_column_int(s.stmt, C.int(index)))
}
*value, isNull = s.ScanInt(index)
case *int64:
if nullable && s.columnType(index) == C.SQLITE_NULL {
*value = 0
isNull = true
} else {
*value = int64(C.sqlite3_column_int64(s.stmt, C.int(index)))
}
*value, isNull = s.ScanInt64(index)
case *byte:
if nullable && s.columnType(index) == C.SQLITE_NULL {
*value = 0
isNull = true
} else {
*value = byte(C.sqlite3_column_int(s.stmt, C.int(index)))
}
*value, isNull = s.ScanByte(index)
case *bool:
if nullable && s.columnType(index) == C.SQLITE_NULL {
*value = false
isNull = true
} else {
*value = C.sqlite3_column_int(s.stmt, C.int(index)) == 1
}
*value, isNull = s.ScanBool(index)
case *float64:
if nullable && s.columnType(index) == C.SQLITE_NULL {
*value = 0
isNull = true
} else {
*value = float64(C.sqlite3_column_double(s.stmt, C.int(index)))
}
*value, isNull = s.ScanFloat64(index)
case *[]byte:
p := C.sqlite3_column_blob(s.stmt, C.int(index))
if p == nil {
*value = nil
isNull = true
} else {
n := C.sqlite3_column_bytes(s.stmt, C.int(index))
*value = (*[1 << 30]byte)(unsafe.Pointer(p))[0:n]
}
*value, isNull = s.ScanBlob(index)
default:
return false, os.NewError("unsupported type in Scan: " + reflect.TypeOf(value).String())
}
return isNull, nil
}
type NamedValue interface {
Value
Name() string
// The leftmost column/index is number 0.
// Returns true when column is null.
// Calls sqlite3_column_text.
// http://sqlite.org/c3ref/column_blob.html
func (s *Stmt) ScanText(index int) (value string, isNull bool) {
p := C.sqlite3_column_text(s.stmt, C.int(index))
if p == nil {
isNull = true
} else {
n := C.sqlite3_column_bytes(s.stmt, C.int(index))
value = C.GoStringN((*C.char)(unsafe.Pointer(p)), n)
}
return
}
type Value interface {
setNull(bool)
setInt(int64) // Versus int?
setFloat(float64)
setText(string)
setBlob([]byte)
// The leftmost column/index is number 0.
// Returns true when column is null.
// Calls sqlite3_column_int.
// http://sqlite.org/c3ref/column_blob.html
func (s *Stmt) ScanInt(index int) (value int, isNull bool) {
if s.ColumnType(index) == Null { // TODO How to avoid this test for not nullable column or when it doesn't care
isNull = true
} else {
value = int(C.sqlite3_column_int(s.stmt, C.int(index)))
}
return
}
// The leftmost column/index is number 0.
// Calls sqlite3_column_(blob|double|int|int64|text) depending on columns type.
// Returns true when column is null.
// Calls sqlite3_column_int64.
// http://sqlite.org/c3ref/column_blob.html
func (s *Stmt) ScanValue(index int, value Value) {
switch s.columnType(index) {
case C.SQLITE_NULL:
value.setNull(true)
case C.SQLITE_TEXT:
p := C.sqlite3_column_text(s.stmt, C.int(index))
n := C.sqlite3_column_bytes(s.stmt, C.int(index))
value.setText(C.GoStringN((*C.char)(unsafe.Pointer(p)), n))
case C.SQLITE_INTEGER:
value.setInt(int64(C.sqlite3_column_int64(s.stmt, C.int(index))))
case C.SQLITE_FLOAT:
value.setFloat(float64(C.sqlite3_column_double(s.stmt, C.int(index))))
case C.SQLITE_BLOB:
p := C.sqlite3_column_blob(s.stmt, C.int(index))
func (s *Stmt) ScanInt64(index int) (value int64, isNull bool) {
if s.ColumnType(index) == Null { // TODO How to avoid this test ...
isNull = true
} else {
value = int64(C.sqlite3_column_int64(s.stmt, C.int(index)))
}
return
}
// The leftmost column/index is number 0.
// Returns true when column is null.
// Calls sqlite3_column_int.
// http://sqlite.org/c3ref/column_blob.html
func (s *Stmt) ScanByte(index int) (value byte, isNull bool) {
if s.ColumnType(index) == Null { // TODO How to avoid this test ...
isNull = true
} else {
value = byte(C.sqlite3_column_int(s.stmt, C.int(index)))
}
return
}
// The leftmost column/index is number 0.
// Returns true when column is null.
// Calls sqlite3_column_int.
// http://sqlite.org/c3ref/column_blob.html
func (s *Stmt) ScanBool(index int) (value bool, isNull bool) {
if s.ColumnType(index) == Null { // TODO How to avoid this test ...
isNull = true
} else {
value = C.sqlite3_column_int(s.stmt, C.int(index)) == 1
}
return
}
// The leftmost column/index is number 0.
// Returns true when column is null.
// Calls sqlite3_column_double.
// http://sqlite.org/c3ref/column_blob.html
func (s *Stmt) ScanFloat64(index int) (value float64, isNull bool) {
if s.ColumnType(index) == Null { // TODO How to avoid this test ...
isNull = true
} else {
value = float64(C.sqlite3_column_double(s.stmt, C.int(index)))
}
return
}
// The leftmost column/index is number 0.
// Returns true when column is null.
// Calls sqlite3_column_bytes.
// http://sqlite.org/c3ref/column_blob.html
func (s *Stmt) ScanBlob(index int) (value []byte, isNull bool) {
p := C.sqlite3_column_blob(s.stmt, C.int(index))
if p == nil {
isNull = true
} else {
n := C.sqlite3_column_bytes(s.stmt, C.int(index))
value.setBlob((*[1 << 30]byte)(unsafe.Pointer(p))[0:n])
default:
panic("The column type is not one of SQLITE_INTEGER, SQLITE_FLOAT, SQLITE_TEXT, SQLITE_BLOB, or SQLITE_NULL")
value = (*[1 << 30]byte)(unsafe.Pointer(p))[0:n]
}
return
}
// Calls http://sqlite.org/c3ref/finalize.html
......
......@@ -315,19 +315,19 @@ func TestScanColumn(t *testing.T) {
t.Fatal("no result")
}
var i1, i2, i3 int
null := Must(s.ScanColumn(0, &i1, true))
null := Must(s.ScanColumn(0, &i1 /*, true*/ ))
if null {
t.Errorf("Expected not null value")
} else if i1 != 1 {
t.Errorf("Expected 1 <> %d\n", i1)
}
null = Must(s.ScanColumn(1, &i2, true))
null = Must(s.ScanColumn(1, &i2 /*, true*/ ))
if !null {
t.Errorf("Expected null value")
} else if i2 != 0 {
t.Errorf("Expected 0 <> %d\n", i2)
}
null = Must(s.ScanColumn(2, &i3, true))
null = Must(s.ScanColumn(2, &i3 /*, true*/ ))
if null {
t.Errorf("Expected not null value")
} else if i3 != 0 {
......@@ -348,19 +348,19 @@ func TestNamedScanColumn(t *testing.T) {
t.Fatal("no result")
}
var i1, i2, i3 int
null := Must(s.NamedScanColumn("i1", &i1, true))
null := Must(s.NamedScanColumn("i1", &i1 /*, true*/ ))
if null {
t.Errorf("Expected not null value")
} else if i1 != 1 {
t.Errorf("Expected 1 <> %d\n", i1)
}
null = Must(s.NamedScanColumn("i2", &i2, true))
null = Must(s.NamedScanColumn("i2", &i2 /*, true*/ ))
if !null {
t.Errorf("Expected null value")
} else if i2 != 0 {
t.Errorf("Expected 0 <> %d\n", i2)
}
null = Must(s.NamedScanColumn("i3", &i3, true))
null = Must(s.NamedScanColumn("i3", &i3 /*, true*/ ))
if null {
t.Errorf("Expected not null value")
} else if i3 != 0 {
......
package sqlite_test
import (
"fmt"
//"fmt"
. "github.com/gwenn/gosqlite"
"testing"
)
func trace(d interface{}, t string) {
fmt.Printf("%s: %s\n", d, t)
//fmt.Printf("%s: %s\n", d, t)
}
func authorizer(d interface{}, action Action, arg1, arg2, arg3, arg4 string) Auth {
fmt.Printf("%s: %d, %s, %s, %s, %s\n", d, action, arg1, arg2, arg3, arg4)
//fmt.Printf("%s: %d, %s, %s, %s, %s\n", d, action, arg1, arg2, arg3, arg4)
return AUTH_OK
}
func profile(d interface{}, sql string, nanoseconds uint64) {
fmt.Printf("%s: %s = %d\n", d, sql, nanoseconds/1000)
//fmt.Printf("%s: %s = %d\n", d, sql, nanoseconds/1000)
}
func progressHandler(d interface{}) int {
fmt.Print("+")
//fmt.Print("+")
return 0
}
......
// 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
/*
#include <sqlite3.h>
#include <stdlib.h>
*/
import "C"
/*
import (
"fmt"
"os"
"unsafe"
)
*/
// Calls sqlite3_column_count and sqlite3_column_(blob|double|int|int64|text) depending on columns type.
// http://sqlite.org/c3ref/column_blob.html
/*
func (s *Stmt) ScanNamedValues(values ...NamedValue) os.Error {
n := s.ColumnCount()
if n != len(values) { // What happens when the number of arguments is less than the number of columns?
return os.NewError(fmt.Sprintf("incorrect argument count for Stmt.ScanValues: have %d want %d", len(values), n))
}
for _, v := range values {
index, err := s.ColumnIndex(v.Name()) // How to look up only once for one statement ?
if err != nil {
return err
}
s.ScanValue(index, v)
}
return nil
}
*/
// Calls sqlite3_column_count and sqlite3_column_(blob|double|int|int64|text) depending on columns type.
// http://sqlite.org/c3ref/column_blob.html
/*
func (s *Stmt) ScanValues(values ...Value) os.Error {
n := s.ColumnCount()
if n != len(values) { // What happens when the number of arguments is less than the number of columns?
return os.NewError(fmt.Sprintf("incorrect argument count for Stmt.ScanValues: have %d want %d", len(values), n))
}
for i, v := range values {
s.ScanValue(i, v)
}
return nil
}
*/
// The leftmost column/index is number 0.
// Calls sqlite3_column_(blob|double|int|int64|text) depending on columns type.
// http://sqlite.org/c3ref/column_blob.html
/*
func (s *Stmt) ScanValue(index int) {
switch s.columnType(index) {
case C.SQLITE_NULL:
value.setNull(true)
case C.SQLITE_TEXT:
p := C.sqlite3_column_text(s.stmt, C.int(index))
n := C.sqlite3_column_bytes(s.stmt, C.int(index))
value.setText(C.GoStringN((*C.char)(unsafe.Pointer(p)), n))
case C.SQLITE_INTEGER:
value.setInt(int64(C.sqlite3_column_int64(s.stmt, C.int(index))))
case C.SQLITE_FLOAT:
value.setFloat(float64(C.sqlite3_column_double(s.stmt, C.int(index))))
case C.SQLITE_BLOB:
p := C.sqlite3_column_blob(s.stmt, C.int(index))
n := C.sqlite3_column_bytes(s.stmt, C.int(index))
value.setBlob((*[1 << 30]byte)(unsafe.Pointer(p))[0:n])
default:
panic("The column type is not one of SQLITE_INTEGER, SQLITE_FLOAT, SQLITE_TEXT, SQLITE_BLOB, or SQLITE_NULL")
}
}
*/
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment