mirror of
https://github.com/gohouse/gorose.git
synced 2025-09-27 12:12:16 +08:00
387 lines
12 KiB
Go
387 lines
12 KiB
Go
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) {
|
|
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
|
|
}
|
|
where, val, err := b.ToSqlWhere(c)
|
|
if err != nil {
|
|
return sql4prepare, values, err
|
|
}
|
|
values = append(values, anies...)
|
|
values = append(values, val...)
|
|
|
|
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)
|
|
}
|
|
sql4prepare = fmt.Sprintf("UPDATE %s SET %s %s", prepare, strings.Join(tmp, ","), where)
|
|
return
|
|
}
|