Go MySql driver doesn't set time correctly - mysql

I have been developing a micro service that interact mysql for a while in golang, and i love this talented language. Anyway have a problem and do not know where is the problem, in my code, in mysql driver else in mysql. So my machine timezone utc+3, i am sharing some result may be it helps
//created_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
mysql> select now();
"2016-11-07 22:43:02", //that is correct.
in go
fmt.PrintLn(time.Now().Local())
"2016-11-07 22:51:02" //that is correct too
But when i added the entity into db, mysql workbench shows me wrong datetime.
"2016-11-07 19:51:02" //
Go code:
func (this *AppLogHandler) addLog(_log *AppLog) (int64, error){
fmt.Println("addLog")
db:= this.Context.DB
stmt, err := db.Prepare("INSERT tbl_logs SET user_id=?,ip_proxy=?, ip_original=?, end_point=?, http_method=?, message=?, status=?, created_date=?")
if(err != nil){
log.Println(err)
return -1, err
}
defer stmt.Close()
res, err := stmt.Exec(&_log.UserID, &_log.IPProxy, &_log.IPOriginal, &_log.Endpoint, &_log.HttpMethod, &_log.Message, &_log.Status, &_log.CreatedDate)
if(err != nil){
log.Println(err)
return -1, err
}
return res.LastInsertId()
}
/// some code here
app_log := AppLog{}
app_log.IPProxy = r.RemoteAddr
app_log.IPOriginal = r.Header.Get("X-Forwarded-For")
app_log.CreatedDate = time.Now().Local()
app_log.UserID = user_id
app_log.Endpoint = r.URL.Path
app_log.HttpMethod = r.Method
fmt.Println(app_log.CreatedDate)
return this.addLog(&app_log)
So guys i need your helps. I couldn't solve the problem for hours.
mysql=> Ver 14.14 Distrib 5.7.15, for osx10.11 (x86_64) using EditLine wrapper
go => 1.7
mysql driver => 1.2, https://github.com/go-sql-driver/mysql/

The mysql driver has a configuration parameter for the default time zone, which you can set to time.Local (the default is time.UTC). When you are saving the value, it first converts the time stamp to the UTC time zone and then sends it off to the database.
As has been already stated in the comments, a much more robust approach would be to accept the default Loc, and standardize on UTC in the database. This greatly simplifies anything having to do with date math further down the line, and doesn't make assumptions about the time zone of the person viewing the data if you just convert it from UTC to local when displaying the value.

Related

go mysql LAST_INSERT_ID() returns 0

I have this MySQL database where I need to add records with a go program and need to retrieve the id of the last added record, to add the id to another table.
When i run insert INSERT INTO table1 values("test",1); SELECT LAST_INSERT_ID() in MySQL Workbench, it returns the last id, which is auto incremented, with no issues.
If I run my go code however, it always prints 0. The code:
_, err := db_client.DBClient.Query("insert into table1 values(?,?)", name, 1)
var id string
err = db_client.DBClient.QueryRow("SELECT LAST_INSERT_ID()").Scan(&id)
if err != nil {
panic(err.Error())
}
fmt.Println("id: ", id)
I tried this variation to try to narrow down the problem scope further: err = db_client.DBClient.QueryRow("SELECT id from table1 where name=\"pleasejustwork\";").Scan(&id), which works perfectly fine; go returns the actual id.
Why is it not working with the LAST_INSERT_ID()?
I'm a newbie in go so please do not go hard on me if i'm making stupid go mistakes that lead to this error :D
Thank you in advance.
The MySQL protocol returns LAST_INSERT_ID() values in its response to INSERT statements. And, the golang driver exposes that returned value. So, you don't need the extra round trip to get it. These ID values are usually unsigned 64-bit integers.
Try something like this.
res, err := db_client.DBClient.Exec("insert into table1 values(?,?)", name, 1)
if err != nil {
panic (err.Error())
}
id, err := res.LastInsertId()
if err != nil {
panic (err.Error())
}
fmt.Println("id: ", id)
I confess I'm not sure why your code didn't work. Whenever you successfully issue a single-row INSERT statement, the next statement on the same database connection always has access to a useful LAST_INSERT_ID() value. This is true whether or not you use explicit transactions.
But if your INSERT is not successful, you must treat the last insert ID value as unpredictable. (That's a technical term for "garbage", trash, rubbish, basura, etc.)

How to avoid race conditions in GORM

I am developing a system to enable patient registration with incremental queue number. I am using Go, GORM, and MySQL.
An issue happens when more than one patients are registering at the same time, they tend to get the same queue number which it should not happen.
I attempted using transactions and hooks to achieve that but I still got duplicate queue number. I have not found any resource about how to lock the database when a transaction is happening.
func (r repository) CreatePatient(pat *model.Patient) error {
tx := r.db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
err := tx.Error
if err != nil {
return err
}
// 1. get latest queue number and assign it to patient object
var queueNum int64
err = tx.Model(&model.Patient{}).Where("registration_id", pat.RegistrationID).Select("queue_number").Order("created_at desc").First(&queueNum).Error
if err != nil && err != gorm.ErrRecordNotFound {
tx.Rollback()
return err
}
pat.QueueNumber = queueNum + 1
// 2. write patient data into the db
err = tx.Create(pat).Error
if err != nil {
tx.Rollback()
return err
}
return tx.Commit().Error
}
As stated by #O. Jones, transactions don't save you here because you're extracting the largest value of a column, incrementing it outside the db and then saving that new value. From the database's point of view the updated value has no dependence on the queried value.
You could try doing the update in a single query, which would make the dependence obvious:
UPDATE patient AS p
JOIN (
SELECT max(queue_number) AS queue_number FROM patient WHERE registration_id = ?
) maxp
SET p.queue_number = maxp.queue_number + 1
WHERE id = ?
In gorm you can't run a complex update like this, so you'll need to make use of Exec.
I'm not 100% certain the above will work because I'm less familiar with MySQL transaction isolation guarantees.
A cleaner way
Overall, it'd be cleaner to keep a table of queues (by reference_id) with a counter that you update atomically:
Start a transaction, then
SELECT queue_number FROM queues WHERE registration_id = ? FOR UPDATE;
Increment the queue number in your app code, then
UPDATE queues SET queue_number = ? WHERE registration_id = ?;
Now you can use the incremented queue number in your patient creation/update before transaction commit.

mysql complains at syntax from go driver

I'm using the github.com/go-sql-driver/mysql and mysql 5.7.10. I have a function:
bulkSetStatus := func(docVers []*_documentVersion) error {
if len(docVers) > 0 {
query := strings.Repeat("CALL documentVersionSetStatus(?, ?); ", len(docVers))
args := make([]interface{}, 0, len(docVers)*2)
for _, docVer := range docVers {
args = append(args, docVer.Id, docVer.Status)
}
_, err := db.Exec(query, args...)
return err
}
return nil
}
which works if len(docVers) == 1 but when there are more, resulting in multiple CALLs to the stored procedure, it errors:
Error 1064: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'CALL documentVersionSetStatus(?, ?)' at line 1
I have also tried a newline character between each call but I get the same error. If I run this in mysql workbench with multiple CALLs to this procedure it works fine, I'm not sure what is wrong with the syntax here.
I have logged out the exact full text with the arguments and it is as expected:
CALL documentVersionSetStatus("9c71cac14a134e7abbc4725997d90d2b", "inprogress"); CALL documentVersionSetStatus("beb65318da96406fa92990426a279efa", "inprogress");
go-sql-driver, by default, does not allow you to have multiple statements in one query (as you are doing by chaining together multiple CALL statements like that) due to the security implications if an attacker manages to perform SQL injection (for example, by injecting 0 OR 0; DROP TABLE foo).
To allow this, you must explicitly enable it by passing multiStatements parameter when connecting to the database, e.g.
db, err := sql.Open("mysql", "user:password#/dbname?multiStatements=True")
Source: https://github.com/go-sql-driver/mysql#multistatements
I have fixed the proc call by doing some manual string interpolation for the parameters instead of using the correct ? way of doing it:
bulkSetStatus := func(docVers []*_documentVersion) error {
if len(docVers) > 0 {
query := strings.Repeat("CALL documentVersionSetStatus(%q, %q); ", len(docVers))
args := make([]interface{}, 0, len(docVers)*2)
for _, docVer := range docVers {
args = append(args, docVer.Id, docVer.Status)
}
_, err := db.Exec(fmt.Sprintf(query, args...))
return err
}
return nil
}
so I swap out the ? for %q and us fmt.Sprintf to inject the parameters, I should note that slugonamission's answer is partially correct, I did need to add the connection string parameter multiStatements=true in order to get this to work with my other changes. I will log an issue on the github repo it looks like there may be some param interpolation issue when there is more than one statement, I think the error was happening because the mysql db was trying to run the script with ? literals in it.

Golang query multiple databases with a JOIN

Using the golang example below, how can I query (JOIN) multiple databases.
For example, I want to have the relation db1.username.id = db2.comments.username_id.
id := 123
var username string
err := db.QueryRow("SELECT username FROM users WHERE id=?", id).Scan(&username)
switch {
case err == sql.ErrNoRows:
log.Printf("No user with that ID.")
case err != nil:
log.Fatal(err)
default:
fmt.Printf("Username is %s\n", username)
}
As you are using MySQL, you can select fields across databases. See this related question for
details. For example you should be able to do this:
err := db.QueryRow(`
SELECT
db1.users.username
FROM
db1.users
JOIN
db2.comments
ON db1.users.id = db2.comments.username_id
`).Scan(&username)
You can of course simply fetch all entries from db2.comments using a second database connection and use the values in a query to db1.users. This is, of course, not recommended as it is the job of the database server which it can, most likely, do better than you.

Go - Connecting to a external mysql database

I'm rather new to use go and am having issues connecting to an external mysql database.
I'm using the go-sql-driver which seams rather nice. Suggestions to other drivers are welcomed!
this is the whole program:
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
"fmt"
)
const (
DB_HOST = "tcp(http://thedburl.com)"
DB_NAME = "nameofdatabase"
DB_USER = "username"
DB_PW = "password"
)
func main() {
dsn := DB_USER + ":" + DB_PW + "#" + DB_HOST + "/" + DB_NAME + "?charset=uf8"
db, err := sql.Open("mysql", dsn)
if err != nil {
fmt.Println("shiiet didn't work yo! Initialization failed")
}
defer db.Close() // go's purty cool
var str string
q := "SELECT * FROM forums"
err = db.QueryRow(q).Scan(&str)
if err != nil {
fmt.Println(err)
}
fmt.Println(str)
}
On the request I'm recieving the following error
"GetAddrInfoW: The specified class was not found."
Any ideas? I've siting for hours on the webs, and can't seem to solve the problem.
It might be worth noting that I have used the same database service many times in java.
Interestingly, this error can be also caused by spaces in the server port as in server.Run(":8080 ") vs server.Run(":8080")
Thanks for everyones answers.
The mysql drivers for Go are at the current time having a hard time dealing with older mysql versions. Specifically this issue happened due to the incompatibility of mysql's old_password from 2006. Therefore working with older databases is a pain. (source: https://github.com/go-sql-driver/mysql/wiki/old_passwords) - My educated guess also applies to the mymysql driver whom throws the "bad connection" error. Which basicly times out after repetitive attempts of connection.
Special thanks to #Lepidosteus who's answer made me discover the real problem.