nakagami / firebirdsql

Firebird RDBMS sql driver for Go (golang)
MIT License
222 stars 60 forks source link

Error after upgrading from Go1.14 to Go1.16 #123

Closed hmyrie closed 2 weeks ago

hmyrie commented 3 years ago

What version of Go are you using (go version)?

$ go version
version 1.16

Does this issue reproduce with the latest release?

Yes. Worked under the previous version Go1.14.

Error started after upgrade to new version 1.16.

What operating system and processor architecture are you using (go env)?

Running on Windows 10 Pro. x64-based processor.

go env Output
$ go env
set GO111MODULE=
set GOARCH=amd64
set GOBIN=
set GOCACHE=C:\Users\ictcon\AppData\Local\go-build
set GOENV=C:\Users\ictcon\AppData\Roaming\go\env
set GOEXE=.exe
set GOFLAGS=
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GOINSECURE=
set GOMODCACHE=C:\Users\ictcon\go\pkg\mod
set GONOPROXY=
set GONOSUMDB=
set GOOS=windows
set GOPATH=C:\Users\ictcon\go
set GOPRIVATE=
set GOPROXY=https://proxy.golang.org,direct
set GOROOT=C:\Program Files\Go
set GOSUMDB=sum.golang.org
set GOTMPDIR=
set GOTOOLDIR=C:\Program Files\Go\pkg\tool\windows_amd64
set GOVCS=
set GOVERSION=go1.16.5
set GCCGO=gccgo
set AR=ar
set CC=gcc
set CXX=g++
set CGO_ENABLED=1
set GOMOD=C:\Go\AutoRx\go.mod
set CGO_CFLAGS=-g -O2
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-g -O2
set CGO_FFLAGS=-g -O2
set CGO_LDFLAGS=-g -O2
set PKG_CONFIG=pkg-config
set GOGCCFLAGS=-m64 -mthreads -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=C:\Users\ictcon\AppData\Local\Temp\go-build3578739727=/tmp/go-build -gno-record-gcc-switches

What did you do?

I upgraded to go version 1.16 from version 1.14

See sample of code below;

func getErrCodes(w http.ResponseWriter, r *http.Request) {

    type Codes struct {
        Description  string  `json:"description"`
        Code   NullString  `json:"code"`
    }
    codes := []Codes{}

    conn, _ := sql.Open("firebirdsql", datapath)
    defer conn.Close()
    rows, err := conn.Query("select description, code from rcodes")
    if err != nil {
        respondWithError(w, http.StatusBadRequest, err.Error())
        logError(err.Error())
        return
    }
    defer rows.Close()
    for rows.Next() {
        var c Codes
        err := rows.Scan(&c.Description, &c.Code)
        if err != nil {
            respondWithError(w, http.StatusBadRequest, err.Error())
            logError(err.Error())
            return
        }
        codes = append(codes, c)
    }
    err = rows.Err()   // <-- error occurs here.
    if err != nil {
        respondWithError(w, http.StatusBadRequest, err.Error())
        logError(err.Error())
        return
    }
    respondWithJSON(w, http.StatusOK, codes)

}

What did you expect to see?

I expected to see the result set being returned from the database.

What did you see instead?

Getting the error (for all select query):

{"error":"Dynamic SQL Error\nSQL error code = -303\narithmetic exception, numeric overflow, or string truncation\nstring right truncation\n"}

Note: If the Row.Err() block is commented out, an empty array is returned.

I am using Firebird 2.5.

nakagami commented 3 years ago

In github actions, I only tested it with go 1.5 until a while ago! I've just changed it to test with go1.5 and go1.6. https://github.com/nakagami/firebirdsql/commit/1d73435bd59e03ecc2dd70596b59bf67db9888bf

It passes the test on go1.6 as well. https://github.com/nakagami/firebirdsql/actions

hmyrie commented 3 years ago

OK. Any idea what may be going on with my specific issue? Using Firebird 3.0 I get additional information: "string right truncation /expected length 0, actual 3". In the DB the Code field is varchar(3) and Description is Varchar(30).

rolandm commented 3 years ago

There is a commit, PR and merge on 21.06. that updates the go.mod file to use go 1.15 instead of go 1.13. However, the latest release v0.9.1 of firebirdsql is about a month old and still uses go 1.13 for build.

nakagami commented 3 years ago

I think it is logical mistake, not a driver's bug. @hmyrie Please send me a pull request for the failing test code.

hmyrie commented 3 years ago

I uninstalled Go, and did a fresh installation of Go1.14.15. I have created a new Firebird Database with One Table.

/***** ROLES **/

CREATE ROLE RDB$ADMIN; /* UDFS ***/

/** GENERATORS ****/

/**** DOMAINS *****/

CREATE DOMAIN SEC$KEY AS Varchar(10) CHARACTER SET UNICODE_FSS NOT NULL COLLATE UNICODE_FSS; CREATE DOMAIN SEC$NAME_PART AS Varchar(10) CHARACTER SET UNICODE_FSS NOT NULL COLLATE UNICODE_FSS; CREATE DOMAIN SEC$USER_NAME AS Varchar(10) CHARACTER SET UNICODE_FSS NOT NULL COLLATE UNICODE_FSS; CREATE DOMAIN SEC$VALUE AS Varchar(85) CHARACTER SET UNICODE_FSS NOT NULL COLLATE UNICODE_FSS; /*** PROCEDURES **/

/**** TABLES **/

CREATE TABLE TESTR ( CODE Varchar(5) NOT NULL, DESCRIPTION Varchar(30) NOT NULL ); /***** VIEWS **/

/ EXCEPTIONS /

/**** TRIGGERS ****/

GRANT DELETE, INSERT, REFERENCES, SELECT, UPDATE ON TESTR TO SYSDBA WITH GRANT OPTION;

/==============================/ The Data.

insert into testr (code, description) values ('123', 'Data 1'); insert into testr (code, description) values ('163', 'Data 5'); insert into testr (code, description) values ('653', 'Data 2');

/==============================/ The Go File

package main

import ( "net/http" "database/sql" "fmt" "log" "encoding/json" "io/ioutil" "gopkg.in/ini.v1" "path/filepath" "os" "os/exec" "strings" // "reflect" // "time" "strconv" "net" // "html/template" "runtime" _ "github.com/nakagami/firebirdsql" "github.com/gorilla/mux" "github.com/rs/cors" // "github.com/getlantern/systray" ) var ( timezone string )

var datapath = "" var port = "80" var smtp = "smtp.yahoo.com" var mailSender = "htest@yahoo.com" var mailUser = "htest@yahoo.com" var mailPswd = "" var autoLoad = true

func getParams() { pathx, err := os.Getwd() //Executable() if err != nil { log.Println(err) } fmt.Println(pathx)

conf := pathx+"\\config.ini"

cfg, err :=  ini.Load (conf) // initialize a CFG
if err != nil {
    fmt.Printf("Fail to read file: %v", err)
    str := "cannot find the path specified"
    errors := err.Error();
    if strings.Contains(errors, str) {
        newParams()
     }
     os.Exit(1)
}
yes := cfg.Section("Data").HasKey("Hostname")
if yes {
    key := cfg.Section("Data").Key("Hostname").String()
    datapath = key
//  fmt.Printf("Hostname: %s", key)
}
prt := cfg.Section("Data").HasKey("Port")
if prt {
    pr := cfg.Section("Data").Key("Port").String()
    port = pr
//  fmt.Printf("Port: %s", pr)
}
email := cfg.Section("Email").HasKey("smtp")
if email {
    smp := cfg.Section("Email").Key("smtp").String()
    smtp = smp
    sdr := cfg.Section("Email").Key("sender").String()
    mailSender = sdr
    usr := cfg.Section("Email").Key("user").String()
    mailUser = usr
    pwd := cfg.Section("Email").Key("pass").String()
    mailPswd = pwd
//  fmt.Printf("Port: %s", pr)
}
isClient := cfg.Section("Data").HasKey("client")
if isClient {
    key := cfg.Section("Data").Key("Client-computer").String()
    autoLoad = true
    if key == "No" {
        autoLoad = false
    }
//  fmt.Printf("Hostname: %s", key)
}

}

func newParams() { // type Data struct { // Hostname string // Port int // } cfg := ini.Empty () sec, err := cfg.NewSection("Data") if err != nil { fmt.Printf("Fail to create section: %v", err) } cfg.Section("Data").NewKey("Hostname", datapath) cfg.Section("Data").NewKey("Port", port) cfg.Section("").Key("app_mode").SetValue("production") fmt.Println(sec)

nxt, err := cfg.NewSection("Email")
if err != nil {
    fmt.Printf("Fail to create section: %v", err)
}
cfg.Section("Email").NewKey("smtp",  "smtp.example.com")
cfg.Section("Email").NewKey("sender",  "user@mail.com")
cfg.Section("Email").NewKey("usr",  "user@mail.com")
cfg.Section("Email").NewKey("pass",  "abc123")
fmt.Println(nxt)
cfg.SaveTo("config.ini")

}

// Get preferred outbound ip of this machine func GetOutboundIP() net.IP { conn, err := net.Dial("udp", "8.8.8.8:80") if err != nil { log.Fatal(err) } defer conn.Close()

localAddr := conn.LocalAddr().(*net.UDPAddr)

return localAddr.IP

}

func portAvailable(ported string) bool { i, err := strconv.Atoi(ported) conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%v", i))

if err != nil {
    if strings.Index(err.Error(), "connection refused") > 0 {
        fmt.Printf("Port %s is unavilable. \n", ported);
        return true
    }
    return false
}

conn.Close()
return false

}

type spaHandler struct { staticPath string indexPath string }

type Exit struct{ Code int }

func (h spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {

// get the absolute path to prevent directory traversal
path, err := filepath.Abs(r.URL.Path)
if err != nil {
    // if we failed to get the absolute path respond with a 400 bad request
    // and stop
    log.Println("Err Path: %s", path)
    http.Error(w, err.Error(), http.StatusBadRequest)
    return
}
dir, err := os.Getwd()
if err != nil {
    log.Println("Err Path: %s", path)
    http.Error(w, err.Error(), http.StatusBadRequest)
    return
}
dir = filepath.Join(dir, h.staticPath)

// log.Printf("absolute Path: %s \n", path) // path2 := filepath.Dir(r.URL.Path) // log.Printf("Directory: %s \n", dir) // log.Printf("URL: %s \n", r.URL.Path)

// prepend the path with the path to the static directory

// path = filepath.Join(path, h.staticPath) path = filepath.Join(dir, "")

// log.Printf("Joined Path: %s \n", path) // log.Printf("Relative Path: %s \n", path2) // check whether a file exists at the given path _, err = os.Stat(path) // log.Println("Info: %s", info) if os.IsNotExist(err) { // file does not exist, serve index.html http.ServeFile(w, r, filepath.Join(h.staticPath, h.indexPath)) return } else if err != nil { // if we got an error (that wasn't that the file doesn't exist) stating the // file, return a 500 internal server error and stop http.Error(w, err.Error(), http.StatusInternalServerError) return }

// otherwise, use http.FileServer to serve the static dir
http.FileServer(http.Dir("automated")).ServeHTTP(w, r)

}

// open opens the specified URL in the default browser of the user. func open(url string) error { var cmd string var args []string

switch runtime.GOOS {
case "windows":
    cmd = "cmd"
    args = []string{"/c", "start"}
case "darwin":
    cmd = "open"
default: // "linux", "freebsd", "openbsd", "netbsd"
    cmd = "xdg-open"
}
args = append(args, url)
return exec.Command(cmd, args...).Start()

}

func onReady() { timezone = "Local"

// ip := GetOutboundIP() // portAvailable(port) // uri := "http://"+ip.String()+":"+port

// systray.SetIcon(getIcon("assets/server.ico")) // systray.SetTitle("Rx Running!") // systray.SetTooltip("Rx Server! |"+uri)

// sfTime := systray.AddMenuItem("Automated Test", "America/Los_Angeles") // systray.AddSeparator() // mQuit := systray.AddMenuItem("Quit", "Quits this app")

routeServe()

/
go func() { for { select { case <-sfTime.ClickedCh: timezone = "America/Los_Angeles" case <-mQuit.ClickedCh: systray.Quit() os.Exit(0) return } } }()
/ }

func onExit() { // Cleaning stuff here. panic(Exit{3}) //handleExit() }

func getIcon(s string) []byte { b, err := ioutil.ReadFile(s) if err != nil { fmt.Print(err) } return b }

// exit code handler func handleExit() { if e := recover(); e != nil { if exit, ok := e.(Exit); ok == true { os.Exit(exit.Code) } panic(e) // not an Exit, bubble up } }

func routeServe() { r := mux.NewRouter() testR := r.PathPrefix("/test").Subrouter() testR.Path("").Methods(http.MethodGet).HandlerFunc(getTest)

c := cors.New(cors.Options{
    AllowedOrigins: []string{"*"},
    AllowedMethods: []string{"GET", "PUT", "POST", "DELETE"},
    AllowCredentials: true,
})
handler := c.Handler(r)

spa := spaHandler{staticPath: "automated", indexPath: "index.html"}
r.PathPrefix("/{_:.*}").Handler(spa)    

ip := GetOutboundIP()
portAvailable(port)
uri := "http://"+ip.String()+":"+port
if autoLoad {
    open(uri)
}
timezone = uri
fmt.Printf("Start listening on %s // %s \n", ip, port)
http.ListenAndServe(":"+port, handler)

}
func getHandler() { var n int conn, _ := sql.Open("firebirdsql", datapath) defer conn.Close() conn.QueryRow("SELECT Count(*) FROM rdb$relations").Scan(&n) fmt.Println("Relations count=", n) }

func main() { getParams() // defer handleExit()

// ip := GetOutboundIP() // portAvailable(port) // uri := "http://"+ip.String()+":"+port

routeServe()

// systray.Run(onReady, onExit)

//-----------------------------------------------

}

func getTest(w http.ResponseWriter, r *http.Request) {

type test struct {
    Code  string  `json:"code"`
    Description   string  `json:"description"`
}
tests := []test{}

conn, _ := sql.Open("firebirdsql", datapath)
defer conn.Close()
rows, err := conn.Query("select code, description from testr")
if err != nil {
    respondWithError(w, http.StatusBadRequest, err.Error())
    logError(err.Error())
    return
}

defer rows.Close()
for rows.Next() {
    var t test
    err := rows.Scan(&t.Code, &t.Description)
    if err != nil {
        respondWithError(w, http.StatusBadRequest, err.Error())
        logError(err.Error())
        return
    }
    tests = append(tests, t)
}
err = rows.Err()
if err != nil {
    respondWithError(w, http.StatusBadRequest, err.Error())
    logError(err.Error())
    return
}
respondWithJSON(w, http.StatusOK, tests)

}

func respondWithError(w http.ResponseWriter, code int, message string) { respondWithJSON(w, code, map[string]string{"error": message}) }

func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) { response, _ := json.Marshal(payload)

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
w.Write(response)

}

func logError(ermsg string) { f, err := os.OpenFile("automated.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { log.Println(err) } defer f.Close()

logger := log.New(f, "prefix", log.LstdFlags)
logger.Println(ermsg)

}

/++++++++++++++++++/

Configuration File

app_mode = production

[Data] Hostname = SYSDBA:masterkey@localhost:3050/rxpath2 Port = 8080 Client-computer = No [Email] smtp = smtp-mail.outlook.com sender = test@hotmail.com user = test@hotmail.com pass = passw

Command to test:

http://192.168.0.13:8080/test

Error received:

{"error":"Dynamic SQL Error\nSQL error code = -303\narithmetic exception, numeric overflow, or string truncation\nstring right truncation\nexpected length 1, actual 5\n"}

image

nakagami commented 3 years ago

Umm, this error occur in Firebird server, and I don't think it depends on the behavior of the driver

Sorry please write a reproducible unittest. https://github.com/nakagami/firebirdsql/blob/master/driver_test.go

hmyrie commented 3 years ago

testKit.zip

A stripped down version of the program, along with the database, is attached. DB was created using Firebird 3.0.

nakagami commented 2 weeks ago

Close because the version of go that we support has been upgraded