Skip to content

Commit

Permalink
internal/parser,v2/pkg/orm: support mysql functions and adds generic …
Browse files Browse the repository at this point in the history
…ToResult helper

MySQL driver will scan `any` type field into `[]byte` as [it mentioned](go-sql-driver/mysql#441)
However, we cannot determine the underlying type of every mysql functions currently.
We need a helper function to generic the `[]byte` -> wellknown SQL type(e.g.: `NullInt64`) to avoid spamming the conversion code all over our business codebase.
  • Loading branch information
scbizu committed Jul 26, 2023
1 parent e8af3c5 commit 1486b7a
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 0 deletions.
62 changes: 62 additions & 0 deletions e2e/mysqlr/gen_methods.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,65 @@ func (m *sqlMethods) Blog(ctx context.Context, req *BlogReq, opts ...RawQueryOpt
}
return results, nil
}

type BlogFunctionResp struct {
Count any `sql:"count"`
Title any `sql:"title"`
}

type BlogFunctionReq struct {
Id int64 `sql:"id"`
}

func (req *BlogFunctionReq) Params() []any {
var params []any

if req.Id != 0 {
params = append(params, req.Id)
}

return params
}

func (req *BlogFunctionReq) Condition() string {
var conditions []string
if req.Id != 0 {
conditions = append(conditions, "id = ?")
}
var query string
if len(conditions) > 0 {
query += " WHERE " + strings.Join(conditions, " AND ")
}
return query
}

const _BlogFunctionSQL = "SELECT COUNT(`id`) AS `count`,UPPER(`title`) AS `title` FROM `blogs` %s"

// BlogFunction is a raw query handler generated function for `e2e/mysqlr/sqls/blog_function.sql`.
func (m *sqlMethods) BlogFunction(ctx context.Context, req *BlogFunctionReq, opts ...RawQueryOptionHandler) ([]*BlogFunctionResp, error) {

rawQueryOption := &RawQueryOption{}

for _, o := range opts {
o(rawQueryOption)
}

query := fmt.Sprintf(_BlogFunctionSQL, req.Condition())

rows, err := db.GetMysql(db.WithDB(rawQueryOption.db)).QueryContext(ctx, query, req.Params()...)
if err != nil {
return nil, err
}
defer rows.Close()

var results []*BlogFunctionResp
for rows.Next() {
var o BlogFunctionResp
err = rows.Scan(&o.Count, &o.Title)
if err != nil {
return nil, err
}
results = append(results, &o)
}
return results, nil
}
16 changes: 16 additions & 0 deletions e2e/mysqlr/mysqlr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"testing"
"time"

"github.com/ezbuy/ezorm/v2/pkg/orm"
"github.com/stretchr/testify/assert"
)

Expand Down Expand Up @@ -160,6 +161,21 @@ func TestBlogsCRUD(t *testing.T) {
assert.Equal(t, 0, len(resp))
})

t.Run("MySQLFunction", func(t *testing.T) {
resps, err := GetRawQuery().BlogFunction(ctx, &BlogFunctionReq{
Id: 0,
}, WithDB(db.DB))
assert.NoError(t, err)
assert.Equal(t, 1, len(resps))
resp := resps[0]
i, err := orm.ToResult[sql.NullInt64](resp.Count)
assert.NoError(t, err)
assert.Equal(t, int64(1), i)
s, err := orm.ToResult[sql.NullString](resp.Title)
assert.NoError(t, err)
assert.Equal(t, "TEST", s)
})

t.Run("Delete", func(t *testing.T) {
af, err := BlogDBMgr(db).DeleteByPrimaryKey(ctx, 1, 1)
assert.NoError(t, err)
Expand Down
7 changes: 7 additions & 0 deletions e2e/mysqlr/sqls/blog_function.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
SELECT
COUNT(id) as count,
UPPER(title) as title
FROM
blogs
WHERE
id > 1;
23 changes: 23 additions & 0 deletions internal/parser/x/query/tidb_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,29 @@ func (tp *TiDBParser) parse(node ast.Node, n int) error {
}
}
}
if expr, ok := f.Expr.(*ast.FuncCallExpr); ok {
field := &QueryField{
Alias: f.AsName.String(),
}
var txt bytes.Buffer
txt.WriteString(expr.FnName.O)
for _, args := range expr.Args {
txt.WriteString("_")
var arg strings.Builder
args.Format(&arg)
txt.WriteString(arg.String())
}
field.Name = txt.String()
field.Type = T_ANY
if len(expr.Args) > 0 {
for _, arg := range expr.Args {
if col, ok := arg.(*ast.ColumnNameExpr); ok {
tp.meta.AppendResult(col.Name.Table.String(), field)
tp.b.resultFields = append(tp.b.resultFields, field)
}
}
}
}
if expr, ok := f.Expr.(*ast.ColumnNameExpr); ok {
field := &QueryField{
Alias: f.AsName.String(),
Expand Down
46 changes: 46 additions & 0 deletions v2/pkg/orm/any.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package orm

import (
"database/sql"
"errors"
)

type Result interface {
sql.NullInt64 | sql.NullString | sql.NullBool | sql.NullFloat64
}

func ToResult[T Result](rawField any) (T, error) {
var t T
switch rawField := rawField.(type) {
default:
return t, errors.New("rawField type should be []byte as scan result")
case []byte:
switch any(t).(type) {
case sql.NullInt64:
i := &sql.NullInt64{}
if err := i.Scan(rawField); err != nil {
return t, err
}
t = any(*i).(T)
case sql.NullString:
s := &sql.NullString{}
if err := s.Scan(rawField); err != nil {
return t, err
}
t = any(*s).(T)
case sql.NullBool:
b := &sql.NullBool{}
if err := b.Scan(rawField); err != nil {
return t, err
}
t = any(*b).(T)
case sql.NullFloat64:
f := &sql.NullFloat64{}
if err := f.Scan(rawField); err != nil {
return t, err
}
t = any(*f).(T)
}
}
return t, nil
}

0 comments on commit 1486b7a

Please sign in to comment.