Skip to content

建议在基类提供跨方言 JSON 类型,避免 JSONB 硬编码破坏 SQLite/MySQL 兼容 #542

Closed

Description

@Echo-Note

问题描述

app/core/base_model.py 中 MappedBase 的注释明确声明「兼容 SQLite、MySQL 和 PostgreSQL」:

class MappedBase(AsyncAttrs, DeclarativeBase):
    """声明式基类
    ...
    兼容 SQLite、MySQL 和 PostgreSQL
    """

但各业务模块在需要 JSON 列时,若直接硬编码 sqlalchemy.dialects.postgresql.JSONB,会在非 PostgreSQL 方言下破坏这一承诺:

  1. SQLite / MySQL 下 create_all 直接失败:sqlalchemy.exc.CompileError: can't render element of type JSONB,导致基于 SQLite 的测试套件无法建表启动。
  2. 方言差异被下沉到每个业务模型:每个需要 JSON 列的模块都要自行决定用 JSONB 还是通用 JSON,容易出现不一致(当前仓库已同时存在 JSON 与 JSONB 两种用法)。

建议

在基类 app/core/base_model.py 提供一个统一的跨方言 JSON 类型,作为各模块定义 JSON 列的唯一入口:

from sqlalchemy import JSON
from sqlalchemy.dialects.postgresql import JSONB

# 跨方言 JSON 类型:PostgreSQL(生产)使用 JSONB,SQLite/MySQL(测试环境)降级为通用 JSON
JSONType = JSONB().with_variant(JSON(), "sqlite", "mysql")
  • PostgreSQL:渲染为 JSONB,保留索引 / 操作符能力。
  • SQLite / MySQL:降级为通用 JSON,保证 create_all 与 autogenerate 三种方言均可渲染、零漂移。

业务模型统一改为 mapped_column(JSONType, ...),一次性消除方言不一致的隐患。

对应实现见关联 PR。

Activity

  1. fastapiadmin commented on Oct 22, 2026

    @fastapiadmin
    Owner

    统一入口已落地:PR #545(基于 #543 的 rebase 版本)已合并到 dev,backend/app/core/base_model.py 现在提供:

    JSONType = JSONB().with_variant(JSON(), 'sqlite', 'mysql')

    已实测:PostgreSQL → JSONB;SQLite / MySQL → JSON;SQLite create_all 建表与 JSON 读写正常。

    但 issue 描述的主要故障路径还没修,且实际比描述更早发生 —— 代码生成器会产出非法 import:

    • GenConstant.DB_TO_SQLALCHEMY['jsonb'] = 'JSONB'(app/common/constant.py:280)
    • 生成器拼 import:import_list.add(f'from sqlalchemy import {映射值}')(gencode/jinja2_template_util.py:701)
    • ⇒ 从 PG 的 jsonb 列生成模型时,产物首行是 from sqlalchemy import JSONB,而 JSONB 不在顶层
      sqlalchemy 命名空间(实测 AttributeError)→ 生成的模块在 import 阶段就失败,比 SQLite 的
      CompileError 更早。

    同一逻辑还影响 INET / CIDR / MACADDR / Bit(均非顶层导出);Geometry 会先加 geoalchemy2 再被第 701 行
    覆盖成非法的 from sqlalchemy import Geometry。

    建议的后续修复(本 issue 暂保持 open):

    1. 生成器把 jsonb 映射为 JSONType,并从 app.core.base_model 导入;
    2. 方言专有类型走正确模块(sqlalchemy.dialects.postgresql / geoalchemy2),避免顶层 import;
    3. 加回归测试:生成的模型文件可 import,且能在 SQLite 上 create_all。
  2. fastapiadmin commented on Oct 22, 2026

    @fastapiadmin
    Owner

    已修复并合并到 dev / master:PR #547(提交 30c0e63a)。

    根因:生成器把 PG 的 jsonb 映射为 JSONB 后拼出 from sqlalchemy import JSONB —— 该类型不在顶层
    sqlalchemy 命名空间(实测 AttributeError),生成的模型在 import 阶段就失败;同一条逻辑还影响
    INET / CIDR / MACADDR / Bit / TSVECTOR / TSQUERY,Geometry 则会被覆盖成非法的 from sqlalchemy import Geometry。

    修复:

    1. PG 映射:jsonb → JSONType(跨方言:PG→JSONB,SQLite/MySQL→JSON)、bit / bit varying → BIT
    2. 新增 DIALECT_TYPE_IMPORTS:JSONType → app.core.base_model、方言类型 → sqlalchemy.dialects.postgresql、
      Geometry → geoalchemy2
    3. 生成器 import 拼装按该表取模块,不再产生顶层方言类型 import

    验证:新增 5 个回归测试(PG 方言子进程 + 端到端渲染 ast.parse);红绿验证(还原源码 → 4 个新测试失败,
    报错即 from sqlalchemy import JSONB);全量 pytest 102 passed、ruff 通过、basedpyright 0 errors。

    说明:生成几何类型模型仍需要 geoalchemy2(当前未在依赖中声明,属可选依赖)——如需开箱可用可另行加入可选依赖组。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions