Closed hmyrie closed 2 weeks 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
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).
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.
I think it is logical mistake, not a driver's bug. @hmyrie Please send me a pull request for the failing test code.
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:
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"}
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
A stripped down version of the program, along with the database, is attached. DB was created using Firebird 3.0.
Close because the version of go that we support has been upgraded
What version of Go are you using (
go version
)?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
OutputWhat did you do?
I upgraded to go version 1.16 from version 1.14
See sample of code below;
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.