Golang中Sq:类型安全的SQL查询构建器与结构体映射工具

Golang中Sq:类型安全的SQL查询构建器与结构体映射工具 GitHub

bokwoon95/go-structured-query

Go 语言类型安全的 SQL 构建器和结构体映射器。在 GitHub 上为 bokwoon95/go-structured-query 开发做出贡献。

在 Go 中直接使用 SQL(通过 database/sql)简单直接。然而 database/sql 本身存在一些缺陷。以下是你可能希望使用 sq 查询构建器(它是 database/sql 的一个薄包装)的一些原因:

database/sql 查询容易因拼写错误而出错,并且如果在迁移中更改了列名或类型,重构起来会很繁琐。

  • sq 从你的表生成结构体定义,以便你可以以类型安全的方式引用表列。
    • 如果迁移更改了列名或类型,它会导致你的应用程序在编译时出错,直到你更改代码中的所有相关部分。

database/sql 查询涉及大量样板代码,尤其是在将列扫描到结构体字段时。

  • sq 允许你在映射器函数中声明式地将表列映射到结构体字段,并在查询中重复使用它。
    • 是的,就像 sqlx 一样。不同的是,sqlx 涉及编写基于字符串的结构体注解,这仍然容易产生拼写错误。sq 使用它从你的表生成的类型安全定义。
    • 此外,sqlx 仍然需要你手动写出要 SELECT 的列,它自动化的唯一事情是将选定的列映射到结构体字段。
    • sq 的映射器函数同时服务于 SELECT 列将它们映射到结构体字段的目的,这就像是增强版的 sqlx
    • 更多信息

database/sql(和 sqlx)不处理动态构建 SQL 查询。

查询示例

SELECT

-- SQL
SELECT u.user_id, u.name, u.email, u.created_at
FROM public.users AS u
WHERE u.name = 'Bob';
// Go
u := tables.USERS().As("u") // table is code generated
var user User
var users []User
err := sq.
    From(u).
    Where(u.NAME.EqString("Bob")).
    Selectx(func(row *sq.Row) {
        user.UserID = row.Int(u.USER_ID)
        user.Name = row.String(u.NAME)
        user.Email = row.String(u.EMAIL)
        user.CreatedAt = row.Time(u.CREATED_AT)
    }, func() {
        users = append(users, user)
    }).
    Fetch(db)
if err != nil {
    // handle error
}

INSERT

-- SQL
INSERT INTO public.users (name, email)
VALUES ('Bob', 'bob@email.com'), ('Alice', 'alice@email.com'), ('Eve', 'eve@email.com');
// Go
u := tables.USERS().As("u") // table is code generated
users := []User{
    {Name: "Bob",   Email: "bob@email.com"},
    {Name: "Alice", Email: "alice@email.com"},
    {Name: "Eve  ", Email: "eve@email.com"},
}
rowsAffected, err := sq.
    InsertInto(u).
    Valuesx(func(col *sq.Column) {
        for _, user := range users {
            col.SetString(u.NAME, user.Name)
            col.SetString(u.EMAIL, user.Email)
        }
    }).
    Exec(db, sq.ErowsAffected)
if err != nil {
    // handle error
}

UPDATE

-- SQL
UPDATE public.users
SET name = 'Bob', password = 'qwertyuiop'
WHERE email = 'bob@email.com';
// Go
u := tables.USERS().As("u") // table is code generated
user := User{
    Name:     "Bob",
    Email:    "bob@email.com",
    Password: "qwertyuiop",
}
rowsAffected, err := sq.
    Update(u).
    Setx(func(col *sq.Column) {
        col.SetString(u.NAME, user.Name)
        col.SetString(u.PASSWORD, user.Password)
    }).
    Where(u.EMAIL.EqString(user.Email)).
    Exec(db, sq.ErowsAffected)
if err != nil {
    // handle error
}

DELETE

-- SQL
DELETE FROM public.users AS u
USING public.user_roles AS ur
JOIN public.user_roles_students AS urs ON urs.user_role_id = ur.user_role_id
WHERE u.user_id = ur.user_id AND urs.team_id = 15;
// Go
u   := tables.USERS().As("u")                 // tables are code generated
ur  := tables.USER_ROLES().As("ur")           // tables are code generated
urs := tables.USER_ROLES_STUDENTS().As("urs") // tables are code generated
rowsAffected, err := sq.
    DeleteFrom(u).
    Using(ur).
    Join(urs, urs.USER_ROLE_ID.Eq(ur.USER_ROLE_ID)).
    Where(
        u.USER_ID.Eq(ur.USER_ID),
        urs.TEAM_ID.EqInt(15),
    ).
    Exec(db, sq.ErowsAffected)
if err != nil {
    // handle error
}

欲了解更多信息,请查看 基础

如需查询示例列表,请查看 查询构建


更多关于Golang中Sq:类型安全的SQL查询构建器与结构体映射工具的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang中Sq:类型安全的SQL查询构建器与结构体映射工具的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


sq 确实是一个优秀的类型安全 SQL 构建器,它通过代码生成解决了 database/sql 在类型安全和重构方面的痛点。以下是一些关键优势的补充说明:

类型安全的列引用

通过生成的表结构体,列引用在编译时就能发现错误:

// 错误示例:如果列名在数据库迁移中已更改,这里会编译失败
err := sq.From(u).
    Where(u.OLD_COLUMN_NAME.EqString("value")). // 编译错误:OLD_COLUMN_NAME 不存在
    Fetch(db)

动态查询构建

sq 支持复杂的动态查询构建,这是原生 database/sql 难以实现的:

func buildDynamicQuery(filters map[string]interface{}) sq.SelectQuery {
    u := tables.USERS()
    query := sq.From(u)
    
    // 动态添加 WHERE 条件
    if name, ok := filters["name"]; ok {
        query = query.Where(u.NAME.EqString(name.(string)))
    }
    if email, ok := filters["email"]; ok {
        query = query.Where(u.EMAIL.EqString(email.(string)))
    }
    
    return query
}

// 使用动态查询
query := buildDynamicQuery(map[string]interface{}{
    "name": "Bob",
    "email": "bob@email.com",
})

批量操作优化

sqValuesx 方法特别适合批量操作,性能优于循环执行单条 SQL:

// 批量插入性能优化
users := []User{/* 大量用户数据 */}
rowsAffected, err := sq.
    InsertInto(u).
    Valuesx(func(col *sq.Column) {
        for _, user := range users {
            col.SetString(u.NAME, user.Name)
            col.SetString(u.EMAIL, user.Email)
            col.SetTime(u.CREATED_AT, time.Now())
        }
    }).
    Exec(db, sq.ErowsAffected)

复杂 JOIN 查询

sq 的类型安全特性在复杂 JOIN 查询中尤为有用:

// 多表 JOIN 查询
u := tables.USERS().As("u")
p := tables.PROFILES().As("p")
o := tables.ORDERS().As("o")

err := sq.
    From(u).
    Join(p, p.USER_ID.Eq(u.USER_ID)).
    LeftJoin(o, o.USER_ID.Eq(u.USER_ID)).
    Where(
        u.ACTIVE.IsBool(true),
        p.COUNTRY.EqString("US"),
        o.STATUS.EqString("completed"),
    ).
    Selectx(func(row *sq.Row) {
        // 类型安全的字段访问
        userID := row.Int(u.USER_ID)
        userName := row.String(u.NAME)
        profileBio := row.StringNull(p.BIO)
        orderAmount := row.FloatNull(o.AMOUNT)
    }, func() {
        // 处理每一行
    }).
    Fetch(db)

子查询支持

sq 天然支持子查询,保持类型安全:

// 使用子查询
subquery := sq.
    Select(tables.ORDERS().USER_ID).
    From(tables.ORDERS()).
    Where(tables.ORDERS().AMOUNT.GtFloat(1000))

err := sq.
    From(u).
    Where(u.USER_ID.In(subquery)).
    Selectx(func(row *sq.Row) {
        // 选择高价值用户
    }, func() {
        // 处理结果
    }).
    Fetch(db)

sq 的设计确实解决了 Go 中 SQL 操作的几个核心问题:类型安全、重构友好性和动态查询构建。它的代码生成方式虽然增加了构建步骤,但换来了更好的开发体验和更少的运行时错误。

回到顶部