Skip to content

feat: add typed fluent query builders - #107

Closed
zhiquanchi wants to merge 3 commits into
fastapi-practices:masterfrom
zhiquanchi:master
Closed

feat: add typed fluent query builders#107
zhiquanchi wants to merge 3 commits into
fastapi-practices:masterfrom
zhiquanchi:master

Conversation

@zhiquanchi

Copy link
Copy Markdown

背景

当前 CRUDPlus 主要通过关键字字符串构造过滤条件,例如 name__likeage__ge。这种方式虽然便于快速开发,但字段名无法获得 IDE 补全、重命名支持和静态类型检查,字段写错通常只能在运行时发现。

本 PR 在保留现有 CRUD API 和字符串过滤兼容性的基础上,增加基于 SQLAlchemy 表达式的不可变链式查询构建器,并提供强类型 Lambda 写法。

主要变更

1. 新增链式查询 API

支持以下查询能力:

  • Lambda 字段表达式过滤
  • SQLAlchemy 原生表达式
  • 排序、limit、offset、分页
  • 字段投影和命名投影
  • countexistsfirstonescalar
  • maxminsumavg
  • JOIN 和 relationship 加载
  • distinctgroup_byhaving
  • 子查询、CTE、Union、Union All
  • for_update 和原生 statement 导出
  • to_sql SQL 预览

示例:

users = await (
    user_crud.query(session)
    .where(lambda user: user.is_active.is_(True))
    .where(lambda user: user.name.contains('张'))
    .order_by(lambda user: user.created_at.desc())
    .paginate(page=1, size=20)
)

2. 强类型字段表达式

Lambda 参数会根据 CRUDPlus[Model] 推断为对应模型类型:

rows = await (
    user_crud.query(session)
    .select(lambda user: {
        'user_id': user.id,
        'user_name': user.name,
    })
    .to_list()
)

不存在的模型字段可以由 IDE 和 ty 报告,而不是等到 SQL 执行时才发现。

JOIN 条件也支持双模型表达式:

rows = await (
    user_crud.query(session)
    .join(Post, lambda user, post: user.id == post.author_id)
    .where(Post.status == 'published')
    .select(User.name, Post.title)
    .to_list()
)

3. 链式写入

新增独立写入构建器:

  • update_query
  • delete_query
  • insert_query
  • upsert_query

同时支持从查询链直接更新或删除:

await (
    user_crud.query(session)
    .where(lambda user: user.id == user_id)
    .update(lambda user: {user.login_count: user.login_count + 1})
)

写入构建器支持:

  • 强类型 whereset
  • 批量插入
  • Upsert 冲突字段和更新字段
  • returning
  • flushcommit
  • PostgreSQL、SQLite、MySQL/MariaDB 的现有 Upsert 方言支持

4. 写入安全保护

链式 update/delete 没有显式过滤条件时默认抛出 UnsafeWriteError

await user_crud.update_query(session).set(...).execute()
# UnsafeWriteError

如果确实需要全表操作,必须显式调用:

await user_crud.update_query(session).set(...).allow_all().execute()

5. 软删除支持

启用 filter_deleted=True 后,链式查询、更新和删除都会自动排除软删除记录;可以使用 include_deleted() 显式包含已删除记录。

6. 文档和测试

  • 新增链式查询与写入文档
  • 新增业务 DAO 封装文档
  • 新增强类型 Lambda 类型检查样例
  • 补充分页、软删除、更新、Upsert 和链式构建器测试
  • 更新版本至 1.14.0

兼容性

  • 保留已有 create_modelselect_modelsupdate_model 等 API
  • 保留 field__operator 字符串过滤语法
  • 不改变现有事务提交行为,链式写入默认不自动提交
  • 新 API 为增量增加,不要求已有调用方迁移

验证结果

  • 384 passed
  • Ruff lint:通过
  • Ruff format:通过
  • 新增链式模块和类型样例 ty check:通过
  • 文档代码示例检查:通过

备注

Python 不支持像 C# 一样重载 and/or 生成 SQL 表达式,因此复杂逻辑使用 SQLAlchemy 的 &|,或使用 where_or()where_if()or_if()

chase.zhi and others added 3 commits July 10, 2026 23:21
Bring CRUDPlus closer to common ORM workflows with upsert_model,
select_models_paginated, global soft-delete filtering, and field__op
atomic expression updates.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread docs/changelog.md

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

此文件内容是自动生成的,请删除此变更

@wu-clan

wu-clan commented Jul 12, 2026

Copy link
Copy Markdown
Member

此方案正在评估中

@codecov

codecov Bot commented Jul 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.56280% with 88 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
sqlalchemy_crud_plus/query.py 82.85% 84 Missing ⚠️
sqlalchemy_crud_plus/crud.py 97.87% 2 Missing ⚠️
sqlalchemy_crud_plus/errors.py 88.88% 1 Missing ⚠️
sqlalchemy_crud_plus/types.py 95.23% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@zhiquanchi

Copy link
Copy Markdown
Author

@wu-clan 你好评估意见如何。如有建议可以增加评论

@wu-clan

wu-clan commented Jul 16, 2026

Copy link
Copy Markdown
Member

感谢你的贡献和详细的设计说明,也感谢补充文档与测试。

经过评估,当前形态下我们不准备合并此 PR,原因如下。

1. 与项目定位不符

sqlalchemy-crud-plus 的定位是:基于 SQLAlchemy 2.0 的异步 CRUD SDK,补齐常见增删改查、过滤与批量操作,复杂查询应回落 SQLAlchemy 原生 API

本 PR 引入了接近完整 ORM QuerySet 的链式构建器(投影、聚合、JOIN、CTE、Union、stream 等),维护面和心智模型都更接近「第二套查询层」,会显著改变库的边界与长期维护成本。这不是当前项目希望承担的方向

2. 范围过大,难以作为单一变更接受

PR 同时包含多组正交能力:

  • 分页 / Upsert / 原子更新 / 全局软删除
  • 全量 typed fluent Query / Write Builder
  • 业务 DAO 文档等

即便其中部分能力(如分页、Upsert、原子更新)有产品价值,也不适合与完整链式 DSL 绑在同一 PR 中整包合入。在「是否引入 QueryBuilder」这一方向性决策为否的前提下,整包合并不合适

3. 类型化链式查询的收益与成本不匹配

字符串 field__op 的补全与重构问题确实存在,但 SQLAlchemy 2.0 本身已提供类型友好的列表达式(User.nameselect().where() 等)。在 SA 之上再维护一套 Lambda/链式 DSL,与原生能力重叠大,却会带来双轨 API、文档与测试的长期成本。我们更倾向于:

  • 简单场景继续使用现有 CRUD + field__op
  • 复杂场景直接使用 SQLAlchemy

而不是在库内再实现一层近似 Django/Tortoise 的 QuerySet


再次感谢你的时间与用心。理解若与预期不同,欢迎继续围绕上述小范围能力参与讨论

@zhiquanchi zhiquanchi closed this Jul 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants