mirror of
https://github.com/gohouse/gorose.git
synced 2025-12-24 12:47:55 +08:00
add dialect for different database
This commit is contained in:
71
assets/gorose.drawio
Normal file
71
assets/gorose.drawio
Normal file
@@ -0,0 +1,71 @@
|
||||
<mxfile host="65bd71144e">
|
||||
<diagram id="nHGE5eSV5kb8BZl-27XF" name="第 1 页">
|
||||
<mxGraphModel dx="965" dy="767" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1654" pageHeight="2336" background="none" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
<mxCell id="22" value="" style="edgeStyle=none;html=1;exitX=0.386;exitY=0;exitDx=0;exitDy=0;exitPerimeter=0;" edge="1" parent="1" source="2" target="11">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="23" style="edgeStyle=none;html=1;exitX=0.909;exitY=-0.075;exitDx=0;exitDy=0;entryX=0.704;entryY=1.013;entryDx=0;entryDy=0;entryPerimeter=0;exitPerimeter=0;" edge="1" parent="1" source="2" target="9">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="2" value="Context<br>Select,Table,Join,Where,GroupBy,Having,OrderBy,LimitOffset" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="580" y="460" width="350" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="33" style="edgeStyle=none;html=1;exitX=0.75;exitY=0;exitDx=0;exitDy=0;entryX=0.75;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" source="7" target="10">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="1020" y="300" as="targetPoint"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="36" style="edgeStyle=none;html=1;exitX=0.25;exitY=0;exitDx=0;exitDy=0;" edge="1" parent="1" source="7">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="980" y="360" as="targetPoint"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="7" value="Engin<br>Query,Exec,Transaction,Log" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="940" y="390" width="160" height="38" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="24" value="" style="edgeStyle=none;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" target="7">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="1020" y="460" as="sourcePoint"/>
|
||||
<mxPoint x="1040" y="400" as="targetPoint"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="34" style="edgeStyle=none;html=1;entryX=0.844;entryY=1.113;entryDx=0;entryDy=0;entryPerimeter=0;exitX=0.643;exitY=0;exitDx=0;exitDy=0;exitPerimeter=0;" edge="1" parent="1" source="8" target="10">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="8" value="database/sql" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="940" y="460" width="280" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="35" style="edgeStyle=none;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.35;entryY=0.988;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="9" target="10">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="9" value="Database ORM API" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="580" y="320" width="450" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="10" value="GoRose V3" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="580" y="256" width="640" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="18" style="edgeStyle=none;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.3;entryY=1;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="11" target="9">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="11" value="Driver Dialect<br>Mysql,MsSQL,Postgresql,Oracle,Sqlite3..." style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="580" y="388" width="270" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="31" style="edgeStyle=none;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.938;entryY=1.013;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="12" target="10">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="12" value="Config" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="1140" y="320" width="80" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="30" style="edgeStyle=none;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" source="28" target="12">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="28" value="Master,Slave" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="1140" y="390" width="80" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
1
assets/gorose.svg
Normal file
1
assets/gorose.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 12 KiB |
@@ -27,8 +27,8 @@ type TypeToSqlUpdateCase struct {
|
||||
}
|
||||
|
||||
type TypeToSqlIncDecCase struct {
|
||||
Symbol string
|
||||
Data map[string]any
|
||||
Symbol string // +/-
|
||||
Data map[string]any // {count: 2} => count = count + 2
|
||||
}
|
||||
|
||||
type TypeToSqlInsertCase struct {
|
||||
@@ -38,3 +38,11 @@ type TypeToSqlInsertCase struct {
|
||||
UpdateFields []string
|
||||
MustColumn []string
|
||||
}
|
||||
|
||||
type TypeLock int8
|
||||
|
||||
const (
|
||||
TypeLockUnknown TypeLock = iota
|
||||
TypeLockInShareMode
|
||||
TypeLockForUpdate
|
||||
)
|
||||
|
||||
@@ -11,7 +11,7 @@ type Context struct {
|
||||
LimitOffsetClause LimitOffsetClause
|
||||
UnionClause UnionClause
|
||||
|
||||
PessimisticLocking string
|
||||
PessimisticLocking TypeLock
|
||||
Prefix string
|
||||
}
|
||||
|
||||
@@ -137,10 +137,10 @@ func (db *Context) Page(num int) *Context {
|
||||
return db
|
||||
}
|
||||
func (db *Context) SharedLock() *Context {
|
||||
db.PessimisticLocking = "LOCK IN SHARE MODE"
|
||||
db.PessimisticLocking = TypeLockInShareMode
|
||||
return db
|
||||
}
|
||||
func (db *Context) LockForUpdate() *Context {
|
||||
db.PessimisticLocking = "FOR UPDATE"
|
||||
db.PessimisticLocking = TypeLockForUpdate
|
||||
return db
|
||||
}
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func IsExpression(obj any) (b bool) {
|
||||
rfv := reflect.Indirect(reflect.ValueOf(obj))
|
||||
if rfv.Kind() == reflect.String && strings.Contains(rfv.String(), "?") {
|
||||
b = true
|
||||
}
|
||||
return
|
||||
}
|
||||
import "reflect"
|
||||
|
||||
func ToSlice(arg any) []any {
|
||||
ref := reflect.Indirect(reflect.ValueOf(arg))
|
||||
@@ -162,13 +162,13 @@ func (w *WhereClause) whereRaw(boolean string, sqlSeg string, bindingsAndBoolean
|
||||
// Examples:
|
||||
//
|
||||
// Where("id=1")
|
||||
// Where("id=?",1)
|
||||
// Where("id=?",1), 不支持
|
||||
// Where("id",1)
|
||||
// Where("id","=",1)
|
||||
// Where("id","=",1,"AND")
|
||||
// Where("id","=",(select id from table limit 1))
|
||||
// Where("id","in",(select id from table), "AND")
|
||||
// Where(func(wh iface.WhereClause){wh.Where().OrWhere().WhereRaw()...})
|
||||
// Where(func(wh builder.WhereClause){wh.Where().OrWhere().WhereRaw()...})
|
||||
// Where(["id=1"])
|
||||
// Where(["id","=",1])
|
||||
// Where(["id",1])
|
||||
@@ -226,9 +226,9 @@ func (w *WhereClause) where(boolean string, column any, args ...any) IWhere {
|
||||
w.Err = errors.New("not supported where params")
|
||||
}
|
||||
case 1:
|
||||
if IsExpression(column) {
|
||||
return w.whereRaw(boolean, column.(string), args...)
|
||||
}
|
||||
//if driver.IsExpression(column) {
|
||||
// return w.whereRaw(boolean, column.(string), args...)
|
||||
//}
|
||||
return w.where(boolean, column, "=", args[0], boolean)
|
||||
case 2:
|
||||
return w.where(boolean, column, args[0], args[1], boolean)
|
||||
|
||||
14
database.go
14
database.go
@@ -11,13 +11,13 @@ import (
|
||||
|
||||
type Database struct {
|
||||
*Engin
|
||||
Driver driver.IDriver
|
||||
Driver *driver.Driver
|
||||
Context *builder.Context
|
||||
}
|
||||
|
||||
func NewDatabase(g *GoRose) *Database {
|
||||
return &Database{
|
||||
Driver: driver.GetDriver(g.driver),
|
||||
Driver: driver.NewDriver(g.driver),
|
||||
Engin: NewEngin(g),
|
||||
Context: builder.NewContext(g.prefix),
|
||||
}
|
||||
@@ -139,31 +139,31 @@ func (db *Database) OrderByRaw(column string) *Database {
|
||||
|
||||
// Limit 设置查询结果的限制数量。
|
||||
func (db *Database) Limit(limit int) *Database {
|
||||
db.Context.LimitOffsetClause.Limit = limit
|
||||
db.Context.Limit(limit)
|
||||
return db
|
||||
}
|
||||
|
||||
// Offset 设置查询结果的偏移量。
|
||||
func (db *Database) Offset(offset int) *Database {
|
||||
db.Context.LimitOffsetClause.Offset = offset
|
||||
db.Context.Offset(offset)
|
||||
return db
|
||||
}
|
||||
|
||||
// Page 页数,根据limit确定
|
||||
func (db *Database) Page(num int) *Database {
|
||||
db.Context.LimitOffsetClause.Page = num
|
||||
db.Context.Page(num)
|
||||
return db
|
||||
}
|
||||
|
||||
// SharedLock 4 select ... locking in share mode
|
||||
func (db *Database) SharedLock() *Database {
|
||||
db.Context.PessimisticLocking = "LOCK IN SHARE MODE"
|
||||
db.Context.SharedLock()
|
||||
return db
|
||||
}
|
||||
|
||||
// LockForUpdate 4 select ... for update
|
||||
func (db *Database) LockForUpdate() *Database {
|
||||
db.Context.PessimisticLocking = "FOR UPDATE"
|
||||
db.Context.LockForUpdate()
|
||||
return db
|
||||
}
|
||||
|
||||
|
||||
40
driver/dialect/dialect.go
Normal file
40
driver/dialect/dialect.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package dialect
|
||||
|
||||
import "sync"
|
||||
|
||||
type IDialect interface {
|
||||
New() IDialect
|
||||
Placeholder() string // 占位符处理,例如 MySQL 使用 `?`, PostgreSQL 使用 `$1`
|
||||
AutoIncrement() string // 自增字段的声明方式
|
||||
LimitOffset(limit, offset int) string // 分页查询的 SQL 片段
|
||||
//InsertQuery(table string, columns []string, values [][]interface{}) (string, []interface{}) // 批量插入 SQL 生成
|
||||
QuoteIdentifier(identifier string) string // 对标识符(字段名、表名)加引号
|
||||
Upsert() string
|
||||
|
||||
LockInShareMode() string
|
||||
LockForUpdate() string
|
||||
}
|
||||
|
||||
var dialectMap = map[string]IDialect{}
|
||||
var dialectLock sync.RWMutex
|
||||
|
||||
func Register(driver string, parser IDialect) {
|
||||
dialectLock.Lock()
|
||||
defer dialectLock.Unlock()
|
||||
dialectMap[driver] = parser
|
||||
}
|
||||
|
||||
func GetDialect(driver string) IDialect {
|
||||
dialectLock.RLock()
|
||||
defer dialectLock.RUnlock()
|
||||
return dialectMap[driver]
|
||||
}
|
||||
|
||||
func DialectList() (dr []string) {
|
||||
dialectLock.RLock()
|
||||
defer dialectLock.RUnlock()
|
||||
for d := range dialectMap {
|
||||
dr = append(dr, d)
|
||||
}
|
||||
return
|
||||
}
|
||||
46
driver/dialect/mssql.go
Normal file
46
driver/dialect/mssql.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package dialect
|
||||
|
||||
import "fmt"
|
||||
|
||||
type MsSQLDialect struct {
|
||||
placeHolderIndex int
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register("mssql", &MsSQLDialect{})
|
||||
}
|
||||
|
||||
func (d *MsSQLDialect) New() IDialect {
|
||||
return &MsSQLDialect{}
|
||||
}
|
||||
|
||||
func (d *MsSQLDialect) Placeholder() string {
|
||||
//return fmt.Sprintf("$%d", idx)
|
||||
d.placeHolderIndex += 1
|
||||
return fmt.Sprintf("@p%d", d.placeHolderIndex)
|
||||
}
|
||||
|
||||
func (d *MsSQLDialect) AutoIncrement() string {
|
||||
return "IDENTITY(1,1)"
|
||||
}
|
||||
|
||||
func (d *MsSQLDialect) LimitOffset(limit, offset int) string {
|
||||
return fmt.Sprintf("OFFSET %d ROWS FETCH NEXT %d ROWS ONLY", offset, limit)
|
||||
}
|
||||
|
||||
func (d *MsSQLDialect) QuoteIdentifier(identifier string) string {
|
||||
if identifier == "" || identifier == "*" {
|
||||
return identifier
|
||||
}
|
||||
return fmt.Sprintf("[%s]", identifier)
|
||||
}
|
||||
|
||||
func (d *MsSQLDialect) Upsert() string {
|
||||
return "MERGE INTO"
|
||||
}
|
||||
|
||||
func (d *MsSQLDialect) LockInShareMode() string { return "" }
|
||||
|
||||
//func (d *MsSQLDialect) LockInShareMode() string { return "WITH (HOLDLOCK)" }
|
||||
|
||||
func (d *MsSQLDialect) LockForUpdate() string { return "WITH (ROWLOCK)" }
|
||||
40
driver/dialect/mysql.go
Normal file
40
driver/dialect/mysql.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package dialect
|
||||
|
||||
import "fmt"
|
||||
|
||||
type MySQLDialect struct{}
|
||||
|
||||
func init() {
|
||||
Register("mysql", &MySQLDialect{})
|
||||
}
|
||||
|
||||
func (d *MySQLDialect) New() IDialect {
|
||||
return &MySQLDialect{}
|
||||
}
|
||||
|
||||
func (d *MySQLDialect) Placeholder() string {
|
||||
return "?"
|
||||
}
|
||||
|
||||
func (d *MySQLDialect) AutoIncrement() string {
|
||||
return "AUTO_INCREMENT"
|
||||
}
|
||||
|
||||
func (d *MySQLDialect) LimitOffset(limit, offset int) string {
|
||||
if offset == 0 {
|
||||
return fmt.Sprintf("LIMIT %d", limit)
|
||||
}
|
||||
return fmt.Sprintf("LIMIT %d OFFSET %d", limit, offset)
|
||||
}
|
||||
|
||||
func (d *MySQLDialect) QuoteIdentifier(identifier string) string {
|
||||
if identifier == "" || identifier == "*" {
|
||||
return identifier
|
||||
}
|
||||
return fmt.Sprintf("`%s`", identifier)
|
||||
}
|
||||
|
||||
func (d *MySQLDialect) Upsert() string { return "ON DUPLICATE KEY UPDATE" }
|
||||
|
||||
func (d *MySQLDialect) LockInShareMode() string { return "LOCK IN SHARE MODE" }
|
||||
func (d *MySQLDialect) LockForUpdate() string { return "FOR UPDATE" }
|
||||
43
driver/dialect/oracle.go
Normal file
43
driver/dialect/oracle.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package dialect
|
||||
|
||||
import "fmt"
|
||||
|
||||
type OracleDialect struct {
|
||||
placeHolderIndex int
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register("oracle", &OracleDialect{})
|
||||
}
|
||||
|
||||
func (d *OracleDialect) New() IDialect {
|
||||
return &OracleDialect{}
|
||||
}
|
||||
|
||||
func (d *OracleDialect) Placeholder() string {
|
||||
//return fmt.Sprintf("$%d", idx)
|
||||
d.placeHolderIndex += 1
|
||||
return fmt.Sprintf("@p%d", d.placeHolderIndex)
|
||||
}
|
||||
|
||||
func (d *OracleDialect) AutoIncrement() string {
|
||||
return "" // 需要使用序列
|
||||
}
|
||||
|
||||
func (d *OracleDialect) LimitOffset(limit, offset int) string {
|
||||
return fmt.Sprintf("OFFSET %d ROWS FETCH NEXT %d ROWS ONLY", offset, limit)
|
||||
}
|
||||
|
||||
func (d *OracleDialect) QuoteIdentifier(identifier string) string {
|
||||
if identifier == "" || identifier == "*" {
|
||||
return identifier
|
||||
}
|
||||
return fmt.Sprintf("\"%s\"", identifier)
|
||||
}
|
||||
|
||||
func (d *OracleDialect) Upsert() string {
|
||||
return "MERGE INTO"
|
||||
}
|
||||
|
||||
func (d *OracleDialect) LockInShareMode() string { return "" }
|
||||
func (d *OracleDialect) LockForUpdate() string { return "FOR UPDATE" }
|
||||
76
driver/dialect/postgresql.go
Normal file
76
driver/dialect/postgresql.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package dialect
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type PostgresqlDialect struct {
|
||||
placeHolderIndex int
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register("postgresql", &PostgresqlDialect{})
|
||||
}
|
||||
|
||||
func (d *PostgresqlDialect) New() IDialect {
|
||||
return &PostgresqlDialect{}
|
||||
}
|
||||
|
||||
func (d *PostgresqlDialect) Placeholder() string {
|
||||
//return fmt.Sprintf("$%d", idx)
|
||||
d.placeHolderIndex += 1
|
||||
return fmt.Sprintf("$%d", d.placeHolderIndex)
|
||||
}
|
||||
|
||||
//func (d *PostgresqlDialect) IsExpression(obj any) (b bool) {
|
||||
// rfv := reflect.Indirect(reflect.ValueOf(obj))
|
||||
// if rfv.Kind() == reflect.String && strings.Contains(rfv.String(), "$") {
|
||||
// b = true
|
||||
// }
|
||||
// return
|
||||
//}
|
||||
//func (d *PostgresqlDialect) PlaceholderMulti(total int) string {
|
||||
// var arr = make([]string, 0, total)
|
||||
// for i := 0; i < total; i++ {
|
||||
// arr = append(arr, fmt.Sprintf("$%d", i+1))
|
||||
// }
|
||||
// return strings.Join(arr, ",")
|
||||
//}
|
||||
//
|
||||
//func (d *PostgresqlDialect) PlaceholderMultiInsert(cols, rows int) string {
|
||||
// placeholders := make([]string, 0, rows)
|
||||
//
|
||||
// for i := 0; i < rows; i++ {
|
||||
// ph := make([]string, cols)
|
||||
// for j := 0; j < cols; j++ {
|
||||
// ph[j] = fmt.Sprintf("$%d", i*cols+j+1)
|
||||
// }
|
||||
// placeholders = append(placeholders, "("+strings.Join(ph, ",")+")")
|
||||
// }
|
||||
// return strings.Join(placeholders, ",")
|
||||
//}
|
||||
|
||||
func (d *PostgresqlDialect) AutoIncrement() string {
|
||||
return "SERIAL"
|
||||
}
|
||||
|
||||
func (d *PostgresqlDialect) LimitOffset(limit, offset int) string {
|
||||
if offset == 0 {
|
||||
return fmt.Sprintf("LIMIT %d", limit)
|
||||
}
|
||||
return fmt.Sprintf("LIMIT %d OFFSET %d", limit, offset)
|
||||
}
|
||||
|
||||
func (d *PostgresqlDialect) QuoteIdentifier(identifier string) string {
|
||||
if identifier == "" || identifier == "*" {
|
||||
return identifier
|
||||
}
|
||||
return fmt.Sprintf("\"%s\"", identifier)
|
||||
}
|
||||
|
||||
func (d *PostgresqlDialect) Upsert() string {
|
||||
return "ON CONFLICT DO UPDATE"
|
||||
}
|
||||
|
||||
func (d *PostgresqlDialect) LockInShareMode() string { return "FOR SHARE" }
|
||||
func (d *PostgresqlDialect) LockForUpdate() string { return "FOR UPDATE" }
|
||||
42
driver/dialect/sqlite3.go
Normal file
42
driver/dialect/sqlite3.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package dialect
|
||||
|
||||
import "fmt"
|
||||
|
||||
type SQLite3Dialect struct{}
|
||||
|
||||
func init() {
|
||||
Register("sqlite3", &SQLite3Dialect{})
|
||||
}
|
||||
|
||||
func (d *SQLite3Dialect) New() IDialect {
|
||||
return &SQLite3Dialect{}
|
||||
}
|
||||
|
||||
func (d *SQLite3Dialect) Placeholder() string {
|
||||
return "?"
|
||||
}
|
||||
|
||||
func (d *SQLite3Dialect) AutoIncrement() string {
|
||||
return "AUTOINCREMENT"
|
||||
}
|
||||
|
||||
func (d *SQLite3Dialect) LimitOffset(limit, offset int) string {
|
||||
if offset == 0 {
|
||||
return fmt.Sprintf("LIMIT %d", limit)
|
||||
}
|
||||
return fmt.Sprintf("LIMIT %d OFFSET %d", limit, offset)
|
||||
}
|
||||
|
||||
func (d *SQLite3Dialect) QuoteIdentifier(identifier string) string {
|
||||
if identifier == "" || identifier == "*" {
|
||||
return identifier
|
||||
}
|
||||
return fmt.Sprintf("\"%s\"", identifier)
|
||||
}
|
||||
|
||||
func (d *SQLite3Dialect) Upsert() string {
|
||||
return "INSERT OR REPLACE"
|
||||
}
|
||||
|
||||
func (d *SQLite3Dialect) LockInShareMode() string { return "" }
|
||||
func (d *SQLite3Dialect) LockForUpdate() string { return "" }
|
||||
560
driver/driver.go
560
driver/driver.go
@@ -1,8 +1,16 @@
|
||||
package driver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gohouse/gorose/v3/builder"
|
||||
"sync"
|
||||
"github.com/gohouse/gorose/v3/driver/dialect"
|
||||
"github.com/gohouse/gorose/v3/parser"
|
||||
"regexp"
|
||||
"sort"
|
||||
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type IDriver interface {
|
||||
@@ -19,26 +27,544 @@ type IDriver interface {
|
||||
ToSqlDelete(c *builder.Context, obj any, mustColumn ...string) (sqlSegment string, binds []any, err error)
|
||||
}
|
||||
|
||||
var driverMap = map[string]IDriver{}
|
||||
var driverLock sync.RWMutex
|
||||
|
||||
func Register(driver string, parser IDriver) {
|
||||
driverLock.Lock()
|
||||
defer driverLock.Unlock()
|
||||
driverMap[driver] = parser
|
||||
type Driver struct {
|
||||
//driver string
|
||||
Dialect dialect.IDialect
|
||||
}
|
||||
|
||||
func GetDriver(driver string) IDriver {
|
||||
driverLock.RLock()
|
||||
defer driverLock.RUnlock()
|
||||
return driverMap[driver]
|
||||
func NewDriver(driver string) *Driver {
|
||||
return &Driver{Dialect: dialect.GetDialect(driver).New()}
|
||||
}
|
||||
|
||||
func DriverList() (dr []string) {
|
||||
driverLock.RLock()
|
||||
defer driverLock.RUnlock()
|
||||
for d := range driverMap {
|
||||
dr = append(dr, d)
|
||||
func (d Driver) ToSql(c *builder.Context) (sql4prepare string, binds []any, err error) {
|
||||
sql4prepare, binds, err = d.toSql(c)
|
||||
if len(c.UnionClause.Unions) > 0 {
|
||||
for _, u := range c.UnionClause.Unions {
|
||||
sql4prepare2, binds2, err2 := u.ToSql()
|
||||
if err2 != nil {
|
||||
return
|
||||
}
|
||||
if sql4prepare2 == "" {
|
||||
continue
|
||||
}
|
||||
var unions = "UNION"
|
||||
if u.IsUnionAll {
|
||||
unions = "UNION ALL"
|
||||
}
|
||||
sql4prepare = fmt.Sprintf("%s %s %s", sql4prepare, unions, sql4prepare2)
|
||||
binds = append(binds, binds2...)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
func (d Driver) toSql(c *builder.Context) (sql4prepare string, binds []any, err error) {
|
||||
selects, anies := d.ToSqlSelect(c)
|
||||
table, binds2, err := d.ToSqlTable(c)
|
||||
if err != nil {
|
||||
return sql4prepare, binds2, err
|
||||
}
|
||||
joins, binds3, err := d.ToSqlJoin(c)
|
||||
if err != nil {
|
||||
return sql4prepare, binds3, err
|
||||
}
|
||||
wheres, binds4, err := d.ToSqlWhere(c)
|
||||
if err != nil {
|
||||
return sql4prepare, binds4, err
|
||||
}
|
||||
orderBy := d.ToSqlOrderBy(c)
|
||||
limit, binds5 := d.ToSqlLimitOffset(c)
|
||||
groupBys := d.ToSqlGroupBy(c)
|
||||
havings, binds6, err := d.ToSqlHaving(c)
|
||||
|
||||
binds = append(binds, anies...)
|
||||
binds = append(binds, binds2...)
|
||||
binds = append(binds, binds3...)
|
||||
binds = append(binds, binds4...)
|
||||
binds = append(binds, binds5...)
|
||||
binds = append(binds, binds6...)
|
||||
|
||||
var locking string
|
||||
if c.PessimisticLocking == builder.TypeLockInShareMode {
|
||||
locking = d.Dialect.LockInShareMode()
|
||||
} else if c.PessimisticLocking == builder.TypeLockForUpdate {
|
||||
locking = d.Dialect.LockForUpdate()
|
||||
}
|
||||
|
||||
//sql4prepare = NamedSprintf("SELECT :selects FROM :table :join :wheres :groupBys :havings :orderBy :pagination :PessimisticLocking", selects, table, joins, wheres, groupBys, havings, orderBy, limit, c.PessimisticLocking)
|
||||
sql4prepare = fmt.Sprintf("SELECT %s FROM %s %s %s %s %s %s %s %s", selects, table, joins, wheres, groupBys, havings, orderBy, limit, locking)
|
||||
sql4prepare = regexp.MustCompile(`\s{2,}`).ReplaceAllString(strings.TrimSpace(sql4prepare), " ")
|
||||
return
|
||||
}
|
||||
|
||||
func (d Driver) ToSqlSelect(c *builder.Context) (sql4prepare string, binds []any) {
|
||||
var cols []string
|
||||
for _, col := range c.SelectClause.Columns {
|
||||
if col.IsRaw {
|
||||
cols = append(cols, col.Name)
|
||||
binds = append(binds, col.Binds...)
|
||||
} else {
|
||||
if col.Alias == "" {
|
||||
cols = append(cols, d.Dialect.QuoteIdentifier(col.Name))
|
||||
} else {
|
||||
cols = append(cols, fmt.Sprintf("%s AS %s", d.Dialect.QuoteIdentifier(col.Name), col.Alias))
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(cols) == 0 {
|
||||
cols = []string{"*"}
|
||||
}
|
||||
var distinct string
|
||||
if c.SelectClause.Distinct {
|
||||
distinct = "DISTINCT "
|
||||
}
|
||||
sql4prepare = fmt.Sprintf("%s%s", distinct, strings.Join(cols, ", "))
|
||||
return
|
||||
}
|
||||
|
||||
func (d Driver) ToSqlTable(c *builder.Context) (sql4prepare string, binds []any, err error) {
|
||||
return d.buildSqlTable(c.TableClause, c.Prefix)
|
||||
}
|
||||
|
||||
func (d Driver) buildSqlTable(tab builder.TableClause, prefix string) (sql4prepare string, binds []any, err error) {
|
||||
if v, ok := tab.Tables.(builder.IBuilder); ok {
|
||||
sql4prepare, binds, err = v.ToSql()
|
||||
if tab.Alias != "" {
|
||||
sql4prepare = fmt.Sprintf("(%s) %s", sql4prepare, d.Dialect.QuoteIdentifier(tab.Alias))
|
||||
}
|
||||
return
|
||||
}
|
||||
rfv := reflect.Indirect(reflect.ValueOf(tab.Tables))
|
||||
switch rfv.Kind() {
|
||||
case reflect.String:
|
||||
sql4prepare = d.Dialect.QuoteIdentifier(fmt.Sprintf("%s%s", prefix, tab.Tables))
|
||||
case reflect.Struct:
|
||||
sql4prepare = d.buildTableName(rfv.Type(), prefix)
|
||||
case reflect.Slice:
|
||||
if rfv.Type().Elem().Kind() == reflect.Struct {
|
||||
sql4prepare = d.buildTableName(rfv.Type().Elem(), prefix)
|
||||
} else {
|
||||
err = errors.New("table param must be string or struct(slice) bind with 1 or 2 params")
|
||||
return
|
||||
}
|
||||
default:
|
||||
err = errors.New("table must be string | struct | slice")
|
||||
return
|
||||
}
|
||||
return strings.TrimSpace(fmt.Sprintf("%s %s", sql4prepare, d.Dialect.QuoteIdentifier(tab.Alias))), binds, err
|
||||
}
|
||||
|
||||
func (d Driver) toSqlWhere(c *builder.Context, wc builder.WhereClause) (sql4prepare string, binds []any, err error) {
|
||||
if len(wc.Conditions) == 0 {
|
||||
return
|
||||
}
|
||||
var sql4prepareArr []string
|
||||
for _, v := range wc.Conditions {
|
||||
switch item := v.(type) {
|
||||
case builder.TypeWhereRaw:
|
||||
sql4prepareArr = append(sql4prepareArr, fmt.Sprintf("%s %s", item.LogicalOp, item.Column))
|
||||
binds = append(binds, item.Bindings...)
|
||||
case builder.TypeWhereStandard:
|
||||
sql4prepareArr = append(sql4prepareArr, fmt.Sprintf("%s %s %s %s", item.LogicalOp, d.Dialect.QuoteIdentifier(item.Column), item.Operator, d.Dialect.Placeholder()))
|
||||
binds = append(binds, item.Value)
|
||||
case builder.TypeWhereIn:
|
||||
values := ToSlice(item.Value)
|
||||
var phs []string
|
||||
for range values {
|
||||
phs = append(phs, d.Dialect.Placeholder())
|
||||
}
|
||||
//sql4prepareArr = append(sql4prepareArr, fmt.Sprintf("%s %s %s (%s)", item.LogicalOp, d.Dialect.QuoteIdentifier(item.Column), item.Operator, strings.Repeat("?,", len(values)-1)+"?"))
|
||||
sql4prepareArr = append(sql4prepareArr, fmt.Sprintf("%s %s %s (%s)", item.LogicalOp, d.Dialect.QuoteIdentifier(item.Column), item.Operator, strings.Join(phs, ",")))
|
||||
binds = append(binds, values...)
|
||||
case builder.TypeWhereBetween:
|
||||
values := ToSlice(item.Value)
|
||||
sql4prepareArr = append(sql4prepareArr, fmt.Sprintf("%s %s %s %s AND %s", item.LogicalOp, d.Dialect.QuoteIdentifier(item.Column), d.Dialect.Placeholder(), item.Operator, d.Dialect.Placeholder()))
|
||||
binds = append(binds, values...)
|
||||
case builder.TypeWhereNested:
|
||||
var tmp = builder.Context{}
|
||||
item.WhereNested(&tmp.WhereClause)
|
||||
prepare, anies, err := d.ToSqlWhere(&tmp)
|
||||
if err != nil {
|
||||
return sql4prepare, binds, err
|
||||
}
|
||||
sql4prepareArr = append(sql4prepareArr, fmt.Sprintf("%s (%s)", item.LogicalOp, strings.TrimPrefix(prepare, "WHERE ")))
|
||||
binds = append(binds, anies...)
|
||||
case builder.TypeWhereSubQuery:
|
||||
query, anies, err := item.SubQuery.ToSql()
|
||||
if err != nil {
|
||||
return sql4prepare, binds, err
|
||||
}
|
||||
sql4prepareArr = append(sql4prepareArr, fmt.Sprintf("%s %s %s (%s)", item.LogicalOp, d.Dialect.QuoteIdentifier(item.Column), item.Operator, query))
|
||||
binds = append(binds, anies...)
|
||||
case builder.TypeWhereSubHandler:
|
||||
var ctx = builder.NewContext(c.Prefix)
|
||||
item.Sub(ctx)
|
||||
query, anies, err := d.ToSql(ctx)
|
||||
if err != nil {
|
||||
return sql4prepare, binds, err
|
||||
}
|
||||
sql4prepareArr = append(sql4prepareArr, fmt.Sprintf("%s %s %s (%s)", item.LogicalOp, d.Dialect.QuoteIdentifier(item.Column), item.Operator, query))
|
||||
binds = append(binds, anies...)
|
||||
}
|
||||
}
|
||||
if len(sql4prepareArr) > 0 {
|
||||
sql4prepare = strings.TrimSpace(strings.Trim(strings.Trim(strings.TrimSpace(strings.Join(sql4prepareArr, " ")), "AND"), "OR"))
|
||||
}
|
||||
return
|
||||
}
|
||||
func (d Driver) ToSqlWhere(c *builder.Context) (sql4prepare string, binds []any, err error) {
|
||||
sql4prepare, binds, err = d.toSqlWhere(c, c.WhereClause)
|
||||
if sql4prepare != "" {
|
||||
if c.WhereClause.Not {
|
||||
sql4prepare = fmt.Sprintf("WHERE NOT %s", sql4prepare)
|
||||
}
|
||||
sql4prepare = fmt.Sprintf("WHERE %s", sql4prepare)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d Driver) ToSqlJoin(c *builder.Context) (sql4prepare string, binds []any, err error) {
|
||||
if c.JoinClause.Err != nil {
|
||||
return sql4prepare, binds, c.JoinClause.Err
|
||||
}
|
||||
if len(c.JoinClause.JoinItems) == 0 {
|
||||
return
|
||||
}
|
||||
for _, v := range c.JoinClause.JoinItems {
|
||||
var prepare string
|
||||
var sql4 string
|
||||
var bind []any
|
||||
switch item := v.(type) {
|
||||
case builder.TypeJoinStandard:
|
||||
prepare, bind, err = d.buildSqlTable(item.TableClause, c.Prefix)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
sql4 = fmt.Sprintf("%s %s ON %s %s %s", item.Type, prepare, d.Dialect.QuoteIdentifier(item.Column1), item.Operator, d.Dialect.QuoteIdentifier(item.Column2))
|
||||
case builder.TypeJoinSub:
|
||||
sql4, bind, err = item.ToSql()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case builder.TypeJoinOn:
|
||||
var tjo builder.TypeJoinOnCondition
|
||||
item.OnClause(&tjo)
|
||||
if len(tjo.Conditions) == 0 {
|
||||
return
|
||||
}
|
||||
var sqlArr []string
|
||||
for _, cond := range tjo.Conditions {
|
||||
sqlArr = append(sqlArr, fmt.Sprintf("%s %s %s %s", cond.Relation, d.Dialect.QuoteIdentifier(cond.Column1), cond.Operator, d.Dialect.QuoteIdentifier(cond.Column2)))
|
||||
}
|
||||
|
||||
sql4 = TrimPrefixAndOr(strings.Join(sqlArr, " "))
|
||||
}
|
||||
sql4prepare = fmt.Sprintf("%s %s", sql4prepare, sql4)
|
||||
binds = append(binds, bind...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d Driver) ToSqlGroupBy(c *builder.Context) (sql4prepare string) {
|
||||
if len(c.GroupClause.Groups) > 0 {
|
||||
var tmp []string
|
||||
for _, col := range c.GroupClause.Groups {
|
||||
if col.IsRaw {
|
||||
tmp = append(tmp, col.Column)
|
||||
} else {
|
||||
tmp = append(tmp, d.Dialect.QuoteIdentifier(col.Column))
|
||||
}
|
||||
}
|
||||
sql4prepare = fmt.Sprintf("GROUP BY %s", strings.Join(tmp, ","))
|
||||
}
|
||||
return
|
||||
}
|
||||
func (d Driver) ToSqlHaving(c *builder.Context) (sql4prepare string, binds []any, err error) {
|
||||
sql4prepare, binds, err = d.toSqlWhere(c, c.HavingClause.WhereClause)
|
||||
if sql4prepare != "" {
|
||||
sql4prepare = fmt.Sprintf("HAVING %s", sql4prepare)
|
||||
}
|
||||
return
|
||||
}
|
||||
func (d Driver) ToSqlOrderBy(c *builder.Context) (sql4prepare string) {
|
||||
if len(c.OrderByClause.Columns) == 0 {
|
||||
return
|
||||
}
|
||||
var orderBys []string
|
||||
for _, v := range c.OrderByClause.Columns {
|
||||
if v.IsRaw {
|
||||
orderBys = append(orderBys, v.Column)
|
||||
} else {
|
||||
if v.Direction == "" {
|
||||
orderBys = append(orderBys, d.Dialect.QuoteIdentifier(v.Column))
|
||||
} else {
|
||||
orderBys = append(orderBys, fmt.Sprintf("%s %s", d.Dialect.QuoteIdentifier(v.Column), v.Direction))
|
||||
}
|
||||
}
|
||||
}
|
||||
sql4prepare = fmt.Sprintf("ORDER BY %s", strings.Join(orderBys, ", "))
|
||||
return
|
||||
}
|
||||
|
||||
func (d Driver) ToSqlLimitOffset(c *builder.Context) (sqlSegment string, binds []any) {
|
||||
var offset int
|
||||
if c.LimitOffsetClause.Offset > 0 {
|
||||
offset = c.LimitOffsetClause.Offset
|
||||
} else if c.LimitOffsetClause.Page > 0 {
|
||||
offset = c.LimitOffsetClause.Limit * (c.LimitOffsetClause.Page - 1)
|
||||
}
|
||||
if c.LimitOffsetClause.Limit > 0 {
|
||||
sqlSegment = d.Dialect.LimitOffset(c.LimitOffsetClause.Limit, offset)
|
||||
//if offset > 0 {
|
||||
// sqlSegment = "LIMIT ? OFFSET ?"
|
||||
// binds = append(binds, c.LimitOffsetClause.Limit, offset)
|
||||
//} else {
|
||||
// sqlSegment = "LIMIT ?"
|
||||
// binds = append(binds, c.LimitOffsetClause.Limit)
|
||||
//}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ToSqlInsert insert
|
||||
func (d Driver) ToSqlInsert(c *builder.Context, obj any, args ...builder.TypeToSqlInsertCase) (sqlSegment string, binds []any, err error) {
|
||||
var arg builder.TypeToSqlInsertCase
|
||||
if len(args) > 0 {
|
||||
arg = args[0]
|
||||
}
|
||||
var ctx = *c
|
||||
rfv := reflect.Indirect(reflect.ValueOf(obj))
|
||||
switch rfv.Kind() {
|
||||
case reflect.Struct:
|
||||
var datas []map[string]any
|
||||
datas, err = parser.StructsToInsert(obj, arg.MustColumn...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ctx.TableClause.Table(obj)
|
||||
return d.toSqlInsert(&ctx, datas, arg)
|
||||
case reflect.Slice:
|
||||
switch rfv.Type().Elem().Kind() {
|
||||
case reflect.Struct:
|
||||
c.TableClause.Table(obj)
|
||||
var datas []map[string]any
|
||||
datas, err = parser.StructsToInsert(obj, arg.MustColumn...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return d.toSqlInsert(c, datas, arg)
|
||||
default:
|
||||
return d.toSqlInsert(c, obj, arg)
|
||||
}
|
||||
default:
|
||||
return d.toSqlInsert(c, obj, arg)
|
||||
}
|
||||
}
|
||||
|
||||
func (d Driver) ToSqlDelete(c *builder.Context, obj any, mustColumn ...string) (sqlSegment string, binds []any, err error) {
|
||||
var ctx = *c
|
||||
rfv := reflect.Indirect(reflect.ValueOf(obj))
|
||||
switch rfv.Kind() {
|
||||
case reflect.Struct:
|
||||
data, err := parser.StructToDelete(obj, mustColumn...)
|
||||
if err != nil {
|
||||
return sqlSegment, binds, err
|
||||
}
|
||||
ctx.TableClause.Table(obj)
|
||||
ctx.WhereClause.Where(data)
|
||||
return d.toSqlDelete(&ctx)
|
||||
case reflect.Int64, reflect.Int32, reflect.String:
|
||||
ctx.WhereClause.Where("id", obj)
|
||||
return d.toSqlDelete(&ctx)
|
||||
default:
|
||||
err = errors.New("obj must be struct or id value")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d Driver) ToSqlUpdate(c *builder.Context, arg any) (sqlSegment string, binds []any, err error) {
|
||||
switch v := arg.(type) {
|
||||
case builder.TypeToSqlUpdateCase:
|
||||
return d.toSqlUpdate(c, v.BindOrData, v.MustColumn...)
|
||||
case builder.TypeToSqlIncDecCase:
|
||||
return d.toSqlIncDec(c, v.Symbol, v.Data)
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (d Driver) toSqlUpdate(c *builder.Context, obj any, mustColumn ...string) (sqlSegment string, binds []any, err error) {
|
||||
rfv := reflect.Indirect(reflect.ValueOf(obj))
|
||||
switch rfv.Kind() {
|
||||
case reflect.Struct:
|
||||
dataMap, pk, pkValue, err := parser.StructToUpdate(obj, mustColumn...)
|
||||
if err != nil {
|
||||
return sqlSegment, binds, err
|
||||
}
|
||||
var ctx = *c
|
||||
ctx.TableClause.Table(obj)
|
||||
if pk != "" {
|
||||
ctx.WhereClause.Where(pk, pkValue)
|
||||
}
|
||||
return d.toSqlUpdateReal(&ctx, dataMap)
|
||||
case reflect.Map:
|
||||
return d.toSqlUpdateReal(c, obj)
|
||||
default:
|
||||
err = errors.New("no support update obj")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (d Driver) toSqlIncDec(c *builder.Context, symbol string, data map[string]any) (sql4prepare string, values []any, err error) {
|
||||
prepare, anies, err := d.ToSqlTable(c)
|
||||
if err != nil {
|
||||
return sql4prepare, values, err
|
||||
}
|
||||
values = append(values, anies...)
|
||||
|
||||
var tmp []string
|
||||
for k, v := range data {
|
||||
tmp = append(tmp, fmt.Sprintf("%s=%s%s%s", d.Dialect.QuoteIdentifier(k), d.Dialect.QuoteIdentifier(k), symbol, d.Dialect.Placeholder()))
|
||||
values = append(values, v)
|
||||
}
|
||||
|
||||
where, val, err := d.ToSqlWhere(c)
|
||||
if err != nil {
|
||||
return sql4prepare, values, err
|
||||
}
|
||||
values = append(values, val...)
|
||||
|
||||
sql4prepare = fmt.Sprintf("UPDATE %s SET %s %s", prepare, strings.Join(tmp, ","), where)
|
||||
return
|
||||
}
|
||||
|
||||
func (d Driver) buildTableName(rft reflect.Type, prefix string) (tab string) {
|
||||
return d.Dialect.QuoteIdentifier(fmt.Sprintf("%s%s", prefix, parser.StructsToTableName(rft)))
|
||||
}
|
||||
|
||||
// func (b Driver) toSqlInsert(c *gorose.Context, data any, ignoreCase string, onDuplicateKeys []string) (sql4prepare string, values []any, err error) {
|
||||
func (d Driver) toSqlInsert(c *builder.Context, data any, insertCase builder.TypeToSqlInsertCase) (sql4prepare string, values []any, err error) {
|
||||
rfv := reflect.Indirect(reflect.ValueOf(data))
|
||||
var fields []string
|
||||
var valuesPlaceholderArr []string
|
||||
switch rfv.Kind() {
|
||||
case reflect.Map:
|
||||
keys := rfv.MapKeys()
|
||||
sort.Slice(keys, func(i, j int) bool {
|
||||
return keys[i].String() < keys[j].String()
|
||||
})
|
||||
var valuesPlaceholderTmp []string
|
||||
for _, key := range keys {
|
||||
fields = append(fields, d.Dialect.QuoteIdentifier(key.String()))
|
||||
valuesPlaceholderTmp = append(valuesPlaceholderTmp, d.Dialect.Placeholder())
|
||||
values = append(values, rfv.MapIndex(key).Interface())
|
||||
}
|
||||
valuesPlaceholderArr = append(valuesPlaceholderArr, fmt.Sprintf("(%s)", strings.Join(valuesPlaceholderTmp, ",")))
|
||||
case reflect.Slice:
|
||||
if rfv.Len() == 0 {
|
||||
return
|
||||
}
|
||||
if rfv.Type().Elem().Kind() == reflect.Map {
|
||||
// 先获取到插入字段
|
||||
keys := rfv.Index(0).MapKeys()
|
||||
sort.Slice(keys, func(i, j int) bool {
|
||||
return keys[i].String() < keys[j].String()
|
||||
})
|
||||
for _, key := range keys {
|
||||
fields = append(fields, d.Dialect.QuoteIdentifier(key.String()))
|
||||
}
|
||||
// 组合插入数据
|
||||
for i := 0; i < rfv.Len(); i++ {
|
||||
var valuesPlaceholderTmp []string
|
||||
for _, key := range keys {
|
||||
valuesPlaceholderTmp = append(valuesPlaceholderTmp, d.Dialect.Placeholder())
|
||||
values = append(values, rfv.Index(i).MapIndex(key).Interface())
|
||||
}
|
||||
valuesPlaceholderArr = append(valuesPlaceholderArr, fmt.Sprintf("(%s)", strings.Join(valuesPlaceholderTmp, ",")))
|
||||
}
|
||||
} else {
|
||||
err = errors.New("only map(slice) data supported")
|
||||
return
|
||||
}
|
||||
default:
|
||||
err = errors.New("only map(slice) data supported")
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var onDuplicateKey string
|
||||
if len(insertCase.UpdateFields) > 0 {
|
||||
var tmp []string
|
||||
for _, v := range insertCase.UpdateFields {
|
||||
tmp = append(tmp, fmt.Sprintf("%s=VALUES(%s)", d.Dialect.QuoteIdentifier(v), d.Dialect.QuoteIdentifier(v)))
|
||||
}
|
||||
onDuplicateKey = fmt.Sprintf("%s %s", d.Dialect.Upsert(), strings.Join(tmp, ", "))
|
||||
}
|
||||
|
||||
var insert = "INSERT"
|
||||
if insertCase.IsReplace {
|
||||
insert = "REPLACE"
|
||||
} else if insertCase.IsIgnoreCase {
|
||||
insert = "INSERT IGNORE"
|
||||
}
|
||||
|
||||
var tables string
|
||||
tables, _, err = d.ToSqlTable(c)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
//sql4prepare = NamedSprintf(":insert INTO :tables (:fields) VALUES :placeholder :onDuplicateKey", insert, tables, strings.Join(fields, ","), strings.Join(valuesPlaceholderArr, ","), onDuplicateKey)
|
||||
sql4prepare = NamedSprintf("%s INTO %s (%s) VALUES %s %s", insert, tables, strings.Join(fields, ","), strings.Join(valuesPlaceholderArr, ","), onDuplicateKey)
|
||||
return
|
||||
}
|
||||
|
||||
func (d Driver) toSqlUpdateReal(c *builder.Context, data any) (sql4prepare string, values []any, err error) {
|
||||
rfv := reflect.Indirect(reflect.ValueOf(data))
|
||||
var updates []string
|
||||
switch rfv.Kind() {
|
||||
case reflect.Map:
|
||||
keys := rfv.MapKeys()
|
||||
sort.Slice(keys, func(i, j int) bool {
|
||||
return keys[i].String() < keys[j].String()
|
||||
})
|
||||
for _, key := range keys {
|
||||
updates = append(updates, fmt.Sprintf("%s = %s", d.Dialect.QuoteIdentifier(key.String()), d.Dialect.Placeholder()))
|
||||
values = append(values, rfv.MapIndex(key).Interface())
|
||||
}
|
||||
default:
|
||||
err = errors.New("only map data supported")
|
||||
return
|
||||
}
|
||||
var tables string
|
||||
tables, _, err = d.ToSqlTable(c)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
wheres, binds, err := d.ToSqlWhere(c)
|
||||
if err != nil {
|
||||
return sql4prepare, values, err
|
||||
}
|
||||
values = append(values, binds...)
|
||||
|
||||
//sql4prepare = NamedSprintf("UPDATE :tables SET :updates :wheres", tables, strings.Join(updates, ", "), wheres)
|
||||
sql4prepare = fmt.Sprintf("UPDATE %s SET %s %s", tables, strings.Join(updates, ", "), wheres)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (d Driver) toSqlDelete(c *builder.Context) (sql4prepare string, values []any, err error) {
|
||||
var tables string
|
||||
tables, _, err = d.ToSqlTable(c)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
wheres, binds, err := d.ToSqlWhere(c)
|
||||
if err != nil {
|
||||
return sql4prepare, values, err
|
||||
}
|
||||
values = append(values, binds...)
|
||||
//sql4prepare = NamedSprintf("DELETE FROM :tables :wheres", tables, wheres)
|
||||
sql4prepare = NamedSprintf("DELETE FROM %s %s", tables, wheres)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,409 +0,0 @@
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gohouse/gorose/v3/builder"
|
||||
"github.com/gohouse/gorose/v3/parser"
|
||||
|
||||
//_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/gohouse/gorose/v3/driver"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const DriverName = "mysql"
|
||||
|
||||
type Builder struct {
|
||||
//prefix string
|
||||
}
|
||||
|
||||
func init() {
|
||||
driver.Register(DriverName, &Builder{})
|
||||
}
|
||||
|
||||
func (b Builder) ToSql(c *builder.Context) (sql4prepare string, binds []any, err error) {
|
||||
sql4prepare, binds, err = b.toSql(c)
|
||||
if len(c.UnionClause.Unions) > 0 {
|
||||
for _, u := range c.UnionClause.Unions {
|
||||
sql4prepare2, binds2, err2 := u.ToSql()
|
||||
if err2 != nil {
|
||||
return
|
||||
}
|
||||
if sql4prepare2 == "" {
|
||||
continue
|
||||
}
|
||||
var unions = "UNION"
|
||||
if u.IsUnionAll {
|
||||
unions = "UNION ALL"
|
||||
}
|
||||
sql4prepare = fmt.Sprintf("%s %s %s", sql4prepare, unions, sql4prepare2)
|
||||
binds = append(binds, binds2...)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
func (b Builder) toSql(c *builder.Context) (sql4prepare string, binds []any, err error) {
|
||||
selects, anies := b.ToSqlSelect(c)
|
||||
table, binds2, err := b.ToSqlTable(c)
|
||||
if err != nil {
|
||||
return sql4prepare, binds2, err
|
||||
}
|
||||
joins, binds3, err := b.ToSqlJoin(c)
|
||||
if err != nil {
|
||||
return sql4prepare, binds3, err
|
||||
}
|
||||
wheres, binds4, err := b.ToSqlWhere(c)
|
||||
if err != nil {
|
||||
return sql4prepare, binds4, err
|
||||
}
|
||||
orderBy := b.ToSqlOrderBy(c)
|
||||
limit, binds5 := b.ToSqlLimitOffset(c)
|
||||
groupBys := b.ToSqlGroupBy(c)
|
||||
havings, binds6, err := b.ToSqlHaving(c)
|
||||
|
||||
binds = append(binds, anies...)
|
||||
binds = append(binds, binds2...)
|
||||
binds = append(binds, binds3...)
|
||||
binds = append(binds, binds4...)
|
||||
binds = append(binds, binds5...)
|
||||
binds = append(binds, binds6...)
|
||||
|
||||
sql4prepare = NamedSprintf("SELECT :selects FROM :table :join :wheres :groupBys :havings :orderBy :pagination :PessimisticLocking", selects, table, joins, wheres, groupBys, havings, orderBy, limit, c.PessimisticLocking)
|
||||
return
|
||||
}
|
||||
|
||||
func (Builder) ToSqlSelect(c *builder.Context) (sql4prepare string, binds []any) {
|
||||
var cols []string
|
||||
for _, col := range c.SelectClause.Columns {
|
||||
if col.IsRaw {
|
||||
cols = append(cols, col.Name)
|
||||
binds = append(binds, col.Binds...)
|
||||
} else {
|
||||
if col.Alias == "" {
|
||||
cols = append(cols, BackQuotes(col.Name))
|
||||
} else {
|
||||
cols = append(cols, fmt.Sprintf("%s AS %s", BackQuotes(col.Name), col.Alias))
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(cols) == 0 {
|
||||
cols = []string{"*"}
|
||||
}
|
||||
var distinct string
|
||||
if c.SelectClause.Distinct {
|
||||
distinct = "DISTINCT "
|
||||
}
|
||||
sql4prepare = fmt.Sprintf("%s%s", distinct, strings.Join(cols, ", "))
|
||||
return
|
||||
}
|
||||
|
||||
func (b Builder) ToSqlTable(c *builder.Context) (sql4prepare string, binds []any, err error) {
|
||||
return b.buildSqlTable(c.TableClause, c.Prefix)
|
||||
}
|
||||
|
||||
func (b Builder) buildSqlTable(tab builder.TableClause, prefix string) (sql4prepare string, binds []any, err error) {
|
||||
if v, ok := tab.Tables.(builder.IBuilder); ok {
|
||||
sql4prepare, binds, err = v.ToSql()
|
||||
if tab.Alias != "" {
|
||||
sql4prepare = fmt.Sprintf("(%s) %s", sql4prepare, BackQuotes(tab.Alias))
|
||||
}
|
||||
return
|
||||
}
|
||||
rfv := reflect.Indirect(reflect.ValueOf(tab.Tables))
|
||||
switch rfv.Kind() {
|
||||
case reflect.String:
|
||||
sql4prepare = BackQuotes(fmt.Sprintf("%s%s", prefix, tab.Tables))
|
||||
case reflect.Struct:
|
||||
sql4prepare = b.buildTableName(rfv.Type(), prefix)
|
||||
case reflect.Slice:
|
||||
if rfv.Type().Elem().Kind() == reflect.Struct {
|
||||
sql4prepare = b.buildTableName(rfv.Type().Elem(), prefix)
|
||||
} else {
|
||||
err = errors.New("table param must be string or struct(slice) bind with 1 or 2 params")
|
||||
return
|
||||
}
|
||||
default:
|
||||
err = errors.New("table must be string | struct | slice")
|
||||
return
|
||||
}
|
||||
return strings.TrimSpace(fmt.Sprintf("%s %s", sql4prepare, BackQuotes(tab.Alias))), binds, err
|
||||
}
|
||||
|
||||
func (b Builder) toSqlWhere(c *builder.Context, wc builder.WhereClause) (sql4prepare string, binds []any, err error) {
|
||||
if len(wc.Conditions) == 0 {
|
||||
return
|
||||
}
|
||||
var sql4prepareArr []string
|
||||
for _, v := range wc.Conditions {
|
||||
switch item := v.(type) {
|
||||
case builder.TypeWhereRaw:
|
||||
sql4prepareArr = append(sql4prepareArr, fmt.Sprintf("%s %s", item.LogicalOp, item.Column))
|
||||
binds = append(binds, item.Bindings...)
|
||||
case builder.TypeWhereStandard:
|
||||
sql4prepareArr = append(sql4prepareArr, fmt.Sprintf("%s %s %s ?", item.LogicalOp, BackQuotes(item.Column), item.Operator))
|
||||
binds = append(binds, item.Value)
|
||||
case builder.TypeWhereIn:
|
||||
values := ToSlice(item.Value)
|
||||
sql4prepareArr = append(sql4prepareArr, fmt.Sprintf("%s %s %s (%s)", item.LogicalOp, BackQuotes(item.Column), item.Operator, strings.Repeat("?,", len(values)-1)+"?"))
|
||||
binds = append(binds, values...)
|
||||
case builder.TypeWhereBetween:
|
||||
values := ToSlice(item.Value)
|
||||
sql4prepareArr = append(sql4prepareArr, fmt.Sprintf("%s %s %s ? AND ?", item.LogicalOp, BackQuotes(item.Column), item.Operator))
|
||||
binds = append(binds, values...)
|
||||
case builder.TypeWhereNested:
|
||||
var tmp = builder.Context{}
|
||||
item.WhereNested(&tmp.WhereClause)
|
||||
prepare, anies, err := b.ToSqlWhere(&tmp)
|
||||
if err != nil {
|
||||
return sql4prepare, binds, err
|
||||
}
|
||||
sql4prepareArr = append(sql4prepareArr, fmt.Sprintf("%s (%s)", item.LogicalOp, strings.TrimPrefix(prepare, "WHERE ")))
|
||||
binds = append(binds, anies...)
|
||||
case builder.TypeWhereSubQuery:
|
||||
query, anies, err := item.SubQuery.ToSql()
|
||||
if err != nil {
|
||||
return sql4prepare, binds, err
|
||||
}
|
||||
sql4prepareArr = append(sql4prepareArr, fmt.Sprintf("%s %s %s (%s)", item.LogicalOp, BackQuotes(item.Column), item.Operator, query))
|
||||
binds = append(binds, anies...)
|
||||
case builder.TypeWhereSubHandler:
|
||||
var ctx = builder.NewContext(c.Prefix)
|
||||
item.Sub(ctx)
|
||||
query, anies, err := b.ToSql(ctx)
|
||||
if err != nil {
|
||||
return sql4prepare, binds, err
|
||||
}
|
||||
sql4prepareArr = append(sql4prepareArr, fmt.Sprintf("%s %s %s (%s)", item.LogicalOp, BackQuotes(item.Column), item.Operator, query))
|
||||
binds = append(binds, anies...)
|
||||
}
|
||||
}
|
||||
if len(sql4prepareArr) > 0 {
|
||||
sql4prepare = strings.TrimSpace(strings.Trim(strings.Trim(strings.TrimSpace(strings.Join(sql4prepareArr, " ")), "AND"), "OR"))
|
||||
}
|
||||
return
|
||||
}
|
||||
func (b Builder) ToSqlWhere(c *builder.Context) (sql4prepare string, binds []any, err error) {
|
||||
sql4prepare, binds, err = b.toSqlWhere(c, c.WhereClause)
|
||||
if sql4prepare != "" {
|
||||
if c.WhereClause.Not {
|
||||
sql4prepare = fmt.Sprintf("WHERE NOT %s", sql4prepare)
|
||||
}
|
||||
sql4prepare = fmt.Sprintf("WHERE %s", sql4prepare)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (b Builder) ToSqlJoin(c *builder.Context) (sql4prepare string, binds []any, err error) {
|
||||
if c.JoinClause.Err != nil {
|
||||
return sql4prepare, binds, c.JoinClause.Err
|
||||
}
|
||||
if len(c.JoinClause.JoinItems) == 0 {
|
||||
return
|
||||
}
|
||||
for _, v := range c.JoinClause.JoinItems {
|
||||
var prepare string
|
||||
var sql4 string
|
||||
var bind []any
|
||||
switch item := v.(type) {
|
||||
case builder.TypeJoinStandard:
|
||||
prepare, bind, err = b.buildSqlTable(item.TableClause, c.Prefix)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
sql4 = fmt.Sprintf("%s %s ON %s %s %s", item.Type, prepare, BackQuotes(item.Column1), item.Operator, BackQuotes(item.Column2))
|
||||
case builder.TypeJoinSub:
|
||||
sql4, bind, err = item.ToSql()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case builder.TypeJoinOn:
|
||||
var tjo builder.TypeJoinOnCondition
|
||||
item.OnClause(&tjo)
|
||||
if len(tjo.Conditions) == 0 {
|
||||
return
|
||||
}
|
||||
var sqlArr []string
|
||||
for _, cond := range tjo.Conditions {
|
||||
sqlArr = append(sqlArr, fmt.Sprintf("%s %s %s %s", cond.Relation, BackQuotes(cond.Column1), cond.Operator, BackQuotes(cond.Column2)))
|
||||
}
|
||||
|
||||
sql4 = TrimPrefixAndOr(strings.Join(sqlArr, " "))
|
||||
}
|
||||
sql4prepare = fmt.Sprintf("%s %s", sql4prepare, sql4)
|
||||
binds = append(binds, bind...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (b Builder) ToSqlGroupBy(c *builder.Context) (sql4prepare string) {
|
||||
if len(c.GroupClause.Groups) > 0 {
|
||||
var tmp []string
|
||||
for _, col := range c.GroupClause.Groups {
|
||||
if col.IsRaw {
|
||||
tmp = append(tmp, col.Column)
|
||||
} else {
|
||||
tmp = append(tmp, BackQuotes(col.Column))
|
||||
}
|
||||
}
|
||||
sql4prepare = fmt.Sprintf("GROUP BY %s", strings.Join(tmp, ","))
|
||||
}
|
||||
return
|
||||
}
|
||||
func (b Builder) ToSqlHaving(c *builder.Context) (sql4prepare string, binds []any, err error) {
|
||||
sql4prepare, binds, err = b.toSqlWhere(c, c.HavingClause.WhereClause)
|
||||
if sql4prepare != "" {
|
||||
sql4prepare = fmt.Sprintf("HAVING %s", sql4prepare)
|
||||
}
|
||||
return
|
||||
}
|
||||
func (b Builder) ToSqlOrderBy(c *builder.Context) (sql4prepare string) {
|
||||
if len(c.OrderByClause.Columns) == 0 {
|
||||
return
|
||||
}
|
||||
var orderBys []string
|
||||
for _, v := range c.OrderByClause.Columns {
|
||||
if v.IsRaw {
|
||||
orderBys = append(orderBys, v.Column)
|
||||
} else {
|
||||
if v.Direction == "" {
|
||||
orderBys = append(orderBys, BackQuotes(v.Column))
|
||||
} else {
|
||||
orderBys = append(orderBys, fmt.Sprintf("%s %s", BackQuotes(v.Column), v.Direction))
|
||||
}
|
||||
}
|
||||
}
|
||||
sql4prepare = fmt.Sprintf("ORDER BY %s", strings.Join(orderBys, ", "))
|
||||
return
|
||||
}
|
||||
|
||||
func (b Builder) ToSqlLimitOffset(c *builder.Context) (sqlSegment string, binds []any) {
|
||||
var offset int
|
||||
if c.LimitOffsetClause.Offset > 0 {
|
||||
offset = c.LimitOffsetClause.Offset
|
||||
} else if c.LimitOffsetClause.Page > 0 {
|
||||
offset = c.LimitOffsetClause.Limit * (c.LimitOffsetClause.Page - 1)
|
||||
}
|
||||
if c.LimitOffsetClause.Limit > 0 {
|
||||
if offset > 0 {
|
||||
sqlSegment = "LIMIT ? OFFSET ?"
|
||||
binds = append(binds, c.LimitOffsetClause.Limit, offset)
|
||||
} else {
|
||||
sqlSegment = "LIMIT ?"
|
||||
binds = append(binds, c.LimitOffsetClause.Limit)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ToSqlInsert insert
|
||||
func (b Builder) ToSqlInsert(c *builder.Context, obj any, args ...builder.TypeToSqlInsertCase) (sqlSegment string, binds []any, err error) {
|
||||
var arg builder.TypeToSqlInsertCase
|
||||
if len(args) > 0 {
|
||||
arg = args[0]
|
||||
}
|
||||
var ctx = *c
|
||||
rfv := reflect.Indirect(reflect.ValueOf(obj))
|
||||
switch rfv.Kind() {
|
||||
case reflect.Struct:
|
||||
var datas []map[string]any
|
||||
datas, err = parser.StructsToInsert(obj, arg.MustColumn...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ctx.TableClause.Table(obj)
|
||||
return b.toSqlInsert(&ctx, datas, arg)
|
||||
case reflect.Slice:
|
||||
switch rfv.Type().Elem().Kind() {
|
||||
case reflect.Struct:
|
||||
c.TableClause.Table(obj)
|
||||
var datas []map[string]any
|
||||
datas, err = parser.StructsToInsert(obj, arg.MustColumn...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return b.toSqlInsert(c, datas, arg)
|
||||
default:
|
||||
return b.toSqlInsert(c, obj, arg)
|
||||
}
|
||||
default:
|
||||
return b.toSqlInsert(c, obj, arg)
|
||||
}
|
||||
}
|
||||
|
||||
func (b Builder) ToSqlDelete(c *builder.Context, obj any, mustColumn ...string) (sqlSegment string, binds []any, err error) {
|
||||
var ctx = *c
|
||||
rfv := reflect.Indirect(reflect.ValueOf(obj))
|
||||
switch rfv.Kind() {
|
||||
case reflect.Struct:
|
||||
data, err := parser.StructToDelete(obj, mustColumn...)
|
||||
if err != nil {
|
||||
return sqlSegment, binds, err
|
||||
}
|
||||
ctx.TableClause.Table(obj)
|
||||
ctx.WhereClause.Where(data)
|
||||
return b.toSqlDelete(&ctx)
|
||||
case reflect.Int64, reflect.Int32, reflect.String:
|
||||
ctx.WhereClause.Where("id", obj)
|
||||
return b.toSqlDelete(&ctx)
|
||||
default:
|
||||
err = errors.New("obj must be struct or id value")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (b Builder) ToSqlUpdate(c *builder.Context, arg any) (sqlSegment string, binds []any, err error) {
|
||||
switch v := arg.(type) {
|
||||
case builder.TypeToSqlUpdateCase:
|
||||
return b.toSqlUpdate(c, v.BindOrData, v.MustColumn...)
|
||||
case builder.TypeToSqlIncDecCase:
|
||||
return b.toSqlIncDec(c, v.Symbol, v.Data)
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (b Builder) toSqlUpdate(c *builder.Context, obj any, mustColumn ...string) (sqlSegment string, binds []any, err error) {
|
||||
rfv := reflect.Indirect(reflect.ValueOf(obj))
|
||||
switch rfv.Kind() {
|
||||
case reflect.Struct:
|
||||
dataMap, pk, pkValue, err := parser.StructToUpdate(obj, mustColumn...)
|
||||
if err != nil {
|
||||
return sqlSegment, binds, err
|
||||
}
|
||||
var ctx = *c
|
||||
ctx.TableClause.Table(obj)
|
||||
if pk != "" {
|
||||
ctx.WhereClause.Where(pk, pkValue)
|
||||
}
|
||||
return b.toSqlUpdateReal(&ctx, dataMap)
|
||||
case reflect.Map:
|
||||
return b.toSqlUpdateReal(c, obj)
|
||||
default:
|
||||
err = errors.New("no support update obj")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (b Builder) toSqlIncDec(c *builder.Context, symbol string, data map[string]any) (sql4prepare string, values []any, err error) {
|
||||
prepare, anies, err := b.ToSqlTable(c)
|
||||
if err != nil {
|
||||
return sql4prepare, values, err
|
||||
}
|
||||
values = append(values, anies...)
|
||||
|
||||
var tmp []string
|
||||
for k, v := range data {
|
||||
tmp = append(tmp, fmt.Sprintf("%s=%s%s?", BackQuotes(k), BackQuotes(k), symbol))
|
||||
values = append(values, v)
|
||||
}
|
||||
|
||||
where, val, err := b.ToSqlWhere(c)
|
||||
if err != nil {
|
||||
return sql4prepare, values, err
|
||||
}
|
||||
values = append(values, val...)
|
||||
|
||||
sql4prepare = fmt.Sprintf("UPDATE %s SET %s %s", prepare, strings.Join(tmp, ","), where)
|
||||
return
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"github.com/gohouse/gorose/v3"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Id int64 `db:"id,pk"`
|
||||
Name string `db:"name"`
|
||||
}
|
||||
|
||||
var dbg = gorose.Open(nil)
|
||||
|
||||
func db() *gorose.Database {
|
||||
return dbg.NewDatabase()
|
||||
}
|
||||
|
||||
func TestDatabase_ToSqlTo(t *testing.T) {
|
||||
var user = User{Id: 1}
|
||||
prepare, values, err := db().ToSqlTo(&user)
|
||||
assertsError(t, err)
|
||||
var expect = "SELECT `id`, `name` FROM `User` WHERE `id` = ? LIMIT ?"
|
||||
assertsEqual(t, expect, prepare)
|
||||
var expectValues = []int{1, 1}
|
||||
assertsEqual(t, expectValues, values)
|
||||
}
|
||||
func TestDatabase_ToSqlToSlice(t *testing.T) {
|
||||
var user []User
|
||||
prepare, values, err := db().Where("id", ">", 1).OrderBy("id").Limit(10).Page(2).ToSqlTo(&user)
|
||||
assertsError(t, err)
|
||||
var expect = "SELECT `id`, `name` FROM `User` WHERE `id` > ? ORDER BY `id` LIMIT ? OFFSET ?"
|
||||
assertsEqual(t, expect, prepare)
|
||||
var expectValues = []int{1, 10, 10}
|
||||
assertsEqual(t, expectValues, values)
|
||||
}
|
||||
func TestDatabase_ToSql(t *testing.T) {
|
||||
prepare, values, err := db().Table("users").Select("b").Where("c", 1).GroupBy("a").Having("a", 1).OrderBy("id").Limit(10).Page(2).ToSql()
|
||||
assertsError(t, err)
|
||||
var expect = "SELECT `b` FROM `users` WHERE `c` = ? GROUP BY `a` HAVING `a` = ? ORDER BY `id` LIMIT ? OFFSET ?"
|
||||
assertsEqual(t, expect, prepare)
|
||||
var expectValues = []int{1, 10, 10, 1}
|
||||
assertsEqual(t, expectValues, values)
|
||||
}
|
||||
func TestDatabase_ToSqlInsert(t *testing.T) {
|
||||
var user = User{Name: "john"}
|
||||
prepare, values, err := db().ToSqlInsert(&user)
|
||||
assertsError(t, err)
|
||||
var expect = "INSERT INTO `User` (`name`) VALUES (?)"
|
||||
assertsEqual(t, expect, prepare)
|
||||
var expectValues = []string{"john"}
|
||||
assertsEqual(t, expectValues, values)
|
||||
}
|
||||
func TestDatabase_ToSqlInserts(t *testing.T) {
|
||||
var user = []User{{Name: "John"}, {Name: "Alice"}}
|
||||
prepare, values, err := db().ToSqlInsert(&user)
|
||||
assertsError(t, err)
|
||||
var expect = "INSERT INTO `User` (`name`) VALUES (?),(?)"
|
||||
assertsEqual(t, expect, prepare)
|
||||
var expectValues = []string{"John", "Alice"}
|
||||
assertsEqual(t, expectValues, values)
|
||||
}
|
||||
func TestDatabase_ToSqlUpdate(t *testing.T) {
|
||||
var user = User{Id: 1, Name: "john"}
|
||||
prepare, values, err := db().ToSqlUpdate(&user)
|
||||
assertsError(t, err)
|
||||
var expect = "UPDATE `User` SET `name` = ? WHERE `id` = ?"
|
||||
assertsEqual(t, expect, prepare)
|
||||
var expectValues = []any{"john", 1}
|
||||
assertsEqual(t, expectValues, values)
|
||||
}
|
||||
func TestDatabase_ToSqlDelete(t *testing.T) {
|
||||
var user = User{Id: 1}
|
||||
prepare, values, err := db().ToSqlDelete(&user, "name")
|
||||
assertsError(t, err)
|
||||
var expect = "DELETE FROM `User` WHERE `id` = ? AND `name` = ?"
|
||||
assertsEqual(t, expect, prepare)
|
||||
var expectValues = []any{1, ""}
|
||||
assertsEqual(t, expectValues, values)
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gohouse/gorose/v3/builder"
|
||||
"github.com/gohouse/gorose/v3/parser"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (b Builder) buildTableName(rft reflect.Type, prefix string) (tab string) {
|
||||
return BackQuotes(fmt.Sprintf("%s%s", prefix, parser.StructsToTableName(rft)))
|
||||
}
|
||||
|
||||
// func (b Builder) toSqlInsert(c *gorose.Context, data any, ignoreCase string, onDuplicateKeys []string) (sql4prepare string, values []any, err error) {
|
||||
func (b Builder) toSqlInsert(c *builder.Context, data any, insertCase builder.TypeToSqlInsertCase) (sql4prepare string, values []any, err error) {
|
||||
rfv := reflect.Indirect(reflect.ValueOf(data))
|
||||
var fields []string
|
||||
var valuesPlaceholderArr []string
|
||||
switch rfv.Kind() {
|
||||
case reflect.Map:
|
||||
keys := rfv.MapKeys()
|
||||
sort.Slice(keys, func(i, j int) bool {
|
||||
return keys[i].String() < keys[j].String()
|
||||
})
|
||||
var valuesPlaceholderTmp []string
|
||||
for _, key := range keys {
|
||||
fields = append(fields, BackQuotes(key.String()))
|
||||
valuesPlaceholderTmp = append(valuesPlaceholderTmp, "?")
|
||||
values = append(values, rfv.MapIndex(key).Interface())
|
||||
}
|
||||
valuesPlaceholderArr = append(valuesPlaceholderArr, fmt.Sprintf("(%s)", strings.Join(valuesPlaceholderTmp, ",")))
|
||||
case reflect.Slice:
|
||||
if rfv.Len() == 0 {
|
||||
return
|
||||
}
|
||||
if rfv.Type().Elem().Kind() == reflect.Map {
|
||||
// 先获取到插入字段
|
||||
keys := rfv.Index(0).MapKeys()
|
||||
sort.Slice(keys, func(i, j int) bool {
|
||||
return keys[i].String() < keys[j].String()
|
||||
})
|
||||
for _, key := range keys {
|
||||
fields = append(fields, BackQuotes(key.String()))
|
||||
}
|
||||
// 组合插入数据
|
||||
for i := 0; i < rfv.Len(); i++ {
|
||||
var valuesPlaceholderTmp []string
|
||||
for _, key := range keys {
|
||||
valuesPlaceholderTmp = append(valuesPlaceholderTmp, "?")
|
||||
values = append(values, rfv.Index(i).MapIndex(key).Interface())
|
||||
}
|
||||
valuesPlaceholderArr = append(valuesPlaceholderArr, fmt.Sprintf("(%s)", strings.Join(valuesPlaceholderTmp, ",")))
|
||||
}
|
||||
} else {
|
||||
err = errors.New("only map(slice) data supported")
|
||||
return
|
||||
}
|
||||
default:
|
||||
err = errors.New("only map(slice) data supported")
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var onDuplicateKey string
|
||||
if len(insertCase.UpdateFields) > 0 {
|
||||
var tmp []string
|
||||
for _, v := range insertCase.UpdateFields {
|
||||
tmp = append(tmp, fmt.Sprintf("%s=VALUES(%s)", BackQuotes(v), BackQuotes(v)))
|
||||
}
|
||||
onDuplicateKey = fmt.Sprintf("ON DUPLICATE KEY UPDATE %s", strings.Join(tmp, ", "))
|
||||
}
|
||||
|
||||
var insert = "INSERT"
|
||||
if insertCase.IsReplace {
|
||||
insert = "REPLACE"
|
||||
} else if insertCase.IsIgnoreCase {
|
||||
insert = "INSERT IGNORE"
|
||||
}
|
||||
|
||||
var tables string
|
||||
tables, _, err = b.ToSqlTable(c)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
sql4prepare = NamedSprintf(":insert INTO :tables (:fields) VALUES :placeholder :onDuplicateKey", insert, tables, strings.Join(fields, ","), strings.Join(valuesPlaceholderArr, ","), onDuplicateKey)
|
||||
return
|
||||
}
|
||||
|
||||
func (b Builder) toSqlUpdateReal(c *builder.Context, data any) (sql4prepare string, values []any, err error) {
|
||||
rfv := reflect.Indirect(reflect.ValueOf(data))
|
||||
var updates []string
|
||||
switch rfv.Kind() {
|
||||
case reflect.Map:
|
||||
keys := rfv.MapKeys()
|
||||
sort.Slice(keys, func(i, j int) bool {
|
||||
return keys[i].String() < keys[j].String()
|
||||
})
|
||||
for _, key := range keys {
|
||||
updates = append(updates, fmt.Sprintf("%s = ?", BackQuotes(key.String())))
|
||||
values = append(values, rfv.MapIndex(key).Interface())
|
||||
}
|
||||
default:
|
||||
err = errors.New("only map data supported")
|
||||
return
|
||||
}
|
||||
var tables string
|
||||
tables, _, err = b.ToSqlTable(c)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
wheres, binds, err := b.ToSqlWhere(c)
|
||||
if err != nil {
|
||||
return sql4prepare, values, err
|
||||
}
|
||||
values = append(values, binds...)
|
||||
|
||||
sql4prepare = NamedSprintf("UPDATE :tables SET :updates :wheres", tables, strings.Join(updates, ", "), wheres)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (b Builder) toSqlDelete(c *builder.Context) (sql4prepare string, values []any, err error) {
|
||||
var tables string
|
||||
tables, _, err = b.ToSqlTable(c)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
wheres, binds, err := b.ToSqlWhere(c)
|
||||
if err != nil {
|
||||
return sql4prepare, values, err
|
||||
}
|
||||
values = append(values, binds...)
|
||||
sql4prepare = NamedSprintf("DELETE FROM :tables :wheres", tables, wheres)
|
||||
return
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package sqlite3
|
||||
|
||||
import (
|
||||
//_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/gohouse/gorose/v3/driver"
|
||||
"github.com/gohouse/gorose/v3/driver/mysql"
|
||||
)
|
||||
|
||||
const DriverName = "sqlite3"
|
||||
|
||||
type Builder struct {
|
||||
//prefix string
|
||||
mysql.Builder
|
||||
}
|
||||
|
||||
func init() {
|
||||
driver.Register(DriverName, &Builder{})
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package mysql
|
||||
package driver
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@@ -10,6 +10,14 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func IsExpression(obj any) (b bool) {
|
||||
rfv := reflect.Indirect(reflect.ValueOf(obj))
|
||||
if rfv.Kind() == reflect.String && strings.Contains(rfv.String(), "?") {
|
||||
b = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func BackQuotes(arg any) string {
|
||||
var tmp []string
|
||||
if v, ok := arg.(string); ok {
|
||||
@@ -56,18 +64,19 @@ func TrimPrefixAndOr(s string) string {
|
||||
return regexp.MustCompile(`(?i)^\s*(and|or)\s+`).ReplaceAllString(s, "")
|
||||
}
|
||||
|
||||
func assertsEqual(t *testing.T, expect, real any) {
|
||||
func AssertsEqual(t *testing.T, expect, real any) {
|
||||
marshal, err := json.Marshal(expect)
|
||||
assertsError(t, err)
|
||||
AssertsError(t, err)
|
||||
bytes, err := json.Marshal(real)
|
||||
assertsError(t, err)
|
||||
AssertsError(t, err)
|
||||
if string(marshal) != string(bytes) {
|
||||
methodName, file, line := getCallerInfo()
|
||||
t.Errorf("[%s] Error\n\t Trace - %s:%v\n\tExpect - %T %s\n\t Got - %T %s\n------------------------------------------------------", methodName, file, line, expect, marshal, real, bytes)
|
||||
t.Errorf("[%s] Error\n\t Trace - %s:%v\n\tExpect - %T %s\n\t Got - %T %s\n------------------------------------------------------",
|
||||
methodName, file, line, expect, marshal, real, bytes)
|
||||
}
|
||||
}
|
||||
|
||||
func assertsError(t *testing.T, err error) {
|
||||
func AssertsError(t *testing.T, err error) {
|
||||
if err != nil {
|
||||
methodName, file, line := getCallerInfo()
|
||||
t.Errorf("[%s] Error\n\t Trace - %s:%v\n\t%s\n------------------------------------------------------", methodName, file, line, err.Error())
|
||||
@@ -99,7 +108,7 @@ func getCallerInfo() (string, string, int) {
|
||||
|
||||
func jsonLog(t *testing.T, data any) {
|
||||
marshal, err := json.Marshal(data)
|
||||
assertsError(t, err)
|
||||
AssertsError(t, err)
|
||||
t.Logf("json data: %s", marshal)
|
||||
}
|
||||
|
||||
2
engin.go
2
engin.go
@@ -66,7 +66,7 @@ func (s *Engin) SavePoint(name any) (err error) {
|
||||
return
|
||||
}
|
||||
func (s *Engin) RollbackTo(name any) (err error) {
|
||||
_, err = s.tx.Exec("ROLLBACK TO ?", name)
|
||||
_, err = s.tx.Exec("ROLLBACK TO SAVEPOINT ?", name)
|
||||
return
|
||||
}
|
||||
func (s *Engin) Rollback() (err error) {
|
||||
|
||||
42
gorose.go
42
gorose.go
@@ -2,21 +2,19 @@ package gorose
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"github.com/gohouse/gorose/v3/builder"
|
||||
_ "github.com/gohouse/gorose/v3/driver/mysql"
|
||||
)
|
||||
|
||||
type GoRose struct {
|
||||
Cluster *ConfigCluster
|
||||
master []*sql.DB
|
||||
slave []*sql.DB
|
||||
driver string
|
||||
prefix string
|
||||
handlers HandlersChain
|
||||
Cluster *ConfigCluster
|
||||
master []*sql.DB
|
||||
slave []*sql.DB
|
||||
driver string
|
||||
prefix string
|
||||
//handlers HandlersChain
|
||||
}
|
||||
|
||||
type HandlerFunc func(*builder.Context)
|
||||
type HandlersChain []HandlerFunc
|
||||
//type HandlerFunc func(*builder.Context)
|
||||
//type HandlersChain []HandlerFunc
|
||||
|
||||
//const abortIndex int8 = math.MaxInt8 >> 1
|
||||
|
||||
@@ -33,10 +31,10 @@ type HandlersChain []HandlerFunc
|
||||
// }
|
||||
//}
|
||||
|
||||
func (g *GoRose) Use(h ...HandlerFunc) *GoRose {
|
||||
g.handlers = append(g.handlers, h...)
|
||||
return g
|
||||
}
|
||||
//func (g *GoRose) Use(h ...HandlerFunc) *GoRose {
|
||||
// g.handlers = append(g.handlers, h...)
|
||||
// return g
|
||||
//}
|
||||
|
||||
//func (dg *GoRose) Use(h ...HandlerFunc) *GoRose {
|
||||
//}
|
||||
@@ -69,16 +67,18 @@ func Open(conf ...any) *GoRose {
|
||||
return &g
|
||||
}
|
||||
g.master, g.slave = g.Cluster.init()
|
||||
} else {
|
||||
g.driver = "mysql" // for toSql test
|
||||
} else if dr, ok := conf[0].(string); ok {
|
||||
g.driver = dr // for toSql test
|
||||
}
|
||||
case 2:
|
||||
g.driver = conf[0].(string)
|
||||
db, err := sql.Open(g.driver, conf[1].(string))
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
g.master = append(g.master, db)
|
||||
if conf[1].(string) != "" {
|
||||
db, err := sql.Open(g.driver, conf[1].(string))
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
g.master = append(g.master, db)
|
||||
} // else: for toSql test
|
||||
default:
|
||||
panic("config must be *gorose.ConfigCluster or sql.Open() origin params")
|
||||
}
|
||||
|
||||
30
readme.md
30
readme.md
@@ -1,13 +1,15 @@
|
||||
# GoRose ORM V3
|
||||
PHP Laravel ORM 的 go 实现, 与 laravel 官方文档保持一致 https://laravel.com/docs/10.x/queries .
|
||||
分为 go 风格 (struct 结构绑定用法) 和 php 风格 (map 结构用法).
|
||||
php 风格用法, 完全可以使用 laravel query builder 的文档做参考, 尽量做到 1:1 还原.
|
||||
php 风格用法, 完全可以使用 laravel query builder 的文档做参考, 尽量做到 1:1 还原.
|
||||
|
||||
## GoRose V3 架构图
|
||||

|
||||
|
||||
## 安装
|
||||
目前还处于beta阶段, 请谨慎使用. 由于没有打 tag, 只能使用 go mod 的方式引入
|
||||
```shell
|
||||
# go.mod
|
||||
|
||||
require github.com/gohouse/gorose/v3 master
|
||||
```
|
||||
|
||||
@@ -57,18 +59,21 @@ func main() {
|
||||
db().Delete(&user)
|
||||
}
|
||||
```
|
||||
php风格用法 和 go风格用法对比
|
||||
php风格用法 和 go风格用法查询如下sql对比:
|
||||
```sql
|
||||
select id,name,email from users where id=1 and name="test"
|
||||
```
|
||||
```go
|
||||
// select id,name,email from users where id=1 and name="test"
|
||||
db().Table("users").
|
||||
// php 风格写法
|
||||
user,err := db().Table("users").
|
||||
Select("id","name","email").
|
||||
Where("id", "=", 1).Where("name", "test").
|
||||
First()
|
||||
// 等同于
|
||||
// go 风格写法
|
||||
var user = User{Id: 1, Name: "test"}
|
||||
db().To(&user)
|
||||
err := db().To(&user)
|
||||
```
|
||||
由此可以看出, 除了对 表 模型的绑定区别, 其他方法通用
|
||||
上边的两种用法结果相同,由此可以看出,除了对 表 模型的绑定区别, 其他方法通用
|
||||
|
||||
## 配置
|
||||
单数据库连接, 可以直接同官方接口一样用法
|
||||
@@ -105,6 +110,13 @@ var rose = gorose.Open(
|
||||
)
|
||||
```
|
||||
|
||||
## 驱动支持
|
||||
- MySQL
|
||||
- MsSQL
|
||||
- Postgresql
|
||||
- Oracle
|
||||
- Sqlite3
|
||||
|
||||
## 事务
|
||||
```go
|
||||
// 全自动事务, 有错误会自动回滚, 无错误会自动提交
|
||||
@@ -346,7 +358,7 @@ type Result struct {
|
||||
Bname string `db:"bname"`
|
||||
}
|
||||
var res Result
|
||||
// select a.id, a.name aname, b.name bname from a inner join b on a.id=b.id where a.id>1
|
||||
// select a.id, a.name aname, b.name bname from a inner join b on a.id=b.aid where a.id>1
|
||||
db().Table("a").Join("b", "a.id","b.aid").Select("a.id", "a.name aname","b.name bname").Where("a.id", ">", 1).Bind(&res)
|
||||
```
|
||||
查询字段的显示名字一定要跟 结构体的字段 tag(db) 名字相同, 否则不会被赋值
|
||||
|
||||
9
toSql.go
9
toSql.go
@@ -88,9 +88,14 @@ func (db *Database) ToSqlDelete(obj any, mustColumn ...string) (sqlSegment strin
|
||||
}
|
||||
|
||||
func (db *Database) ToSqlUpdate(obj any, mustColumn ...string) (sqlSegment string, binds []any, err error) {
|
||||
return db.Driver.ToSqlUpdate(db.Context, builder.TypeToSqlUpdateCase{obj, mustColumn})
|
||||
return db.Driver.ToSqlUpdate(db.Context, builder.TypeToSqlUpdateCase{BindOrData: obj, MustColumn: mustColumn})
|
||||
}
|
||||
|
||||
// ToSqlIncDec
|
||||
//
|
||||
// symbol: +/-
|
||||
// data: {count: 2} => count = count + 2
|
||||
func (db *Database) ToSqlIncDec(symbol string, data map[string]any) (sql4prepare string, values []any, err error) {
|
||||
//return db.Driver.ToSqlIncDec(db.Context, symbol, data)
|
||||
return db.Driver.ToSqlUpdate(db.Context, builder.TypeToSqlIncDecCase{symbol, data})
|
||||
return db.Driver.ToSqlUpdate(db.Context, builder.TypeToSqlIncDecCase{Symbol: symbol, Data: data})
|
||||
}
|
||||
|
||||
@@ -1,14 +1,91 @@
|
||||
package gorose
|
||||
|
||||
import (
|
||||
"github.com/gohouse/gorose/v3/driver"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDatabase_ToSql(t *testing.T) {
|
||||
//db := Open("mysql", "root:root@tcp(localhost:3306)/test?charset=utf8mb4&parseTime=true")
|
||||
//prepare, values, err := db.NewDatabase().Table("user").Where("id", 1).Where("name", "kk").Where("age", 18).Where("sex", 1).Where("status", 1).
|
||||
// Union(
|
||||
// db.NewDatabase().Table("card").Where("id", 1),
|
||||
// ).ToSql()
|
||||
//log.Println(prepare, values, err)
|
||||
type User struct {
|
||||
Id int64 `db:"id,pk"`
|
||||
Name string `db:"name"`
|
||||
}
|
||||
|
||||
// var dbg = Open("mysql") // just test toSql
|
||||
// var dbg = Open("postgresql") // just test toSql
|
||||
// var dbg = Open("mssql") // just test toSql
|
||||
// var dbg = Open("oracle") // just test toSql
|
||||
var dbg = Open("sqlite3") // just test toSql
|
||||
|
||||
func db() *Database {
|
||||
return dbg.NewDatabase()
|
||||
}
|
||||
|
||||
func TestDatabase_ToSqlTo(t *testing.T) {
|
||||
var user = User{Id: 1}
|
||||
prepare, values, err := db().ToSqlTo(&user)
|
||||
driver.AssertsError(t, err)
|
||||
//db().Driver.Dialect.Placeholder()
|
||||
var expect = map[string]string{
|
||||
"mysql": "SELECT `id`, `name` FROM `User` WHERE `id` = ? LIMIT 1",
|
||||
"postgresql": `SELECT "id", "name" FROM "User" WHERE "id" = $1 LIMIT 1`,
|
||||
"mssql": `SELECT [id], [name] FROM [User] WHERE [id] = @p1 OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY`,
|
||||
"oracle": `SELECT "id", "name" FROM "User" WHERE "id" = @p1 OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY`,
|
||||
"sqlite3": `SELECT "id", "name" FROM "User" WHERE "id" = ? LIMIT 1`,
|
||||
}
|
||||
driver.AssertsEqual(t, expect[dbg.driver], prepare)
|
||||
var expectValues = []interface{}{1}
|
||||
driver.AssertsEqual(t, expectValues, values)
|
||||
}
|
||||
func TestDatabase_ToSqlToSlice(t *testing.T) {
|
||||
var user []User
|
||||
prepare, values, err := db().Where("id", ">", 1).OrderBy("id").Limit(10).Page(2).ToSqlTo(&user)
|
||||
driver.AssertsError(t, err)
|
||||
var expect = "SELECT `id`, `name` FROM `User` WHERE `id` > ? ORDER BY `id` LIMIT 10 OFFSET 10"
|
||||
driver.AssertsEqual(t, expect, prepare)
|
||||
var expectValues = []interface{}{1}
|
||||
driver.AssertsEqual(t, expectValues, values)
|
||||
}
|
||||
func TestDatabase_ToSql(t *testing.T) {
|
||||
prepare, values, err := db().Table("users").Select("b").Where("c", 1).GroupBy("a").Having("a", 1).OrderBy("id").Limit(10).Page(2).ToSql()
|
||||
driver.AssertsError(t, err)
|
||||
var expect = "SELECT `b` FROM `users` WHERE `c` = ? GROUP BY `a` HAVING `a` = ? ORDER BY `id` LIMIT 10 OFFSET 10"
|
||||
driver.AssertsEqual(t, expect, prepare)
|
||||
var expectValues = []int{1, 1}
|
||||
driver.AssertsEqual(t, expectValues, values)
|
||||
}
|
||||
func TestDatabase_ToSqlInsert(t *testing.T) {
|
||||
var user = User{Name: "john"}
|
||||
prepare, values, err := db().ToSqlInsert(&user)
|
||||
driver.AssertsError(t, err)
|
||||
var expect = "INSERT INTO `User` (`name`) VALUES (?)"
|
||||
driver.AssertsEqual(t, expect, prepare)
|
||||
var expectValues = []string{"john"}
|
||||
driver.AssertsEqual(t, expectValues, values)
|
||||
}
|
||||
func TestDatabase_ToSqlInserts(t *testing.T) {
|
||||
var user = []User{{Name: "John"}, {Name: "Alice"}}
|
||||
prepare, values, err := db().ToSqlInsert(&user)
|
||||
driver.AssertsError(t, err)
|
||||
var expect = "INSERT INTO `User` (`name`) VALUES (?),(?)"
|
||||
driver.AssertsEqual(t, expect, prepare)
|
||||
var expectValues = []string{"John", "Alice"}
|
||||
driver.AssertsEqual(t, expectValues, values)
|
||||
}
|
||||
func TestDatabase_ToSqlUpdate(t *testing.T) {
|
||||
var user = User{Id: 1, Name: "john"}
|
||||
prepare, values, err := db().ToSqlUpdate(&user)
|
||||
driver.AssertsError(t, err)
|
||||
var expect = "UPDATE `User` SET `name` = ? WHERE `id` = ?"
|
||||
driver.AssertsEqual(t, expect, prepare)
|
||||
var expectValues = []any{"john", 1}
|
||||
driver.AssertsEqual(t, expectValues, values)
|
||||
}
|
||||
func TestDatabase_ToSqlDelete(t *testing.T) {
|
||||
var user = User{Id: 1}
|
||||
prepare, values, err := db().ToSqlDelete(&user, "name")
|
||||
driver.AssertsError(t, err)
|
||||
var expect = "DELETE FROM `User` WHERE `id` = ? AND `name` = ?"
|
||||
driver.AssertsEqual(t, expect, prepare)
|
||||
var expectValues = []any{1, ""}
|
||||
driver.AssertsEqual(t, expectValues, values)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user