Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature Request] Enhanced ORM code generation support. #2881

Closed
hinego opened this issue Aug 18, 2023 · 2 comments
Closed

[Feature Request] Enhanced ORM code generation support. #2881

hinego opened this issue Aug 18, 2023 · 2 comments
Labels
discuss We need discuss to make decision. inactive

Comments

@hinego
Copy link
Contributor

hinego commented Aug 18, 2023

现在goframe各个方面我都感觉非常好用了,唯一就是ORM这块还不够好用

现在提出一些优化/改进方案

以下参考gorm-gen / ent / queryx

package main

import (
	"github.com/sucold/starter/internal/dao"
	"github.com/sucold/starter/internal/dao/addresses"
	"github.com/sucold/starter/internal/dao/users"
	"gorm.io/gen"
	"gorm.io/gen/field"
)

func main() {
	data, _ := dao.QueryAddress.Where(addresses.ID.Eq(1)).First()
	dao.QueryAddress.Where(addresses.ID.Eq(1)).UpdateSimple(addresses.Address.Value("11"), addresses.Private.Value("123123"))
	var (
		where = []gen.Condition{
			addresses.ID.Eq(1),
			addresses.Type.Eq(2),
		}
		update = []field.AssignExpr{
			addresses.Address.Value("123123"),
			addresses.Private.Value("123123"),
		}
	)
	dao.QueryAddress.Where(where...).UpdateSimple(update...)
	data.QueryUser() // 可以通过模型获取到任何有关联关系的快捷查询 相当于如下
	dao.QueryUser.Where(users.ID.Eq(data.UserID))

	//下面的两条语句是等价的
	data.QueryUser().First()
	dao.QueryUser.Where(users.ID.Eq(data.UserID)).First()

	//当前不仅如此还可以实现快捷查询结果,以下两条语句是等价的
	user, err := data.GetUser()
	user, err := dao.QueryUser.Where(users.ID.Eq(data.UserID)).First()

	//也可以直接省略错误,如果查询不到会直接panic
	user := data.GetUserX()

	//同样的在User之上应该有它关联的全部查询
	user.QueryAddress() //等价于
	dao.QueryAddress.Where(addresses.UserID.Eq(user.ID))
	user.QueryAddress().Find() //获取此用户的全部地址,等价于
	dao.QueryAddress.Where(addresses.UserID.Eq(user.ID)).Find()

	// 关于关联查询和预加载
	/*
		所有有关联关系的都应该实现快捷关联查询(例如 用户和地址 可以通过地址快捷查询用户,也可以通过用户快捷查询地址)

		需要实现预加载的需要手动标注,然后可以在生成模型上添加预加载的字段

		预加载的字段可以参考ent的实现添加到一个edges的结构体中,例如
		type AddressEdges struct {
			User *User
		}
		然后在生成的模型上添加一个字段
		Edges AddressEdges
		当然访问它们时,还是应该通过GetUser() GetUserX() 来访问,预加载只是可以把查询结果缓存到Edges中,不会每次调用时都需要查询数据库

		当然,可以把Edges小写,不暴露于外,然后把所有的关联模型都放到Edge结构体中,这样可以缓存全部
		而查询时也可以通过传入一个可选的update参数来忽略缓存,例如
		func (r *Address) GetUser(update ...bool) (*User, error) {
			if len(update) == 0 && r.edges.User != nil {
				return r.edges.User, nil
			}
			var data, err = QueryUser.Key(r.UserID).First()
			r.edges.User = data
			return r.edges.User, err
		}


	*/
	addr, err := dao.QueryAddress.WithUser().First()
	addr.GetUserX() //这里就不会再次查询数据库了,因为WithUser已经预加载了,所以可以直接获取
	/*
		PS: GetUser和GetUserX 可以传入一个可选的update函数,如果传入后即使已经加载过也可以强制查询最新的数据
	*/

	//链式的各种方法必然也是都需要的,例如
	dao.QueryAddress.Where().Limit(1).Select().First()

	//此外,应该对单主键支持快捷查询,例如
	dao.QueryAddress.Get(1)
	dao.QueryAddress.Key(1).First()
	dao.QueryAddress.Key(1).UpdateSimple(addresses.Address.Value("123123"))
}


关于实现方案,我目前想到两个方案
1. 参考queryx使用的hcl进行数据库结构描述,关联关系描述,自定义结构体描述,然后通过hcl解析器解析出来,然后生成模型
2. 直接通过golang的结构体描述,参考:

type Type struct {
	Type     string //此类型在数据库中的类型
	Postgres string //在Postgres中的类型
	Mysql    string //在Mysql中的类型
	Sqlite   string //在Sqlite中的类型
	Dest     any    // 此类型在golang中的类型,直接传入该类型即可,例如 decimal.Decimal{} 或者 int64(0) 然后通过反射获取到类型和包名
}

type Column struct {
	Name    string            //字段的名称
	Type    *Type             //字段的类型
	Tags    map[string]string //给结构体的Tags
	Index   bool              //此字段是否索引
	Unique  bool              //此字段是否唯一
	Uniques []string          //联合唯一索引 例如:[]string{"type_index"}
}
type Relation struct {
	Type       string // belongs_to has_one has_many,一旦创建A->B的关系,B->A的关系也会自动创建
	Dest       any    // 关联的对象
	Query      bool   //是否创建Query功能
	ForeignKey bool   //是否创建外键
}
type Model struct {
	Name     string
	Column   []Column
	Relation []Relation
}


//这样做的优势在于生成时不需要去连接数据库获取什么东西,很快就能生成完毕
//(最好是在10秒之内)
@Issues-translate-bot
Copy link

Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿


Now I feel that goframe is very easy to use in all aspects, the only thing is that the ORM is not easy to use.

Now come up with some optimization/improvement proposals

The following reference gorm-gen/ent/queryx

package main

import (
"github.com/sucold/starter/internal/dao"
"github.com/sucold/starter/internal/dao/addresses"
"github.com/sucold/starter/internal/dao/users"
"gorm.io/gen"
"gorm.io/gen/field"
)

func main() {
data, _ := dao.QueryAddress.Where(addresses.ID.Eq(1)).First()
dao.QueryAddress.Where(addresses.ID.Eq(1)).UpdateSimple(addresses.Address.Value("11"), addresses.Private.Value("123123"))
var (
where = []gen. Condition{
addresses.ID.Eq(1),
addresses.Type.Eq(2),
}
update = []field.AssignExpr{
addresses.Address.Value("123123"),
addresses. Private. Value("123123"),
}
)
dao.QueryAddress.Where(where...).UpdateSimple(update...)
data.QueryUser() // You can get any shortcut query related to the relationship through the model, which is equivalent to the following
dao.QueryUser.Where(users.ID.Eq(data.UserID))

//The following two statements are equivalent
data.QueryUser().First()
dao.QueryUser.Where(users.ID.Eq(data.UserID)).First()

//At present, not only that, but also quick query results can be realized. The following two statements are equivalent
user, err := data. GetUser()
user, err := dao.QueryUser.Where(users.ID.Eq(data.UserID)).First()

//You can also omit the error directly, if you can't find it, you will panic directly
user := data. GetUserX()

//The same should have all the queries associated with it on User
user.QueryAddress() //Equivalent to
dao.QueryAddress.Where(addresses.UserID.Eq(user.ID))
user.QueryAddress().Find() //Get all addresses of this user, equivalent to
dao.QueryAddress.Where(addresses.UserID.Eq(user.ID)).Find()

// About associated query and preloading
/*
All related relationships should implement quick related queries (for example, users and addresses can quickly query users through addresses, and can also quickly query addresses through users)

Those that need to be preloaded need to be manually marked, and then the preloaded fields can be added to the generated model

The preloaded fields can be added to an edges structure by referring to the implementation of ent, for example
type AddressEdges struct {
User *User
}
Then add a field on the generated model
Edges Address Edges
Of course, when accessing them, they should still be accessed through GetUser() GetUserX(). Preloading can only cache query results in Edges, and does not need to query the database every time it is called

Of course, you can put Edges in lowercase, not exposed to the outside world, and then put all associated models into the Edge structure, so that you can cache all
The query can also ignore the cache by passing in an optional update parameter, for example
func (r *Address) GetUser(update ...bool) (*User, error) {
if len(update) == 0 && r.edges.User != nil {
return r.edges.User, nil
}
var data, err = QueryUser.Key(r.UserID).First()
r.edges.User = data
return r.edges.User, err
}


*/
addr, err := dao.QueryAddress.WithUser().First()
addr.GetUserX() //The database will not be queried again here, because WithUser has been preloaded, so it can be obtained directly
/*
PS: GetUser and GetUserX can pass in an optional update function, and if it is passed in, it can be forced to query the latest data even if it has already been loaded
*/

//Chained methods are necessarily required, for example
dao.QueryAddress.Where().Limit(1).Select().First()

//In addition, shortcut queries should be supported for single primary keys, for example
dao. QueryAddress. Get(1)
dao.QueryAddress.Key(1).First()
dao.QueryAddress.Key(1).UpdateSimple(addresses.Address.Value("123123"))
}

@gqcn gqcn added the discuss We need discuss to make decision. label Aug 21, 2023
@shuqingzai
Copy link

relation #2831

@github-actions github-actions bot closed this as not planned Won't fix, can't repro, duplicate, stale Sep 27, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discuss We need discuss to make decision. inactive
Projects
None yet
Development

No branches or pull requests

4 participants