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 }