RawQuerier 原生查询

RawQuerier 原生查询

语法分析

就 MySQL SELECT 语句来说,ORM 框架能支持全部 语法吗? 显然不能,也不愿意支持全部 , 也不仅仅是 ORM 框架,大多数框架设计的时候,都要考 虑提供兜底的措施,或者提供绕开你的框架的机制。 在 ORM 这里,就是要允许用户手写 SQL,直接绕开 ORM 的各种机制。
image.png

开源实例

Beego ORM

image.png
image.png
Beego ORM 的原生查询接口是,直接让用户传入 sql 语句与对应的参数,然后返回一个原生查询的抽象 RawSeter,该抽象提供了众多的方法支持。

GORM

image.png
image.png
image.png
image.png
GORM 也是依赖于用户传 sql 语句与参数,但是返回给用户的是 DB,用户负责调用目标获取结果的接口,例如: Row()、Rows()、Scan() 等方法。

type Result struct {
  ID   int
  Name string
  Age  int
}

var result Result
db.Raw("SELECT id, name, age FROM users WHERE name = ?", 3).Scan(&result)

db.Raw("SELECT id, name, age FROM users WHERE name = ?", 3).Scan(&result)

var age int
db.Raw("SELECT SUM(age) FROM users WHERE role = ?", "admin").Scan(&age)

var users []User
db.Raw("UPDATE users SET name = ? WHERE age = ? RETURNING id, name", "jinzhu", 20).Scan(&users)

// 使用原生 SQL
row := db.Raw("select name, age, email from users where name = ?", "jinzhu").Row()
row.Scan(&name, &age, &email)

db.Exec("DROP TABLE users")
db.Exec("UPDATE orders SET shipped_at = ? WHERE id IN ?", time.Now(), []int64{1, 2, 3})

// Exec with SQL Expression
db.Exec("UPDATE users SET money = ? WHERE name = ?", gorm.Expr("money * ? + ?", 10000, 1), "jinzhu")

API 设计

本文主要支持中间这种。 第三种用户可以直接使用 sql.DB,都用不着 ORM 框架
image.png

var _ Querier[any] = &RawQuerier[any]{}

// RawQuerier 原生查询器
type RawQuerier[T any] struct {
	core
	sess session
	sql string
	args []any
}

func (r *RawQuerier[T]) Exec(ctx context.Context) Result {
	// TODO implement me
	panic("implement me")
}

func (r *RawQuerier[T]) Get(ctx context.Context) (*T, error) {
	// TODO implement me
	panic("implement me")
}

func (r *RawQuerier[T]) GetMulti(ctx context.Context) ([]*T, error) {
	// TODO implement me
	panic("implement me")
}

具体实现

其中exec 和 get 两个方法是从我们原本的 Selector 与 Updateor 等实现里面抽取出来的。 目的是为了实现
Executor、Querier 等接口,另外为了兼容 Seletor 等构造模式的构造方式,也必须实现 QueryBuilder 接口。

var _ Querier[any] = &RawQuerier[any]{}

// RawQuerier 原生查询器
type RawQuerier[T any] struct {
	core
	sess session
	sql  string
	args []any
}

// RawQuery 创建一个 RawQuerier 实例
// 泛型参数 T 是目标类型。
// 例如,如果查询 User 的数据,那么 T 就是 User
func RawQuery[T any](sess session, sql string, args ...any) *RawQuerier[T] {
	return &RawQuerier[T]{
		core: sess.getCore(),
		sess: sess,
		sql:  sql,
		args: args,
	}
}

func (r *RawQuerier[T]) Build() (*Query, error) {
	return &Query{
		SQL:  r.sql,
		Args: r.args,
	}, nil
}

func (r *RawQuerier[T]) Get(ctx context.Context) (*T, error) {
	//  当通过 RawQuery 方法调用 Get ,如果 T 是 time.Time, sql.Scanner 的实现,
	//  内置类型或者基本类型时, 在这里都会报错,但是这种情况我们认为是可以接受的
	//  所以在此将报错忽略,因为基本类型取值用不到 meta 里的数据
	model, _ := r.r.Get(new(T))
	res := get[T](ctx, r.core, r.sess, &QueryContext{
		Builder: r,
		Type:    "RAW",
		Meta:    model,
	})
	if res.Err != nil {
		return nil, res.Err
	}
	return res.Result.(*T), nil
}

func (r *RawQuerier[T]) GetMulti(ctx context.Context) ([]*T, error) {
	//  当通过 RawQuery 方法调用 Get ,如果 T 是 time.Time, sql.Scanner 的实现,
	//  内置类型或者基本类型时, 在这里都会报错,但是这种情况我们认为是可以接受的
	//  所以在此将报错忽略,因为基本类型取值用不到 meta 里的数据
	model, _ := r.r.Get(new(T))
	res := getMulti[T](ctx, r.core, r.sess, &QueryContext{
		Builder: r,
		Type:    "RAW",
		Meta:    model,
	})
	if res.Err != nil {
		return nil, res.Err
	}
	return res.Result.([]*T), nil
}

func (r *RawQuerier[T]) Exec(ctx context.Context) Result {
	return exec[T](ctx, r.core, r.sess, &QueryContext{
		Type:    "RAW",
		Builder: r,
	})
}

单元测试

func TestRawQuerier_Get(t *testing.T) {
	//mockDB, mock, err := sqlmock.New()
	mockDB, mock, err := sqlmock.New(
		sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
	if err != nil {
		t.Fatal(err)
	}
	defer func() { _ = mockDB.Close() }()
	db, err := OpenDB("mysql", mockDB)
	if err != nil {
		t.Fatal(err)
	}

	testCases := []struct {
		name      string
		queryRes  func(t *testing.T) any
		mockErr   error
		mockOrder func(mock sqlmock.Sqlmock)
		wantErr   error
		wantVal   any
	}{
		//返回原生基本类型
		{
			name: "res RawQuery int",
			queryRes: func(t *testing.T) any {
				queryer := RawQuery[int](db, "SELECT `age` FROM `test_model` LIMIT ?;", 1)
				result, err := queryer.Get(context.Background())
				require.NoError(t, err)
				return result
			},
			mockOrder: func(mock sqlmock.Sqlmock) {
				rows := mock.NewRows([]string{"age"}).AddRow(10)
				mock.ExpectQuery("SELECT `age` FROM `test_model` LIMIT ?;").
					WithArgs(1).
					WillReturnRows(rows)
			},
			wantVal: func() *int {
				val := 10
				return &val
			}(),
		},
		{
			name: "res RawQuery bytes",
			queryRes: func(t *testing.T) any {
				queryer := RawQuery[[]byte](db, "SELECT `first_name` FROM `test_model` WHERE `id`=? LIMIT ?;", 1, 1)
				result, err := queryer.Get(context.Background())
				require.NoError(t, err)
				return result
			},
			mockOrder: func(mock sqlmock.Sqlmock) {
				rows := mock.NewRows([]string{"first_name"}).AddRow([]byte("Li"))
				mock.ExpectQuery("SELECT `first_name` FROM `test_model` WHERE `id`=? LIMIT ?;").
					WithArgs(1, 1).
					WillReturnRows(rows)
			},
			wantVal: func() *[]byte {
				val := []byte("Li")
				return &val
			}(),
		},
		{
			name: "res RawQuery string",
			queryRes: func(t *testing.T) any {
				queryer := RawQuery[string](db, "SELECT `first_name` FROM `test_model` WHERE `id`=? LIMIT ?;", 1, 1)
				result, err := queryer.Get(context.Background())
				require.NoError(t, err)
				return result
			},
			mockOrder: func(mock sqlmock.Sqlmock) {
				rows := mock.NewRows([]string{"first_name"}).AddRow("Da")
				mock.ExpectQuery("SELECT `first_name` FROM `test_model` WHERE `id`=? LIMIT ?;").
					WithArgs(1, 1).
					WillReturnRows(rows)
			},
			wantVal: func() *string {
				val := "Da"
				return &val
			}(),
		},
		{
			name: "res RawQuery struct ptr",
			queryRes: func(t *testing.T) any {
				queryer := RawQuery[TestModel](db, "SELECT `first_name`,`age` FROM `test_model` WHERE `id`=? LIMIT ?;", 1, 1)
				result, err := queryer.Get(context.Background())
				require.NoError(t, err)
				return result
			},
			mockOrder: func(mock sqlmock.Sqlmock) {
				rows := mock.NewRows([]string{"first_name", "age"}).AddRow("Da", 18)
				mock.ExpectQuery("SELECT `first_name`,`age` FROM `test_model` WHERE `id`=? LIMIT ?;").
					WithArgs(1, 1).
					WillReturnRows(rows)
			},
			wantVal: func() *TestModel {
				return &TestModel{
					FirstName: "Da",
					Age:       18,
				}
			}(),
		},
		{
			name: "res RawQuery sql.NullString",
			queryRes: func(t *testing.T) any {
				queryer := RawQuery[sql.NullString](db, "SELECT `last_name` FROM `test_model` WHERE `id`=? LIMIT ?;", 1, 1)
				result, err := queryer.Get(context.Background())
				require.NoError(t, err)
				return result
			},
			mockOrder: func(mock sqlmock.Sqlmock) {
				rows := mock.NewRows([]string{"last_name"}).AddRow([]byte("ming"))
				mock.ExpectQuery("SELECT `last_name` FROM `test_model` WHERE `id`=? LIMIT ?;").
					WithArgs(1, 1).
					WillReturnRows(rows)
			},
			wantVal: func() *sql.NullString {
				return &sql.NullString{String: "ming", Valid: true}
			}(),
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			tc.mockOrder(mock)
			res := tc.queryRes(t)
			assert.Equal(t, tc.wantVal, res)
		})
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值