NOTE
sql
1. What it is 2. What it contains 2.1. Connections 2.2. CRUD 2.3. Transactions 3. Prepared statements 4. Other libraries 4.1. sql-builder 4.2. xorm 5. References
This is a historical learning note and may contain outdated or incomplete understanding.
1. What It Is
2. What It Contains
2.1. Connections
DBis safe for concurrent use.func Open(driverName, dataSourceName string) (*DB, error):Openonly validates the connection-string format; it does not open a connection. UsePing.func (db *DB) Ping() error: creates a connection when needed and verifies that the connection is alive.- 0 < MaxidleConnection < MaxOpenConnection.
func (db *DB) Close() errorcloses a connection and waits for all queries on that connection to finish. It is rarely used.
2.2. CRUD
func (db *DB) Exec(query string, args ...interface{}) (Result, error)executes CUD SQL. The second parameter corresponds to placeholders and should be passed withargs.... It returnsResult, which wrapslastInsertedIdandrowsAffected.func (db *DB) QueryRow(query string, args ...interface{}) *Rowqueries at most one row. It returns a non-nilRow, butRow.Scanmust be called. If multiple rows exist, only the first is used; if none exist, an error is returned.func (db *DB) Query(query string, args ...interface{}) (*Rows, error)queries multiple rows and returns a non-nilRows. CallNextto check whether another row exists, thenRows.Scanto copy columns into destination values.- During
Scan, if one field is wrong, the later fields are set to zero values. Scancannot directly handle NULL values; fields need types such assql.NullInt64.
- During
func (db *DB) Prepare(query string) (*Stmt, error)prepares SQL and returns aStmt, which must be closed.
2.3. Transactions
func (db *DB) Begin() (*Tx, error)starts a transaction and returns aTx. The default isolation level depends on the driver.func (tx *Tx) Commit() errorcommits the transaction.func (tx *Tx) Rollback() errorrolls back the transaction.Txalso hasQueryRow,Query, andExecmethods.
3. Prepared Statements
SQL preparation has two purposes: preventing SQL injection and improving performance through caching.
The reason it can prevent injection is that after compilation the syntax tree is fixed and cannot be changed, so injected syntax cannot alter it.
The performance benefit comes from the database not needing to repeatedly parse SQL and generate execution plans. This requires support from the client-side driver library for caching.
The go-sql-driver parameter InterpolateParams defaults to false. In this mode, prepared statements are enabled and there are three interactions with the server. If it is true, placeholders (?) in the SQL are directly replaced with concrete values. This requires only one interaction with the server, but there is no server-side preparation, so injection can become a concern. go-sql-driver escapes values on the client side, which addresses part of the problem, although multi-encoding environments can still be problematic. Bytes are UTF-8 by default, so this issue normally does not occur; GORM therefore sets this value to true by default.
go-sql-driver itself does not cache prepared statements. GORM provides this through PrepareStmt. When PrepareStmt=true, GORM uses go-sql-driver to prepare the SQL and then caches the prepared statement locally so that it can be reused next time.
4. Other Libraries
4.1. sql-builder
package sqlbuilder_demo
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/huandu/go-sqlbuilder"
"time"
)
var db *sql.DB
var userStruct = sqlbuilder.NewStruct(new(TbUser))
//type TbUser struct {
// Id sql.NullInt64 `db:"id"`
// Username string `db:"username"`
// Password string `db:"password"`
// Level sql.NullInt64 `db:"level"`
// Created int64 `db:"created"`
// Updated int64 `db:"updated"`
//
//}
type TbUserVo struct {
Collection string
Created int64
Email string
Id int64
Level int64
Password string
Perms string
Phone string
Updated int64
Username string
}
type TbUser struct {
Collection sql.NullString `db:"collection"`
Created time.Time `db:"created"`
Email sql.NullString `db:"email"`
Id int64 `db:"id"`
Level sql.NullInt64 `db:"level"`
Password string `db:"password"`
Perms sql.NullString `db:"perms"`
Phone sql.NullString `db:"phone"`
Updated time.Time `db:"updated"`
Username string `db:"username"`
}
func SelectDemo() {
selectBuilder := sqlbuilder.NewSelectBuilder()
selectBuilder.Select("id", "username")
selectBuilder.From("tb_user")
selectBuilder.Where(selectBuilder.In("phone", "<PHONE_1>", "<PHONE_2>"))
sql, args := selectBuilder.Build()
fmt.Println(sql, args)
}
func UserCountSql() {
builder := sqlbuilder.NewSelectBuilder()
builder.Select("count(*)")
builder.From("tb_user")
builder.Where(builder.GreaterThan("id", 1))
sql, args := builder.Build()
fmt.Println(sql, args)
row := db.QueryRow(sql, args...)
var count int
err := row.Scan(&count)
if err != nil {
fmt.Println(err)
}
fmt.Println(count)
}
func UserListSql() {
// Query all fields by default.
builder := userStruct.SelectFrom("tb_user")
builder.Where(builder.GreaterThan("id", 10))
sql, args := builder.Build()
fmt.Println(sql, args)
rows, err := db.Query(sql, args...)
if err != nil {
fmt.Println(err)
return
}
users := make([]TbUser, 0)
for rows.Next() {
var user TbUser
// Use reflection to obtain all fields.
err = rows.Scan(userStruct.Addr(&user)...)
//err = rows.Scan(&user.Id, &user.Level,&user.Password)
if err != nil {
fmt.Println(err)
continue
}
users = append(users, user)
}
fmt.Println(users)
}
func UserInsertSql(user TbUser) {
// No need to specify columns and values.
builder := userStruct.InsertInto("tb_user", user)
// They can also be specified explicitly.
//builder.Cols("id", "username", "password", "created", "updated")
//builder.Values(user.Id, user.Username, user.Password, user.Created, user.Updated)
sql, args := builder.Build()
fmt.Println(sql, args)
result, err := db.Exec(sql, args...)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(result.LastInsertId())
}
func UserDeleteSql() {
builder := userStruct.DeleteFrom("tb_user")
builder.Where(builder.Equal("id", 1))
sql, args := builder.Build()
fmt.Println(sql, args)
result, err := db.Exec(sql, args...)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(result.RowsAffected())
}
func UserUpdateSqlSelective(userVo TbUserVo) {
updateBuilder := sqlbuilder.NewUpdateBuilder()
updateBuilder.Update("tb_user")
updateBuilder.SetMore(updateBuilder.Assign("email", sql.NullString{
String: userVo.Email,
Valid: true,
}))
if userVo.Collection != "" {
updateBuilder.SetMore(updateBuilder.Assign("collection", sql.NullString{
String: userVo.Collection,
Valid: true,
}))
}
if userVo.Level != 0 {
updateBuilder.SetMore(updateBuilder.Assign("level", sql.NullInt64{
Int64: userVo.Level,
Valid: true,
}))
}
if userVo.Password != "" {
updateBuilder.SetMore(updateBuilder.Assign("password", userVo.Password))
}
if userVo.Phone != "" {
updateBuilder.SetMore(updateBuilder.Assign("phone", sql.NullString{
String: userVo.Phone,
Valid: true,
}))
}
if userVo.Perms != "" {
updateBuilder.SetMore(updateBuilder.Assign("perms", sql.NullString{
String: userVo.Perms,
Valid: true,
}))
}
updateBuilder.SetMore(updateBuilder.Assign("updated", time.Now()))
updateBuilder.SetMore(updateBuilder.Assign("created", time.Now()))
updateBuilder.Where(updateBuilder.Equal("id", userVo.Id))
sql, args := updateBuilder.Build()
fmt.Println(sql, args)
result, err := db.Exec(sql, args...)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(result.RowsAffected())
}
func UserUpdateSql(user TbUser) {
builder := userStruct.Update("tb_user", user)
builder.Where(builder.Equal("id", 666))
sql, args := builder.Build()
fmt.Println(sql, args)
result, err := db.Exec(sql, args...)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(result.RowsAffected())
}
func TestTx() {
// Add a timeout.
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(2)*time.Second)
defer cancel()
// Start the transaction.
tx, err := db.BeginTx(ctx, nil)
if err != nil {
fmt.Println(err)
}
_, err = tx.Exec("insert into ttest (status, creater, created) values (?, ?, ?)", "test", "user1", time.Now())
if err != nil {
fmt.Println(err)
tx.Rollback()
return
}
_, err = tx.Exec("insert into ttest (status, creater, created) values (?, ?, ?)", "test2", "user2", time.Now())
if err != nil {
fmt.Println(err)
tx.Rollback()
return
}
// Simulate other work that does not execute SQL.
// If it times out, it is automatically rolled back.
// Do not put time-consuming tasks inside a transaction, because the connection cannot be released.
time.Sleep(time.Duration(5) * time.Second)
if err := tx.Commit(); err != nil {
fmt.Println(err)
tx.Rollback()
}
// A committed transaction cannot be committed or rolled back again.
//if err := tx.Commit(); err != nil {
// fmt.Println(err)//sql: transaction has already been committed or rolled back
// if err := tx.Rollback(); err != nil {
// fmt.Println(err)//sql: transaction has already been committed or rolled back
// }
//}
}
func init() {
var err error
db, err = sql.Open("mysql", "root:<PASSWORD>@(127.0.0.1:3306)/tao?charset=utf8")
if err != nil {
panic(err)
}
}
4.2. xorm
4.2.1. reverse tool
4.2.1.1. Installation
go get xorm.io/reverse
go get github.com/go-sql-driver/mysql
4.2.1.2. Usage
- Configuration file
tao.yml
kind: reverse
name: mydb
source:
database: mysql
conn_str: 'root:<PASSWORD>@(127.0.0.1:3306)/tao?charset=utf8'
targets:
- type: codes
language: golang
output_dir: ./
- Command
cd models
reverse -f tao.yml
5. References
- huandu/go-sqlbuilder: A flexible and powerful SQL string builder library plus a zero-config ORM.
- Ways to Work with MySQL in Go | Qifengle
- database - SetMaxOpenConns and SetMaxIdleConns - Stack Overflow
- Using xorm to Generate Go Code from a Database - artong0416 - CNBlogs
- Xorm
- xorm/README_CN.md at master · go-xorm/xorm
- Go: Handling NULL Values in Databases - CSDN Blog
- A Brief Look at Golang Database Programming - Juejin
- Golang MySQL Notes (4): Transactions - Jianshu
- xorm/reverse: A flexsible and powerful command line tool to convert database to codes - README_CN.md at master - reverse - Gitea: Git with a cup of tea
- Xorm
- GitHub - go-sql-driver/mysql: Go MySQL Driver is a MySQL driver for Go’s (golang) database/sql package
- Performance | GORM - The fantastic ORM library for Golang, aims to be developer friendly.
- DBResolver | GORM - The fantastic ORM library for Golang, aims to be developer friendly.
- postgresql - What’s the difference between these two Gorm way of query things? - Stack Overflow
- How Go, Gorm, and MySQL Prevent SQL Injection - CSDN Blog
- A Deeper Understanding of database/sql | Youmai’s Blog
- GitHub - go-sql-driver/mysql: Go MySQL Driver is a MySQL driver for Go’s (golang) database/sql package
- Overview of Golang SQL Connection Pools - CNBlogs
- Why Can Prepared Statements Prevent SQL Injection? - Zhihu
Discussion
Sign in with GitHub to comment. Discussions are stored as GitHub Issues.View on GitHub