已开启
【实习#34】新增 Django 经 MySQL 协议连接 openGauss B 库示例与自测 #103
【实习#34】新增 Django 经 MySQL 协议连接 openGauss B 库示例与自测 #103
已开启
Yhw050920创建于 8月5日
22 个文件变更+1055-0
@@ -0,0 +1,10 @@
1+# Byte-compiled / cache
2+__pycache__/
3+*.py[cod]
4+.venv/
5+venv/
6+*.egg-info/
7+.pytest_cache/
8+.idea/
9+.vscode/
10+*.log
@@ -0,0 +1,87 @@
1+# DjangoConnectOpenGaussB
2+ 
3+openGauss B 兼容模式 + MySQL 协议(dolphin)下,使用 **原生** `django.db.backends.mysql` + PyMySQL 连接与 CRUD 示例。
4+ 
5+## 环境
6+ 
7+- openEuler 20.03 LTS
8+- openGauss 7.0.x 企业版/Server(需 dolphin,Lite 不支持)
9+- **Plugin 补丁(须按顺序合入或安装)**
10+ 1. [Plugin !2522](https://gitcode.com/opengauss/Plugin/merge_requests/2522) — `NO_BACKSLASH_ESCAPES` 协议状态与字符串转义语义(**须先合入**,否则 `OPTIONS.sql_mode` 不生效)
11+ 2. [Plugin !2524](https://gitcode.com/opengauss/Plugin/merge_requests/2524) — `@@default_storage_engine` / `@@sql_auto_is_null` 兼容桩
12+- Python 3.8+
13+- Django 4.2+/5.0
14+- 驱动:PyMySQL(主路径示例);也可换 mysqlclient
15+ 
16+## 快速开始
17+ 
18+```bash
19+python -m venv .venv
20+# Windows
21+.venv\Scripts\activate
22+# Linux
23+# source .venv/bin/activate
24+ 
25+pip install -r requirements.txt
26+ 
27+# 按实际环境修改(密码仅通过环境变量注入)
28+export OG_MYSQL_HOST=127.0.0.1
29+export OG_MYSQL_PORT=3308
30+export OG_MYSQL_DB=mysql_test_db
31+export OG_MYSQL_USER=omm
32+export OG_MYSQL_PASSWORD='your_password_here'
33+ 
34+python scripts/run_demo.py
35+```
36+ 
37+## 配置要点
38+ 
39+`config/settings.py` 默认使用:
40+ 
41+- `ENGINE = django.db.backends.mysql`(原生 MySQL backend)
42+- `PORT = dolphin_server_port`(如 3308)
43+- `NAME` = B 库下 schema(MySQL database)
44+- `PASSWORD` = 环境变量 `OG_MYSQL_PASSWORD`(仓库不保存明文口令)
45+ 
46+并通过 `pymysql_bootstrap.py` 将 PyMySQL 注册为 MySQLdb;若 `SELECT VERSION()` 仍是 openGauss banner,则按 dolphin 握手版本解析为 MySQL 8.0.28。
47+ 
48+### Plugin 侧依赖
49+ 
50+Django 初始化会执行:
51+ 
52+```sql
53+SELECT VERSION(),
54+ @@sql_mode,
55+ @@default_storage_engine,
56+ @@sql_auto_is_null,
57+ @@lower_case_table_names,
58+ ...
59+```
60+ 
61+**须按顺序合入或安装以下 Plugin 补丁:**
62+ 
63+| 顺序 | MR | 作用 |
64+|------|-----|------|
65+| 1(必须先合入) | [Plugin !2522](https://gitcode.com/opengauss/Plugin/merge_requests/2522) | 修复 dolphin 协议层 `NO_BACKSLASH_ESCAPES` 状态位,使 `config/settings.py``OPTIONS.sql_mode = NO_BACKSLASH_ESCAPES` 与实际字符串转义一致 |
66+| 2 | [Plugin !2524](https://gitcode.com/opengauss/Plugin/merge_requests/2524) | 新增 `@@default_storage_engine` / `@@sql_auto_is_null` 兼容桩 |
67+ 
68+openGauss 本身没有 MySQL 存储引擎概念;!2524 使 `@@default_storage_engine` 固定返回 `InnoDB``@@sql_auto_is_null` 默认为 `0` 且可 `SET`,从而原生 backend 可正常工作。
69+ 
70+**未合入 !2522 时**,即使配置了 `NO_BACKSLASH_ESCAPES`,PyMySQL 仍可能按错误转义语义编码单引号/反斜杠,导致 ORM 写入 `O'Brien` 等值失败。请先合入 !2522 再运行 `scripts/run_demo.py` 中的特殊字符断言。
71+ 
72+未合入 Plugin 补丁的旧环境,可临时:
73+ 
74+```bash
75+export OG_MYSQL_ENGINE=opengauss_mysql
76+```
77+ 
78+使用本仓库附带的兼容 Backend(仅作过渡)。
79+ 
80+## 产出
81+ 
82+- 示例工程:本目录
83+- 设计文档:`doc/设计文档.md`
84+- 自测报告:`docs/自测报告.md`
85+- 测试思维导图:`docs/测试思维导图.mm` / `.md` / `.html`
86+- 官方开发指南:docs 仓 `django_development.md`
87+- Plugin:[!2522](https://gitcode.com/opengauss/Plugin/merge_requests/2522)(`NO_BACKSLASH_ESCAPES`,须先合入)、[!2524](https://gitcode.com/opengauss/Plugin/merge_requests/2524)(`@@default_storage_engine` / `@@sql_auto_is_null`
@@ -0,0 +1 @@
1+# empty package marker
@@ -0,0 +1,53 @@
1+from pathlib import Path
2+import os
3+import sys
4+ 
5+BASE_DIR = Path(__file__).resolve().parent.parent
6+sys.path.insert(0, str(BASE_DIR))
7+ 
8+import pymysql_bootstrap # noqa: F401
9+ 
10+SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", "django-opengauss-b-demo-not-for-production")
11+DEBUG = os.environ.get("DJANGO_DEBUG", "1") == "1"
12+ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS", "127.0.0.1,localhost").split(",")
13+ 
14+INSTALLED_APPS = [
15+ "django.contrib.contenttypes",
16+ "django.contrib.auth",
17+ "demo_app",
18+]
19+ 
20+MIDDLEWARE = []
21+ 
22+ROOT_URLCONF = "config.urls"
23+WSGI_APPLICATION = "config.wsgi.application"
24+ 
25+# Prefer native Django MySQL backend after Plugin adds @@default_storage_engine /
26+# @@sql_auto_is_null stubs. Override with OG_MYSQL_ENGINE=opengauss_mysql for
27+# older servers without the Plugin patch.
28+_ENGINE = os.environ.get("OG_MYSQL_ENGINE", "django.db.backends.mysql")
29+ 
30+DATABASES = {
31+ "default": {
32+ "ENGINE": _ENGINE,
33+ "NAME": os.environ.get("OG_MYSQL_DB", "mysql_test_db"),
34+ "USER": os.environ.get("OG_MYSQL_USER", "omm"),
35+ "PASSWORD": os.environ["OG_MYSQL_PASSWORD"],
36+ "HOST": os.environ.get("OG_MYSQL_HOST", "127.0.0.1"),
37+ "PORT": os.environ.get("OG_MYSQL_PORT", "3308"),
38+ "OPTIONS": {
39+ "charset": "utf8mb4",
wofanzheng
wofanzhengwofanzheng4 天前

【问题】 OPTIONS 只设置了 charset,没有让 PyMySQL 使用与 Dolphin 实际解析方式一致的字符串转义。协议状态没有 NO_BACKSLASH_ESCAPES 时,PyMySQL 会把单引号编码为 \';在 Dolphin/openGauss 默认字符串解析下,ORM 写入 O'Brien 等正常业务值可能产生 SQL 语法错误。当前 demo 只写入 zhaoliu,无法发现该问题。

PyMySQL 对应转义逻辑:https://github.com/PyMySQL/PyMySQL/blob/v1.1.0/pymysql/connections.py#L527-L530

【建议】 明确依赖并合入 Plugin #2522,在 OPTIONS 中增加 "sql_mode": "NO_BACKSLASH_ESCAPES"(或提供等价且经过验证的会话转义方案),并增加包含单引号、反斜杠字符串的写入和读取断言。

likedislike
wofanzheng
wofanzheng
3 天前 评论:
wofanzheng
wofanzheng
3 天前 评论:
40+ # Align PyMySQL escaping with Dolphin string parsing (see Plugin NO_BACKSLASH_ESCAPES).
41+ "sql_mode": "NO_BACKSLASH_ESCAPES",
42+ },
43+ }
44+}
45+ 
46+LANGUAGE_CODE = "zh-hans"
47+TIME_ZONE = "Asia/Shanghai"
48+USE_I18N = True
49+USE_TZ = True
50+ 
51+DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
52+ 
53+SILENCED_SYSTEM_CHECKS = ["mysql.W002"]
@@ -0,0 +1,7 @@
1+from django.urls import path
2+from django.http import HttpResponse
3+ 
4+ 
5+urlpatterns = [
6+ path("", lambda request: HttpResponse("Django + openGauss B-protocol demo")),
7+]
@@ -0,0 +1,6 @@
1+import os
2+ 
3+from django.core.wsgi import get_wsgi_application
4+ 
5+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
6+application = get_wsgi_application()
@@ -0,0 +1 @@
1+# demo app package
@@ -0,0 +1,6 @@
1+from django.apps import AppConfig
2+ 
3+ 
4+class DemoAppConfig(AppConfig):
5+ default_auto_field = "django.db.models.BigAutoField"
6+ name = "demo_app"
@@ -0,0 +1,16 @@
1+from django.db import models
2+ 
3+ 
4+class User(models.Model):
5+ """Maps to openGauss B-mode table `user` under schema mysql_test_db."""
6+ 
7+ id = models.AutoField(primary_key=True)
8+ name = models.CharField(max_length=50)
9+ age = models.IntegerField(null=True, blank=True)
10+ 
11+ class Meta:
12+ db_table = "user"
13+ managed = False # table prepared by SQL / docs guide
14+ 
15+ def __str__(self):
16+ return f"User{{id={self.id}, name='{self.name}', age={self.age}}}"
@@ -0,0 +1,106 @@
1+# 设计文档
2+ 
3+> 对应 Issue:[开源实习] openGauss B库B协议适配MySQL python orm框架 Django #34
4+> https://gitcode.com/opengauss/opensource-intership/issues/34
5+ 
6+## 1. 项目背景
7+ 
8+openGauss 在 dolphin 插件中实现了 MySQL 协议兼容。社区已有 JDBC / MyBatis / Hibernate 等指南,本任务补齐 **Python ORM(Django)+ MySQL Python 驱动** 经 dolphin 连接 B 兼容模式数据库的验证、适配与文档。
9+ 
10+按社区评审要求:**优先支持原生 `django.db.backends.mysql`**;dolphin 缺少的 MySQL 变量/能力在 **Plugin** 仓补齐,而不是仅靠客户端自定义 Backend 绕过。
11+ 
12+## 2. 目标与范围
13+ 
14+| 目标 | 说明 |
15+|------|------|
16+| 连通性 | Django 经 `dolphin_server_port`(如 3308)完成认证与会话 |
17+| 原生 Backend | 使用官方 `django.db.backends.mysql` |
18+| Plugin 兼容 | 为 Django 探测变量提供兼容桩(无真实引擎语义) |
19+| 业务可用性 | 覆盖 ORM 查询与 CRUD 主路径 |
20+| 交付 | examples 示例/自测/思维导图;docs 开发指南;Plugin 补丁 |
21+ 
22+**非目标:** 存储过程、server-side cursor 等协议明确不支持能力的强制支持;实现真实 InnoDB 引擎。
23+ 
24+## 3. 环境约束
25+ 
26+- OS:openEuler 20.03 LTS(x86_64 / aarch64)
27+- DB:openGauss 7.0.0-LTS(正式包未上架时可用 archive_test 转测包,需含 dolphin 的 Server 版,非 Lite)
28+- 协议:`enable_dolphin_proto=on``dolphin_server_port` ≠ 原生 port
29+- 客户端:Python 3.8+,Django 4.2+/5.0,PyMySQL(或 mysqlclient)
30+ 
31+## 4. 总体方案
32+ 
33+```text
34+Django App
35+ -> ENGINE=django.db.backends.mysql
36+ -> PyMySQL (MySQLdb 兼容层)
37+ -> TCP :dolphin_server_port
38+ -> openGauss B DB + dolphin
39+ @@default_storage_engine -> "InnoDB" (stub)
40+ @@sql_auto_is_null -> 0 (stub, SET 可写)
41+```
42+ 
43+### 4.1 服务端配置要点
44+ 
45+1. 创建 B 库:`CREATE DATABASE ... DBCOMPATIBILITY 'B'`
46+2. `CREATE EXTENSION dolphin`(如需要)
47+3. `SELECT set_native_password(user, password, '')`
48+4. GUC:`listen_addresses``enable_dolphin_proto``dolphin_server_port``dolphin.default_database_name`
49+ 
50+### 4.2 Plugin 适配要点(评审结论)
51+ 
52+原生 Django MySQL backend 在 `mysql_server_data` 中会查询:
53+ 
54+- `@@sql_mode`
55+- `@@default_storage_engine` ← 原报错 **28804**(变量不存在)
56+- `@@sql_auto_is_null`
57+- `@@lower_case_table_names`
58+-
59+ 
60+openGauss **没有** MySQL 存储引擎实现。参考 `contrib/dolphin/plugin_postgres.cpp``query_cache_size` / `system_time_zone` 等“无实际语义、仅兼容客户端探测”的变量做法:
61+ 
62+1.`BSqlPluginContext` 增加字段
63+2. `DefineCustomStringVariable("default_storage_engine", ...)` 默认 `"InnoDB"`
64+3. `DefineCustomIntVariable("sql_auto_is_null", ...)` 默认 `0`(支持 Django `SET SQL_AUTO_IS_NULL = 0`
65+4. SET 时打 WARNING 标明无实际含义(与现有 stub 一致)
66+ 
67+这样框架可正常初始化,且不引入虚假引擎能力。
68+ 
69+另外,Django 用 `re.match()` 解析 `SELECT VERSION()`,要求字符串**以** `X.Y.Z` 开头。内核 `version()` 是 builtin,无法 `CREATE OR REPLACE`;dolphin 握手包已是 `8.0.28-dophin-server`。示例里 `pymysql_bootstrap.py` 在识别到 openGauss banner 时按握手版本解析为 `(8, 0, 28)`,从而继续走原生 `django.db.backends.mysql`
70+ 
71+### 4.3 示例业务
72+ 
73+- Schema/DB:`mysql_test_db`
74+- 表:`user(id, name, age)`
75+- 脚本:`scripts/run_demo.py`(查询/增/改/删)
76+- 过渡:未合入 Plugin 的环境可设 `OG_MYSQL_ENGINE=opengauss_mysql`
77+ 
78+## 5. 测试设计
79+ 
80+| 编号 | 场景 | 通过标准 |
81+|------|------|----------|
82+| T0 | 版本与进程 | `gs_ctl status` running |
83+| T1 | 端口 | 5432 / 3308 LISTEN |
84+| T2 | 协议认证 | PyMySQL 握手成功 |
85+| T3 | DDL | 建库建表成功 |
86+| T4 | `SELECT @@default_storage_engine` | 返回 `InnoDB` |
87+| T5 | ORM CRUD(原生 mysql backend) | 打印 `DEMO_OK` |
88+| T6 | Django 安装检查 | 打印 `INSTALL_OK` |
89+| T7 | 不支持项 | 文档标注跳过 |
90+ 
91+## 6. 风险与对策
92+ 
93+| 风险 | 对策 |
94+|------|------|
95+| 正式 LTS 包不可得 | 使用同系列 archive_test 转测包并在报告中说明 |
96+| Lite 无 dolphin | 强制 Server/企业版 |
97+| 敏感信息入库 | 密码仅通过环境变量注入,文档脱敏 |
98+| 协议能力差异 | 文档明确不支持项,避免误用 |
99+| 兼容桩被误用为真实引擎能力 | 文档/WARNING 标明无实际含义 |
100+ 
101+## 7. 交付物清单
102+ 
103+1. `DjangoConnectOpenGaussB/` 示例工程(默认原生 mysql backend)
104+2. `docs/自测报告.md``docs/测试思维导图.*``doc/设计文档.md`
105+3. docs 仓 `django_development.md``_toc.yaml`
106+4. Plugin:`default_storage_engine` / `sql_auto_is_null` 兼容变量 + 回归用例
@@ -0,0 +1,46 @@
1+# openGauss B 库 B 协议适配 Django(MySQL Python 驱动)开发方案
2+ 
3+> Issue:`[开源实习]: openGauss B库B协议适配MySQL python orm框架 Django #34`
4+> 环境:openEuler 20.03 LTS x86_64 · openGauss 7.0.0-LTS.B008(转测包)· dolphin:3308
5+ 
6+## 1. 目标
7+ 
8+验证并适配 **Django ORM + MySQL Python 驱动(PyMySQL)**,经 dolphin MySQL 协议连接 openGauss B 兼容模式数据库,输出 examples 示例、自测报告/思维导图与 docs 开发指南。
9+ 
10+## 2. 技术路径
11+ 
12+1. 开启 `enable_dolphin_proto``dolphin_server_port=3308`,B 库 `DBCOMPATIBILITY 'B'`
13+2. `set_native_password(user, pwd, '')` 配置 MySQL native 认证
14+3. Django 使用自定义 Backend `opengauss_mysql`(继承官方 mysql backend)
15+ - 规避 `@@default_storage_engine` 等探测导致的 **28804**
16+4. 以 CRUD demo(`scripts/run_demo.py`)作为主验收路径
17+ 
18+## 3. 产出规划
19+ 
20+| 仓 | 内容 |
21+|----|------|
22+| examples | `DjangoConnectOpenGaussB` 工程、自测报告、测试思维导图 |
23+| docs | `django_development.md` + `_toc.yaml` |
24+| Plugin | 无服务端缺陷则不改 |
25+ 
26+## 4. 测试范围
27+ 
28+- T0 环境/版本/运行状态
29+- T1–T3 端口、认证、建库建表
30+- T4–T5 ORM 查询与 CRUD(`DEMO_OK`
31+- T6 Django 安装检查(`INSTALL_OK`
32+- T7 协议不支持项跳过(存储过程 / server-side cursor)
33+ 
34+## 5. 当前进度(2026-08-05)
35+ 
36+- [x] 云环境安装 openGauss 7.0.0-LTS.B008(build `75f983eb`
37+- [x] dolphin 3308 + B 库 + demo 表
38+- [x] Django 安装与 CRUD 自测通过
39+- [x] examples / docs 已推个人 fork 特性分支
40+- [x] 测试思维导图(`.mm` / `.md` / `.html`
41+- [ ] 向上游 `opengauss/examples``opengauss/docs` 提 MR(领取通过后)
42+- [ ] 导师邮件领取评审(截图 + 简历 + 本方案)
43+ 
44+## 6. 风险与说明
45+ 
46+- 正式 **7.0.0-LTS** 尚未官网上架;本任务使用 archive_test **LTS.B008**(openEuler20.03/x86 最新有包版本)。包文件名仍可能显示 RC3,以转测目录与 `git_num` 为准。
@@ -0,0 +1,147 @@
1+<!DOCTYPE html>
2+<html lang="zh-CN">
3+<head>
4+ <meta charset="UTF-8" />
5+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6+ <title>Django × openGauss B协议 — 测试思维导图</title>
7+ <style>
8+ :root {
9+ --bg: #f7f4ef;
10+ --ink: #1f2a24;
11+ --line: #8aa399;
12+ --root: #1f6f5b;
13+ --node: #ffffff;
14+ --accent: #c45c26;
15+ }
16+ * { box-sizing: border-box; }
17+ body {
18+ margin: 0;
19+ font-family: "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
20+ color: var(--ink);
21+ background:
22+ radial-gradient(circle at 10% 10%, #e8f2ec 0, transparent 40%),
23+ radial-gradient(circle at 90% 0%, #f8e9df 0, transparent 35%),
24+ var(--bg);
25+ padding: 24px;
26+ }
27+ h1 { text-align: center; font-size: 22px; margin: 0 0 8px; }
28+ .sub { text-align: center; color: #5b6b63; margin-bottom: 28px; font-size: 13px; }
29+ .mind {
30+ display: grid;
31+ grid-template-columns: 1fr auto 1fr;
32+ gap: 18px;
33+ align-items: center;
34+ max-width: 1200px;
35+ margin: 0 auto;
36+ }
37+ .col { display: flex; flex-direction: column; gap: 14px; }
38+ .col.left { align-items: flex-end; }
39+ .col.right { align-items: flex-start; }
40+ .root {
41+ width: 180px;
42+ min-height: 180px;
43+ border-radius: 50%;
44+ background: var(--root);
45+ color: #fff;
46+ display: flex;
47+ align-items: center;
48+ justify-content: center;
49+ text-align: center;
50+ padding: 18px;
51+ font-weight: 700;
52+ box-shadow: 0 10px 28px rgba(31, 111, 91, 0.28);
53+ z-index: 2;
54+ }
55+ .branch {
56+ background: var(--node);
57+ border: 1px solid #d7e0db;
58+ border-left: 4px solid var(--root);
59+ border-radius: 10px;
60+ padding: 12px 14px;
61+ width: min(360px, 100%);
62+ box-shadow: 0 4px 14px rgba(0,0,0,0.04);
63+ }
64+ .branch.left { border-left: none; border-right: 4px solid var(--accent); }
65+ .branch h3 { margin: 0 0 8px; font-size: 15px; }
66+ .branch ul { margin: 0; padding-left: 18px; font-size: 13px; line-height: 1.55; }
67+ .pass { color: #1f6f5b; font-weight: 600; }
68+ .skip { color: #9a6b2f; font-weight: 600; }
69+ .legend {
70+ max-width: 1200px;
71+ margin: 28px auto 0;
72+ font-size: 13px;
73+ color: #5b6b63;
74+ text-align: center;
75+ }
76+ </style>
77+</head>
78+<body>
79+ <h1>Django × openGauss B 协议适配 — 测试思维导图</h1>
80+ <p class="sub">Issue #34 · openEuler 20.03 · openGauss 7.0.x · dolphin:3308 · Django + PyMySQL</p>
81+ <div class="mind">
82+ <div class="col left">
83+ <div class="branch left">
84+ <h3>4. ORM 功能测试</h3>
85+ <ul>
86+ <li>T4 查询全部 <span class="pass">通过</span></li>
87+ <li>T5 新增 / 模糊查 / 更新 / 删除 <span class="pass">通过 DEMO_OK</span></li>
88+ <li>入口:scripts/run_demo.py</li>
89+ </ul>
90+ </div>
91+ <div class="branch left">
92+ <h3>5. 协议限制</h3>
93+ <ul>
94+ <li>T6 存储过程 <span class="skip">跳过</span></li>
95+ <li>server-side cursor 不支持</li>
96+ <li>对照 dolphin 官方兼容说明</li>
97+ </ul>
98+ </div>
99+ <div class="branch left">
100+ <h3>6. 问题与修复</h3>
101+ <ul>
102+ <li>openblas / OOM / unixODBC</li>
103+ <li>Lite 无 dolphin → Server</li>
104+ <li>28804 → opengauss_mysql 适配层</li>
105+ </ul>
106+ </div>
107+ <div class="branch left">
108+ <h3>7. 交付物</h3>
109+ <ul>
110+ <li>examples 示例 + 自测报告 + 思维导图</li>
111+ <li>docs django_development.md</li>
112+ <li>Plugin 本期无服务端补丁</li>
113+ </ul>
114+ </div>
115+ </div>
116+ <div class="root">Django × openGauss<br/>B协议适配测试</div>
117+ <div class="col right">
118+ <div class="branch">
119+ <h3>1. 环境准备</h3>
120+ <ul>
121+ <li>openEuler 20.03 LTS(x86_64/aarch64)</li>
122+ <li>openGauss 7.0.x Server(非 Lite)</li>
123+ <li>dolphin:enable_dolphin_proto + 3308</li>
124+ <li>B库 proto_test_db / 业务库 mysql_test_db</li>
125+ </ul>
126+ </div>
127+ <div class="branch">
128+ <h3>2. 驱动与适配层</h3>
129+ <ul>
130+ <li>PyMySQL(主)/ mysqlclient(可选)</li>
131+ <li>ENGINE = opengauss_mysql</li>
132+ <li>规避 @@default_storage_engine → 28804</li>
133+ </ul>
134+ </div>
135+ <div class="branch">
136+ <h3>3. 连接认证测试</h3>
137+ <ul>
138+ <li>T1 TCP 5432/3308 <span class="pass">通过</span></li>
139+ <li>T2 set_native_password 握手 <span class="pass">通过</span></li>
140+ <li>T3 CREATE DATABASE/TABLE <span class="pass">通过</span></li>
141+ </ul>
142+ </div>
143+ </div>
144+ </div>
145+ <p class="legend">配套文件:测试思维导图.mm(可导入 XMind)· 测试思维导图.md(Mermaid)· 自测报告.md</p>
146+</body>
147+</html>
@@ -0,0 +1,55 @@
1+# Django × openGauss B 协议适配 — 测试思维导图
2+ 
3+> 对应任务:`[开源实习]: openGauss B库B协议适配MySQL python orm框架 Django #34`
4+> 可打开文件:同目录 [`测试思维导图.mm`](./测试思维导图.mm)(FreeMind / XMind 可导入)
5+ 
6+## Mermaid 总览
7+ 
8+```mermaid
9+mindmap
10+ root((Django × openGauss B协议适配测试))
11+ 环境准备
12+ openEuler 20.03 LTS
13+ openGauss 7.0.x Server
14+ dolphin 3308
15+ B库/业务库
16+ 驱动与适配
17+ PyMySQL
18+ opengauss_mysql Backend
19+ 规避 28804
20+ 连接认证
21+ T1 端口连通
22+ T2 native_password
23+ T3 Schema就绪
24+ ORM功能
25+ T4 查询全部
26+ T5 增删改查 DEMO_OK
27+ 协议限制
28+ T6 存储过程跳过
29+ server-side cursor 不支持
30+ 问题修复
31+ openblas / OOM / unixODBC
32+ Lite无dolphin
33+ Backend适配
34+ 交付物
35+ examples
36+ docs
37+ 自测报告
38+```
39+ 
40+## 用例映射
41+ 
42+| 编号 | 分支 | 结果 |
43+|------|------|------|
44+| T1 | 连接认证 / 端口连通 | 通过 |
45+| T2 | 连接认证 / 握手认证 | 通过 |
46+| T3 | 连接认证 / Schema就绪 | 通过 |
47+| T4 | ORM功能 / 查询全部 | 通过 |
48+| T5 | ORM功能 / CRUD闭环 | 通过(`DEMO_OK`) |
49+| T6 | 协议限制 / 存储过程 | 跳过 |
50+ 
51+## 使用说明
52+ 
53+1. 用 [XMind](https://xmind.app/) / FreeMind / MindMaster 打开 `测试思维导图.mm`
54+2. 或在支持 Mermaid 的 Markdown 预览中查看上方导图
55+3. 详细步骤与日志见 [`自测报告.md`](./自测报告.md)
@@ -0,0 +1,99 @@
1+<?xml version="1.0" encoding="UTF-8"?>
2+<map version="1.0.1">
3+ <node TEXT="Django × openGauss B协议适配测试" FOLDED="false" ID="root">
4+ <node TEXT="1.环境准备" POSITION="right" ID="env">
5+ <node TEXT="操作系统" ID="env-os">
6+ <node TEXT="openEuler 20.03 LTS" ID="env-os-1"/>
7+ <node TEXT="架构 x86_64 / aarch64" ID="env-os-2"/>
8+ </node>
9+ <node TEXT="数据库" ID="env-db">
10+ <node TEXT="openGauss 7.0.x Server(非 Lite)" ID="env-db-1"/>
11+ <node TEXT="任务要求 7.0.0-LTS / 实测 7.0.0-RC3" ID="env-db-2"/>
12+ <node TEXT="安装路径 /opt/software/openGauss" ID="env-db-3"/>
13+ </node>
14+ <node TEXT="dolphin MySQL协议" ID="env-dolphin">
15+ <node TEXT="enable_dolphin_proto=on" ID="env-dolphin-1"/>
16+ <node TEXT="dolphin_server_port=3308" ID="env-dolphin-2"/>
17+ <node TEXT="listen_addresses=*" ID="env-dolphin-3"/>
18+ <node TEXT="原生端口 5432" ID="env-dolphin-4"/>
19+ </node>
20+ <node TEXT="业务库" ID="env-biz">
21+ <node TEXT="B库 proto_test_db" ID="env-biz-1"/>
22+ <node TEXT="业务库 mysql_test_db + user表" ID="env-biz-2"/>
23+ </node>
24+ </node>
25+ 
26+ <node TEXT="2.驱动与适配层" POSITION="right" ID="driver">
27+ <node TEXT="Python 驱动" ID="driver-py">
28+ <node TEXT="主路径 PyMySQL" ID="driver-py-1"/>
29+ <node TEXT="可选 mysqlclient" ID="driver-py-2"/>
30+ <node TEXT="pymysql_bootstrap 注册 MySQLdb" ID="driver-py-3"/>
31+ </node>
32+ <node TEXT="Django Backend" ID="driver-backend">
33+ <node TEXT="ENGINE=opengauss_mysql" ID="driver-backend-1"/>
34+ <node TEXT="基于 django.db.backends.mysql" ID="driver-backend-2"/>
35+ <node TEXT="软探测 version/sql_mode" ID="driver-backend-3"/>
36+ <node TEXT="规避 @@default_storage_engine → 28804" ID="driver-backend-4"/>
37+ </node>
38+ <node TEXT="连接参数" ID="driver-conn">
39+ <node TEXT="HOST / PORT=3308" ID="driver-conn-1"/>
40+ <node TEXT="NAME=mysql_test_db" ID="driver-conn-2"/>
41+ <node TEXT="USER/PASSWORD(native_password)" ID="driver-conn-3"/>
42+ </node>
43+ </node>
44+ 
45+ <node TEXT="3.连接认证测试" POSITION="right" ID="auth">
46+ <node TEXT="T1 端口连通" ID="auth-t1">
47+ <node TEXT="TCP 5432 通过" ID="auth-t1-1"/>
48+ <node TEXT="TCP 3308 通过" ID="auth-t1-2"/>
49+ <node TEXT="安全组放行" ID="auth-t1-3"/>
50+ </node>
51+ <node TEXT="T2 握手认证" ID="auth-t2">
52+ <node TEXT="set_native_password" ID="auth-t2-1"/>
53+ <node TEXT="PyMySQL 认证通过" ID="auth-t2-2"/>
54+ </node>
55+ <node TEXT="T3 Schema/表就绪" ID="auth-t3">
56+ <node TEXT="CREATE DATABASE/TABLE 通过" ID="auth-t3-1"/>
57+ <node TEXT="预置 user 样例数据" ID="auth-t3-2"/>
58+ </node>
59+ </node>
60+ 
61+ <node TEXT="4.ORM功能测试" POSITION="left" ID="orm">
62+ <node TEXT="T4 查询全部" ID="orm-t4">
63+ <node TEXT="User.objects.all() 通过" ID="orm-t4-1"/>
64+ </node>
65+ <node TEXT="T5 CRUD闭环" ID="orm-t5">
66+ <node TEXT="新增 create 通过" ID="orm-t5-1"/>
67+ <node TEXT="模糊查询 filter(name__icontains) 通过" ID="orm-t5-2"/>
68+ <node TEXT="更新 save/update 通过" ID="orm-t5-3"/>
69+ <node TEXT="按ID查询 get 通过" ID="orm-t5-4"/>
70+ <node TEXT="删除 delete 通过" ID="orm-t5-5"/>
71+ <node TEXT="DEMO_OK" ID="orm-t5-6"/>
72+ </node>
73+ <node TEXT="入口脚本" ID="orm-script">
74+ <node TEXT="scripts/run_demo.py" ID="orm-script-1"/>
75+ <node TEXT="云上 /opt/django-opengauss-b" ID="orm-script-2"/>
76+ </node>
77+ </node>
78+ 
79+ <node TEXT="5.协议限制" POSITION="left" ID="limit">
80+ <node TEXT="T6 存储过程 跳过" ID="limit-t6"/>
81+ <node TEXT="server-side cursor 不支持" ID="limit-cursor"/>
82+ <node TEXT="对照 dolphin 官方兼容说明" ID="limit-doc"/>
83+ </node>
84+ 
85+ <node TEXT="6.问题与修复" POSITION="left" ID="bugs">
86+ <node TEXT="缺 libopenblas → yum install openblas" ID="bugs-1"/>
87+ <node TEXT="OOM → overcommit=0 + 调小内存参数" ID="bugs-2"/>
88+ <node TEXT="缺 unixODBC → yum install unixODBC" ID="bugs-3"/>
89+ <node TEXT="Lite 无 dolphin → 改用 Server" ID="bugs-4"/>
90+ <node TEXT="28804 → opengauss_mysql 适配层" ID="bugs-5"/>
91+ </node>
92+ 
93+ <node TEXT="7.交付物" POSITION="left" ID="deliver">
94+ <node TEXT="examples 示例工程 + 自测报告 + 本思维导图" ID="deliver-1"/>
95+ <node TEXT="docs django_development.md" ID="deliver-2"/>
96+ <node TEXT="Plugin 本期无服务端补丁" ID="deliver-3"/>
97+ </node>
98+ </node>
99+</map>
@@ -0,0 +1,75 @@
1+# Django × openGauss B 协议适配 — 自测报告
2+ 
3+## 1. 测试环境
4+ 
5+| 项 | 值 |
6+|----|----|
7+| OS | openEuler 20.03 (LTS) x86_64 |
8+| DB | openGauss **7.0.0-LTS.B008** Server(archive_test 转测包,simpleInstall) |
9+| 构建号 | `75f983eb`(与安装包目录 `git_num` 一致) |
10+| 协议 | dolphin MySQL 协议,`enable_dolphin_proto=on`,端口 3308 |
11+| B 库 | `proto_test_db` |
12+| Schema/DB | `mysql_test_db` |
13+| 客户端 | Django 5.0.14 + PyMySQL |
14+| 版本说明 | 任务要求 7.0.0-LTS;正式 LTS 未上架时采用 archive_test **LTS.B008**(openEuler20.03/x86 最新可用包)。包文件名可能仍含 RC3 字样,以转测目录与 `git_num` 为准。 |
15+ 
16+## 2. 用例与结果
17+ 
18+| 编号 | 用例 | 结果 | 说明 |
19+|------|------|------|------|
20+| T0 | OS / 版本 / 运行状态 | **通过** | openEuler 20.03;`gs_ctl -V` build 75f983eb;`server is running` |
21+| T1 | TCP 5432/3308 监听 | **通过** | `ss -lntp` 双端口 LISTEN |
22+| T2 | PyMySQL 握手认证 | **通过** | `set_native_password('user', 'pwd', '')` 后可用 |
23+| T3 | CREATE DATABASE/TABLE(协议侧) | **通过** | `mysql_test_db` + 表 `user` |
24+| T4 | Django ORM 查询全部 | **通过** | `scripts/run_demo.py` |
25+| T5 | Django ORM 新增/模糊查/更新/删除 | **通过** | `DEMO_OK` |
26+| T6 | Django 安装检查 | **通过** | `INSTALL_OK` |
27+| T7 | 存储过程 / server-side cursor | 跳过 | 协议文档明确不支持 |
28+ 
29+### 运行日志摘要(脱敏)
30+ 
31+```text
32+gs_ctl: server is running
33+INSTALL_OK
34+=== 查询所有用户 ===
35+...
36+DEMO_OK
37+```
38+ 
39+## 3. 问题与处理
40+ 
41+| 问题 | 处理 |
42+|------|------|
43+| 正式 7.0.0-LTS 未发布 | 使用 archive_test **LTS.B008** |
44+| 缺 libopenblas | `yum install openblas` |
45+| 内存参数过大导致启动失败 | `max_process_memory` 调小,`vm.overcommit_memory=0` |
46+| 缺 unixODBC | `yum install unixODBC` |
47+| Lite 无 dolphin | 使用 Server/企业版 |
48+| Django 探测 `@@default_storage_engine` → 28804 | Backend `opengauss_mysql` |
49+| `set_native_password` 三参数 | `SELECT set_native_password(user, pwd, '');` |
50+ 
51+## 4. 结论
52+ 
53+在 openEuler 20.03 + openGauss 7.0.0-LTS.B008 上,经 dolphin 3308,Django ORM(PyMySQL)CRUD 闭环验证通过(`INSTALL_OK` / `DEMO_OK`)。
54+ 
55+适配要点:不能直接使用原生 `django.db.backends.mysql` 的部分服务端变量探测,需使用本示例 `opengauss_mysql` Backend。
56+ 
57+## 5. 测试思维导图
58+ 
59+| 文件 | 说明 |
60+|------|------|
61+| [`测试思维导图.mm`](./测试思维导图.mm) | FreeMind / XMind 可导入 |
62+| [`测试思维导图.md`](./测试思维导图.md) | Mermaid 导图 + 用例映射 |
63+| [`测试思维导图.html`](./测试思维导图.html) | 浏览器打开 |
64+ 
65+## 6. 验收截图
66+ 
67+验收截图见 PR 描述(已脱敏,不入库,避免敏感信息扫描误报)。自测结论:`INSTALL_OK` / `DEMO_OK`
68+ 
69+## 7. 交付仓库
70+ 
71+| 仓 | 说明 |
72+|----|------|
73+| examples | 本目录 `DjangoConnectOpenGaussB` |
74+| docs | `django_development.md` |
75+| Plugin | 本期无服务端补丁 |
@@ -0,0 +1,104 @@
1+# 设计文档
2+ 
3+> 对应 Issue:[开源实习] openGauss B库B协议适配MySQL python orm框架 Django #34
4+> https://gitcode.com/opengauss/opensource-intership/issues/34
5+ 
6+## 1. 项目背景
7+ 
8+openGauss 在 dolphin 插件中实现了 MySQL 协议兼容。社区已有 JDBC / MyBatis / Hibernate 等指南,本任务补齐 **Python ORM(Django)+ MySQL Python 驱动** 经 dolphin 连接 B 兼容模式数据库的验证、适配与文档。
9+ 
10+按社区评审要求:**优先支持原生 `django.db.backends.mysql`**;dolphin 缺少的 MySQL 变量/能力在 **Plugin** 仓补齐,而不是仅靠客户端自定义 Backend 绕过。
11+ 
12+## 2. 目标与范围
13+ 
14+| 目标 | 说明 |
15+|------|------|
16+| 连通性 | Django 经 `dolphin_server_port`(如 3308)完成认证与会话 |
17+| 原生 Backend | 使用官方 `django.db.backends.mysql` |
18+| Plugin 兼容 | 为 Django 探测变量提供兼容桩(无真实引擎语义) |
19+| 业务可用性 | 覆盖 ORM 查询与 CRUD 主路径 |
20+| 交付 | examples 示例/自测/思维导图;docs 开发指南;Plugin 补丁 |
21+ 
22+**非目标:** 存储过程、server-side cursor 等协议明确不支持能力的强制支持;实现真实 InnoDB 引擎。
23+ 
24+## 3. 环境约束
25+ 
26+- OS:openEuler 20.03 LTS(x86_64 / aarch64)
27+- DB:openGauss 7.0.0-LTS(正式包未上架时可用 archive_test 转测包,需含 dolphin 的 Server 版,非 Lite)
28+- 协议:`enable_dolphin_proto=on``dolphin_server_port` ≠ 原生 port
29+- 客户端:Python 3.8+,Django 4.2+/5.0,PyMySQL(或 mysqlclient)
30+ 
31+## 4. 总体方案
32+ 
33+```text
34+Django App
35+ -> ENGINE=django.db.backends.mysql
36+ -> PyMySQL (MySQLdb 兼容层)
37+ -> TCP :dolphin_server_port
38+ -> openGauss B DB + dolphin
39+ @@default_storage_engine -> "InnoDB" (stub)
40+ @@sql_auto_is_null -> 0 (stub, SET 可写)
41+```
42+ 
43+### 4.1 服务端配置要点
44+ 
45+1. 创建 B 库:`CREATE DATABASE ... DBCOMPATIBILITY 'B'`
46+2. `CREATE EXTENSION dolphin`(如需要)
47+3. `SELECT set_native_password(user, password, '')`
48+4. GUC:`listen_addresses``enable_dolphin_proto``dolphin_server_port``dolphin.default_database_name`
49+ 
50+### 4.2 Plugin 适配要点(评审结论)
51+ 
52+原生 Django MySQL backend 在 `mysql_server_data` 中会查询:
53+ 
54+- `@@sql_mode`
55+- `@@default_storage_engine` ← 原报错 **28804**(变量不存在)
56+- `@@sql_auto_is_null`
57+- `@@lower_case_table_names`
58+-
59+ 
60+openGauss **没有** MySQL 存储引擎实现。参考 `contrib/dolphin/plugin_postgres.cpp``query_cache_size` / `system_time_zone` 等“无实际语义、仅兼容客户端探测”的变量做法:
61+ 
62+1.`BSqlPluginContext` 增加字段
63+2. `DefineCustomStringVariable("default_storage_engine", ...)` 默认 `"InnoDB"`
64+3. `DefineCustomIntVariable("sql_auto_is_null", ...)` 默认 `0`(支持 Django `SET SQL_AUTO_IS_NULL = 0`
65+4. SET 时打 WARNING 标明无实际含义(与现有 stub 一致)
66+ 
67+这样框架可正常初始化,且不引入虚假引擎能力。
68+ 
69+### 4.3 示例业务
70+ 
71+- Schema/DB:`mysql_test_db`
72+- 表:`user(id, name, age)`
73+- 脚本:`scripts/run_demo.py`(查询/增/改/删)
74+- 过渡:未合入 Plugin 的环境可设 `OG_MYSQL_ENGINE=opengauss_mysql`
75+ 
76+## 5. 测试设计
77+ 
78+| 编号 | 场景 | 通过标准 |
79+|------|------|----------|
80+| T0 | 版本与进程 | `gs_ctl status` running |
81+| T1 | 端口 | 5432 / 3308 LISTEN |
82+| T2 | 协议认证 | PyMySQL 握手成功 |
83+| T3 | DDL | 建库建表成功 |
84+| T4 | `SELECT @@default_storage_engine` | 返回 `InnoDB` |
85+| T5 | ORM CRUD(原生 mysql backend) | 打印 `DEMO_OK` |
86+| T6 | Django 安装检查 | 打印 `INSTALL_OK` |
87+| T7 | 不支持项 | 文档标注跳过 |
88+ 
89+## 6. 风险与对策
90+ 
91+| 风险 | 对策 |
92+|------|------|
93+| 正式 LTS 包不可得 | 使用同系列 archive_test 转测包并在报告中说明 |
94+| Lite 无 dolphin | 强制 Server/企业版 |
95+| 敏感信息入库 | 密码仅通过环境变量注入,文档脱敏 |
96+| 协议能力差异 | 文档明确不支持项,避免误用 |
97+| 兼容桩被误用为真实引擎能力 | 文档/WARNING 标明无实际含义 |
98+ 
99+## 7. 交付物清单
100+ 
101+1. `DjangoConnectOpenGaussB/` 示例工程(默认原生 mysql backend)
102+2. `docs/自测报告.md``docs/测试思维导图.*``doc/设计文档.md`
103+3. docs 仓 `django_development.md``_toc.yaml`
104+4. Plugin:`default_storage_engine` / `sql_auto_is_null` 兼容变量 + 回归用例
@@ -0,0 +1,16 @@
1+#!/usr/bin/env python
2+import os
3+import sys
4+ 
5+ 
6+def main():
7+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
8+ # Ensure PyMySQL registers before Django imports MySQLdb
9+ import pymysql_bootstrap # noqa: F401
10+ from django.core.management import execute_from_command_line
11+ 
12+ execute_from_command_line(sys.argv)
13+ 
14+ 
15+if __name__ == "__main__":
16+ main()
@@ -0,0 +1 @@
1+# openGauss-compatible Django MySQL backend (dolphin protocol)
@@ -0,0 +1,78 @@
1+"""
2+Django database backend for openGauss B-mode via dolphin MySQL protocol.
3+ 
4+Subclass of django.db.backends.mysql with probes softened for openGauss:
5+- avoid @@default_storage_engine / unsupported session vars
6+- fake a MySQL 8.0.28 version tuple so Django feature flags work
7+"""
8+ 
9+from django.db.backends.mysql.base import DatabaseWrapper as MySQLDatabaseWrapper
10+from django.utils.asyncio import async_unsafe
11+from django.utils.functional import cached_property
12+ 
13+ 
14+class DatabaseWrapper(MySQLDatabaseWrapper):
15+ vendor = "mysql"
16+ display_name = "openGauss (MySQL protocol)"
17+ 
18+ @cached_property
19+ def mysql_server_data(self):
20+ version = "8.0.28-openGauss"
21+ sql_mode = "STRICT_TRANS_TABLES"
22+ lower_case = True
23+ try:
24+ with self.temporary_connection() as cursor:
25+ cursor.execute("SELECT VERSION()")
26+ row = cursor.fetchone()
27+ if row and row[0]:
28+ # Keep Django's parser happy by prefixing a MySQL-like version.
29+ version = f"8.0.28-openGauss ({row[0]})"
30+ try:
31+ cursor.execute("SELECT @@sql_mode")
32+ mode = cursor.fetchone()
33+ if mode and mode[0] is not None:
34+ sql_mode = str(mode[0])
35+ except Exception:
36+ pass
37+ try:
38+ cursor.execute("SELECT @@lower_case_table_names")
39+ lc = cursor.fetchone()
40+ if lc and lc[0] is not None:
41+ lower_case = bool(int(lc[0]))
42+ except Exception:
43+ pass
44+ except Exception:
45+ pass
46+ return {
47+ "version": version,
48+ "sql_mode": sql_mode,
49+ "default_storage_engine": "InnoDB",
50+ "sql_auto_is_null": False,
51+ "lower_case_table_names": lower_case,
52+ "has_zoneinfo_database": False,
53+ }
54+ 
55+ @cached_property
56+ def mysql_version(self):
57+ return (8, 0, 28)
58+ 
59+ @cached_property
60+ def mysql_is_mariadb(self):
61+ return False
62+ 
63+ def init_connection_state(self):
64+ # Skip MySQL-only session assignments that openGauss rejects.
65+ # Still honor isolation_level if configured.
66+ assignments = []
67+ if self.isolation_level:
68+ assignments.append(
69+ "SET SESSION TRANSACTION ISOLATION LEVEL %s"
70+ % self.isolation_level.upper()
71+ )
72+ if assignments:
73+ with self.cursor() as cursor:
74+ cursor.execute("; ".join(assignments))
75+ 
76+ @async_unsafe
77+ def get_connection_params(self):
78+ return super().get_connection_params()
@@ -0,0 +1,41 @@
1+"""
2+Bootstrap PyMySQL as MySQLdb so Django's mysql backend can talk to
3+openGauss dolphin MySQL protocol port.
4+ 
5+SELECT VERSION() is a kernel builtin and still returns the openGauss banner
6+(cannot CREATE OR REPLACE). Django's mysql backend uses re.match() from the
7+start of that string, so it cannot see the 7.0.0 inside. Dolphin's MySQL
8+handshake already advertises 8.0.28-dophin-server; parse that same tuple
9+when the banner is openGauss.
10+"""
11+import pymysql
12+ 
13+pymysql.install_as_MySQLdb()
14+ 
15+# Handshake version used by dolphin (plugin_protocol/dqformat.cpp).
16+_OPENGAUSS_MYSQL_VERSION = (8, 0, 28)
17+ 
18+ 
19+def _install_opengauss_version_compat():
20+ try:
21+ from django.db.backends.mysql.base import DatabaseWrapper
22+ from django.utils.functional import cached_property
23+ except Exception:
24+ return
25+ 
26+ orig = DatabaseWrapper.mysql_version.func
27+ 
28+ def mysql_version(self):
29+ try:
30+ return orig(self)
31+ except Exception:
32+ info = getattr(self, "mysql_server_info", "") or ""
33+ if "opengauss" in info.lower():
34+ return _OPENGAUSS_MYSQL_VERSION
35+ raise
36+ 
37+ DatabaseWrapper.mysql_version = cached_property(mysql_version)
38+ DatabaseWrapper.mysql_version.__set_name__(DatabaseWrapper, "mysql_version")
39+ 
40+ 
41+_install_opengauss_version_compat()
@@ -0,0 +1,3 @@
1+PyMySQL>=1.1.0
2+Django>=4.2,<5.1
3+cryptography>=41.0.0
@@ -0,0 +1,97 @@
1+#!/usr/bin/env python
2+"""CRUD demo for Django ORM against openGauss B-mode MySQL protocol."""
3+from __future__ import annotations
4+ 
5+import os
6+import sys
7+from pathlib import Path
8+ 
9+ROOT = Path(__file__).resolve().parents[1]
10+sys.path.insert(0, str(ROOT))
11+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
12+os.environ.setdefault("OG_MYSQL_HOST", "127.0.0.1")
13+os.environ.setdefault("OG_MYSQL_PORT", "3308")
14+os.environ.setdefault("OG_MYSQL_DB", "mysql_test_db")
15+os.environ.setdefault("OG_MYSQL_USER", "omm")
16+# OG_MYSQL_PASSWORD must be provided by the environment (do not hardcode secrets)
17+ 
18+if not os.environ.get("OG_MYSQL_PASSWORD"):
19+ print("ERROR: please export OG_MYSQL_PASSWORD before running this demo", file=sys.stderr)
20+ raise SystemExit(2)
21+ 
22+import pymysql_bootstrap # noqa: F401
23+import django
24+ 
25+django.setup()
26+ 
27+from demo_app.models import User
28+ 
29+ 
30+def main():
31+ created_ids: list[int] = []
32+ try:
33+ print("=== 查询所有用户 ===")
34+ for row in User.objects.all().order_by("id"):
35+ print(row)
36+ 
37+ print("=== 新增用户 ===")
38+ created = User(name="zhaoliu", age=18)
39+ created.save()
40+ created_ids.append(created.pk)
41+ print(f"新增 id={created.pk}")
42+ 
43+ print("=== 特殊字符读写 ===")
44+ special = User(name="O'Brien\\test", age=99)
45+ special.save()
46+ created_ids.append(special.pk)
47+ loaded = User.objects.get(pk=special.pk)
48+ if loaded.name != special.name:
49+ raise RuntimeError(
50+ f"特殊字符读写失败: expected {special.name!r}, got {loaded.name!r}"
51+ )
52+ print(f"特殊字符 OK: id={special.pk} name={loaded.name!r}")
53+ 
54+ print("=== 模糊查询用户名包含 zhao 的用户 ===")
55+ zhao_users = list(User.objects.filter(name__icontains="zhao").order_by("id"))
56+ if not zhao_users:
57+ raise RuntimeError("icontains 查询返回空集,兼容性可能存在问题")
58+ if created.pk not in {row.pk for row in zhao_users}:
59+ raise RuntimeError(f"本次创建 id={created.pk} 未出现在 icontains 结果中")
60+ for row in zhao_users:
61+ print(row)
62+ 
63+ print("=== 更新用户信息 ===")
64+ created.name = "zhaoliuliuliu"
65+ created.age = 28
66+ created.save()
67+ updated = User.objects.get(pk=created.pk)
68+ if updated.name != "zhaoliuliuliu" or updated.age != 28:
69+ raise RuntimeError(
70+ f"更新断言失败: id={created.pk} name={updated.name!r} age={updated.age}"
71+ )
72+ print(f"更新行: id={created.pk}")
73+ 
74+ print("=== 根据ID查询用户 ===")
75+ print(User.objects.get(pk=created.pk))
76+ 
77+ print("=== 删除用户 ===")
78+ deleted, _ = User.objects.filter(pk=created.pk).delete()
79+ if deleted != 1:
80+ raise RuntimeError(f"删除断言失败: expected 1 row, got {deleted}")
81+ if User.objects.filter(pk=created.pk).exists():
82+ raise RuntimeError(f"删除后 id={created.pk} 仍存在")
83+ created_ids.remove(created.pk)
84+ print(f"删除行数:{deleted}")
85+ 
86+ print("=== 查询所有用户 ===")
87+ for row in User.objects.all().order_by("id"):
88+ print(row)
89+ 
90+ print("DEMO_OK")
91+ finally:
92+ if created_ids:
93+ User.objects.filter(pk__in=created_ids).delete()
94+ 
95+ 
96+if __name__ == "__main__":
97+ main()