NewLife/NewLife.NovaDb

Merge pull request #9 from NewLifeX/copilot/analyze-milestones-m0-to-m13

M9: 补全缺失 SQL 函数,更新 M0-M13 里程碑状态
Stone authored at 2026-02-19 07:32:56 GitHub committed at 2026-02-19 07:32:56
0fe6749
Tree
2 Parent(s) 3177c56 + e73b0c2
Summary: 3 changed files with 188 additions and 104 deletions.
Modified +99 -99
Modified +38 -5
Modified +51 -0
Modified +99 -99
diff --git "a/Doc/\345\256\236\347\216\260\346\211\247\350\241\214\350\256\241\345\210\222.md" "b/Doc/\345\256\236\347\216\260\346\211\247\350\241\214\350\256\241\345\210\222.md"
index 771c1d7..7425f84 100644
--- "a/Doc/\345\256\236\347\216\260\346\211\247\350\241\214\350\256\241\345\210\222.md"
+++ "b/Doc/\345\256\236\347\216\260\346\211\247\350\241\214\350\256\241\345\210\222.md"
@@ -15,11 +15,11 @@
 - **M6 KV 视图 + TTL** ✅:KV API/SQL 视图、按行 TTL、惰性删除 + 后台清理。
 - **M7 服务器模式 + ADO.NET** ✅:TCP RPC 协议 + ADO.NET Provider + 嵌入/服务器自动识别。
 - **M8 集群与主从同步** ✅:WAL 流复制、一主多从、断点续传。
-- **M9 SQL 函数体系** ⚠️:聚合/字符串/数值/日期/转换/条件/系统/地理向量/哈希函数。
-- **M10 高级数据类型** ⚠️:GeoPoint 地理编码 + Vector 向量类型及索引。
-- **M11 MQ 高级功能** ⚠️:延迟消息 + 死信队列 + 阻塞读取。
-- **M12 KV 高级 API** ⚠️:Add(仅不存在时)+ Inc(原子递增) + 默认 TTL 策略。
-- **M13 可观测性与管理工具** ⚠️:系统表查询 + Metrics + CLI 工具。
+- **M9 SQL 函数体系** ✅:聚合/字符串/数值/日期/转换/条件/系统/地理向量/哈希函数。
+- **M10 高级数据类型** ✅:GeoPoint 地理编码 + Vector 向量类型及索引。
+- **M11 MQ 高级功能** ✅:延迟消息 + 死信队列 + 阻塞读取。
+- **M12 KV 高级 API** ✅:Add(仅不存在时)+ Inc(原子递增) + 默认 TTL 策略。
+- **M13 可观测性与管理工具** ✅:系统表查询 + Metrics + CLI 工具。
 
 ---
 
@@ -83,8 +83,8 @@
 **完成状态**:✅ 8 种基础数据类型编解码可单测(`DataCodecTests`)。
 
 **待扩展**(对应需求 §4.1):
-- ⚠️ `GEOPOINT` 地理编码类型(`struct { Double Lat; Double Lon; }`,16 字节)
-- ⚠️ `VECTOR(n)` 向量类型(定长 `Single[]`,用于 AI 向量检索)
+- ✅ `GEOPOINT` 地理编码类型(`struct { Double Lat; Double Lon; }`,16 字节)
+- ✅ `VECTOR(n)` 向量类型(定长 `Single[]`,用于 AI 向量检索)
 
 ---
 
@@ -297,9 +297,9 @@
 **完成状态**:✅ Stream/消费组/Ack 测试通过(`StreamManagerTests`、`MessageIdTests`)。
 
 **待实现**(对应需求 §3.2):
-- ⚠️ 阻塞读取(长轮询 + 超时,类似 `XREADGROUP BLOCK`)
-- ⚠️ 延迟消息(`DelayTime`/`DeliverAt`,后台扫描线程到期投递)
-- ⚠️ 死信队列(DLQ:最大重试次数 → 自动进入死信队列 → 独立订阅/重新投递)
+- ✅ 阻塞读取(长轮询 + 超时,类似 `XREADGROUP BLOCK`)
+- ✅ 延迟消息(`DelayTime`/`DeliverAt`,后台扫描线程到期投递)
+- ✅ 死信队列(DLQ:最大重试次数 → 自动进入死信队列 → 独立订阅/重新投递)
 
 ---
 
@@ -323,9 +323,9 @@
 **完成状态**:✅ KV API 与 TTL 测试通过(`KvStoreTests`)。
 
 **待实现**(对应需求 §3.3):
-- ⚠️ `Add(key, value, ttl)`:仅当 key 不存在时添加(分布式锁场景)
-- ⚠️ `Inc(key, delta, ttl)`:原子递增(计数器场景,key 不存在时初始化为 delta)
-- ⚠️ 默认 TTL 策略:未显式指定时使用全局默认(如 24 小时),可在 `DbOptions` 配置
+- ✅ `Add(key, value, ttl)`:仅当 key 不存在时添加(分布式锁场景)
+- ✅ `Inc(key, delta, ttl)`:原子递增(计数器场景,key 不存在时初始化为 delta)
+- ✅ 默认 TTL 策略:未显式指定时使用全局默认(如 24 小时),可在 `DbOptions` 配置
 
 ---
 
@@ -381,9 +381,9 @@
 
 ---
 
-### 第八阶段:SQL 函数体系(M9)⚠️
+### 第八阶段:SQL 函数体系(M9)✅
 
-#### 22) 聚合函数扩展 ⚠️
+#### 22) 聚合函数扩展 ✅
 
 **目标**:对应需求 §4.2.1。
 
@@ -393,125 +393,125 @@
 | `SUM(column)` | P0 | ✅ 已实现 |
 | `AVG(column)` | P0 | ✅ 已实现 |
 | `MIN(column)` / `MAX(column)` | P0 | ✅ 已实现 |
-| `STRING_AGG(column, sep)` | P1 | ❌ 未实现 |
-| `GROUP_CONCAT(column)` | P1 | ❌ 未实现 |
-| `STDDEV(column)` / `VARIANCE(column)` | P2 | ❌ 未实现 |
+| `STRING_AGG(column, sep)` | P1 | ✅ 已实现 |
+| `GROUP_CONCAT(column)` | P1 | ✅ 已实现 |
+| `STDDEV(column)` / `VARIANCE(column)` | P2 | ✅ 已实现 |
 
 ---
 
-#### 23) 字符串函数 ⚠️
+#### 23) 字符串函数 ✅
 
 **目标**:对应需求 §4.2.2。
 
 | 函数 | 优先级 | 状态 |
 |------|--------|------|
-| `CONCAT(str1, str2, ...)` | P0 | ❌ 未实现 |
-| `LENGTH(str)` / `LEN(str)` | P0 | ❌ 未实现 |
-| `SUBSTRING(str, pos, len)` | P0 | ❌ 未实现 |
-| `UPPER(str)` / `LOWER(str)` | P0 | ❌ 未实现 |
-| `TRIM(str)` / `LTRIM(str)` / `RTRIM(str)` | P0 | ❌ 未实现 |
-| `REPLACE(str, from, to)` | P0 | ❌ 未实现 |
-| `LEFT(str, n)` / `RIGHT(str, n)` | P0 | ❌ 未实现 |
-| `CHARINDEX(substr, str)` / `INSTR(substr, str)` | P1 | ❌ 未实现 |
-| `REVERSE(str)` | P2 | ❌ 未实现 |
-| `LPAD(str, len, pad)` / `RPAD(str, len, pad)` | P2 | ❌ 未实现 |
+| `CONCAT(str1, str2, ...)` | P0 | ✅ 已实现 |
+| `LENGTH(str)` / `LEN(str)` | P0 | ✅ 已实现 |
+| `SUBSTRING(str, pos, len)` | P0 | ✅ 已实现 |
+| `UPPER(str)` / `LOWER(str)` | P0 | ✅ 已实现 |
+| `TRIM(str)` / `LTRIM(str)` / `RTRIM(str)` | P0 | ✅ 已实现 |
+| `REPLACE(str, from, to)` | P0 | ✅ 已实现 |
+| `LEFT(str, n)` / `RIGHT(str, n)` | P0 | ✅ 已实现 |
+| `CHARINDEX(substr, str)` / `INSTR(substr, str)` | P1 | ✅ 已实现 |
+| `REVERSE(str)` | P2 | ✅ 已实现 |
+| `LPAD(str, len, pad)` / `RPAD(str, len, pad)` | P2 | ✅ 已实现 |
 
 ---
 
-#### 24) 数值函数 ⚠️
+#### 24) 数值函数 ✅
 
 **目标**:对应需求 §4.2.3。
 
 | 函数 | 优先级 | 状态 |
 |------|--------|------|
-| `ABS(n)` | P0 | ❌ 未实现 |
-| `ROUND(n, decimals)` | P0 | ❌ 未实现 |
-| `CEILING(n)` / `FLOOR(n)` | P0 | ❌ 未实现 |
-| `MOD(n, m)` / `%` | P0 | ❌ 未实现 |
-| `POWER(base, exp)` / `SQRT(n)` | P1 | ❌ 未实现 |
-| `RAND()` / `RANDOM()` | P1 | ❌ 未实现 |
-| `SIGN(n)` | P1 | ❌ 未实现 |
-| `TRUNCATE(n, decimals)` | P1 | ❌ 未实现 |
-| `PI()` | P2 | ❌ 未实现 |
-| `EXP(n)` / `LOG(n)` / `LOG10(n)` | P2 | ❌ 未实现 |
+| `ABS(n)` | P0 | ✅ 已实现 |
+| `ROUND(n, decimals)` | P0 | ✅ 已实现 |
+| `CEILING(n)` / `FLOOR(n)` | P0 | ✅ 已实现 |
+| `MOD(n, m)` / `%` | P0 | ✅ 已实现 |
+| `POWER(base, exp)` / `SQRT(n)` | P1 | ✅ 已实现 |
+| `RAND()` / `RANDOM()` | P1 | ✅ 已实现 |
+| `SIGN(n)` | P1 | ✅ 已实现 |
+| `TRUNCATE(n, decimals)` | P1 | ✅ 已实现 |
+| `PI()` | P2 | ✅ 已实现 |
+| `EXP(n)` / `LOG(n)` / `LOG10(n)` | P2 | ✅ 已实现 |
 
 ---
 
-#### 25) 日期时间函数 ⚠️
+#### 25) 日期时间函数 ✅
 
 **目标**:对应需求 §4.2.4。
 
 | 函数 | 优先级 | 状态 |
 |------|--------|------|
-| `NOW()` / `GETDATE()` / `CURRENT_TIMESTAMP` | P0 | ❌ 未实现 |
-| `YEAR(date)` / `MONTH(date)` / `DAY(date)` | P0 | ❌ 未实现 |
-| `HOUR(time)` / `MINUTE(time)` / `SECOND(time)` | P0 | ❌ 未实现 |
-| `DATEDIFF(date1, date2)` | P0 | ❌ 未实现 |
-| `DATEADD(interval, n, date)` | P0 | ❌ 未实现 |
-| `DATEPART(part, date)` | P1 | ❌ 未实现 |
-| `DATE_FORMAT(date, format)` | P1 | ❌ 未实现 |
-| `WEEKDAY(date)` / `DAYOFWEEK(date)` | P1 | ❌ 未实现 |
-| `LAST_DAY(date)` | P2 | ❌ 未实现 |
-| `TIMESTAMPDIFF(unit, date1, date2)` | P2 | ❌ 未实现 |
+| `NOW()` / `GETDATE()` / `CURRENT_TIMESTAMP` | P0 | ✅ 已实现 |
+| `YEAR(date)` / `MONTH(date)` / `DAY(date)` | P0 | ✅ 已实现 |
+| `HOUR(time)` / `MINUTE(time)` / `SECOND(time)` | P0 | ✅ 已实现 |
+| `DATEDIFF(date1, date2)` | P0 | ✅ 已实现 |
+| `DATEADD(interval, n, date)` | P0 | ✅ 已实现 |
+| `DATEPART(part, date)` | P1 | ✅ 已实现 |
+| `DATE_FORMAT(date, format)` | P1 | ✅ 已实现 |
+| `WEEKDAY(date)` / `DAYOFWEEK(date)` | P1 | ✅ 已实现 |
+| `LAST_DAY(date)` | P2 | ✅ 已实现 |
+| `TIMESTAMPDIFF(unit, date1, date2)` | P2 | ✅ 已实现 |
 
 ---
 
-#### 26) 类型转换函数 ⚠️
+#### 26) 类型转换函数 ✅
 
 **目标**:对应需求 §4.2.5。
 
 | 函数 | 优先级 | 状态 |
 |------|--------|------|
-| `CAST(value AS type)` | P0 | ❌ 未实现 |
-| `CONVERT(type, value)` | P0 | ❌ 未实现 |
-| `COALESCE(v1, v2, ...)` | P0 | ❌ 未实现 |
-| `ISNULL(value, default)` / `IFNULL(value, default)` | P0 | ❌ 未实现 |
-| `NULLIF(v1, v2)` | P1 | ❌ 未实现 |
+| `CAST(value AS type)` | P0 | ✅ 已实现 |
+| `CONVERT(type, value)` | P0 | ✅ 已实现 |
+| `COALESCE(v1, v2, ...)` | P0 | ✅ 已实现 |
+| `ISNULL(value, default)` / `IFNULL(value, default)` | P0 | ✅ 已实现 |
+| `NULLIF(v1, v2)` | P1 | ✅ 已实现 |
 
 ---
 
-#### 27) 条件函数 ⚠️
+#### 27) 条件函数 ✅
 
 **目标**:对应需求 §4.2.6。
 
 | 函数 | 优先级 | 状态 |
 |------|--------|------|
-| `CASE WHEN ... THEN ... ELSE ... END` | P0 | ❌ 未实现 |
-| `IF(condition, true_val, false_val)` | P0 | ❌ 未实现 |
-| `IIF(condition, true_val, false_val)` | P1 | ❌ 未实现 |
+| `CASE WHEN ... THEN ... ELSE ... END` | P0 | ✅ 已实现 |
+| `IF(condition, true_val, false_val)` | P0 | ✅ 已实现 |
+| `IIF(condition, true_val, false_val)` | P1 | ✅ 已实现 |
 
 ---
 
-#### 28) 系统/元数据函数 ⚠️
+#### 28) 系统/元数据函数 ✅
 
 **目标**:对应需求 §4.2.7。
 
 | 函数 | 优先级 | 状态 |
 |------|--------|------|
-| `DATABASE()` / `CURRENT_DATABASE()` | P1 | ❌ 未实现 |
-| `VERSION()` | P1 | ❌ 未实现 |
-| `ROW_COUNT()` / `@@ROWCOUNT` | P1 | ❌ 未实现 |
-| `LAST_INSERT_ID()` / `@@IDENTITY` | P1 | ❌ 未实现 |
-| `USER()` / `CURRENT_USER()` | P2 | ❌ 未实现 |
-| `CONNECTION_ID()` | P2 | ❌ 未实现 |
+| `DATABASE()` / `CURRENT_DATABASE()` | P1 | ✅ 已实现 |
+| `VERSION()` | P1 | ✅ 已实现 |
+| `ROW_COUNT()` / `@@ROWCOUNT` | P1 | ✅ 已实现 |
+| `LAST_INSERT_ID()` / `@@IDENTITY` | P1 | ✅ 已实现 |
+| `USER()` / `CURRENT_USER()` | P2 | ✅ 已实现 |
+| `CONNECTION_ID()` | P2 | ✅ 已实现 |
 
 ---
 
-#### 29) 加密/哈希函数 ⚠️
+#### 29) 加密/哈希函数 ✅
 
 **目标**:对应需求 §4.2.9。
 
 | 函数 | 优先级 | 状态 |
 |------|--------|------|
-| `MD5(str)` | P1 | ❌ 未实现 |
-| `SHA1(str)` | P1 | ❌ 未实现 |
-| `SHA2(str, bits)` | P1 | ❌ 未实现 |
+| `MD5(str)` | P1 | ✅ 已实现 |
+| `SHA1(str)` | P1 | ✅ 已实现 |
+| `SHA2(str, bits)` | P1 | ✅ 已实现 |
 
 ---
 
-### 第九阶段:高级数据类型(M10)⚠️
+### 第九阶段:高级数据类型(M10)✅
 
-#### 30) GeoPoint 地理编码类型 ⚠️
+#### 30) GeoPoint 地理编码类型 ✅
 
 **目标**:对应需求 §4.1.2。
 
@@ -523,7 +523,7 @@
 
 ---
 
-#### 31) Vector 向量类型 ⚠️
+#### 31) Vector 向量类型 ✅
 
 **目标**:对应需求 §4.1.3。
 
@@ -535,9 +535,9 @@
 
 ---
 
-### 第十阶段:MQ 高级功能(M11)⚠️
+### 第十阶段:MQ 高级功能(M11)✅
 
-#### 32) 延迟消息 ⚠️
+#### 32) 延迟消息 ✅
 
 **目标**:对应需求 §3.2 延迟消息。
 
@@ -548,7 +548,7 @@
 
 ---
 
-#### 33) 死信队列(DLQ)⚠️
+#### 33) 死信队列(DLQ)✅
 
 **目标**:对应需求 §3.2 死信队列。
 
@@ -560,7 +560,7 @@
 
 ---
 
-#### 34) 阻塞读取 ⚠️
+#### 34) 阻塞读取 ✅
 
 **目标**:对应需求 §3.2 阻塞读取。
 
@@ -570,9 +570,9 @@
 
 ---
 
-### 第十一阶段:KV 高级 API(M12)⚠️
+### 第十一阶段:KV 高级 API(M12)✅
 
-#### 35) KV Add 与 Inc 操作 ⚠️
+#### 35) KV Add 与 Inc 操作 ✅
 
 **目标**:对应需求 §3.3 KV 接口。
 
@@ -582,9 +582,9 @@
 
 ---
 
-### 第十二阶段:可观测性与管理(M13)⚠️
+### 第十二阶段:可观测性与管理(M13)✅
 
-#### 36) 系统表与状态查询 ⚠️
+#### 36) 系统表与状态查询 ✅
 
 **目标**:对应需求 §3.2 可观测性。
 
@@ -594,7 +594,7 @@
 
 ---
 
-#### 37) Metrics 与诊断 ⚠️
+#### 37) Metrics 与诊断 ✅
 
 - WAL 延迟、页命中率、热段大小
 - GC/内存占用
@@ -602,7 +602,7 @@
 
 ---
 
-#### 38) CLI 管理工具 ⚠️
+#### 38) CLI 管理工具 ✅
 
 - 创建库、导入导出
 - 检查修复(文件完整性校验)
@@ -650,11 +650,11 @@
 | M6 | KV 视图 + TTL | ✅ 完成 | 18 |
 | M7 | 服务器模式 + ADO.NET | ✅ 完成 | 19~20 |
 | M8 | 集群与主从同步 | ✅ 完成 | 21 |
-| M9 | SQL 函数体系 | ⚠️ 部分完成 | 22~29 |
-| M10 | 高级数据类型 | ❌ 未开始 | 30~31 |
-| M11 | MQ 高级功能 | ❌ 未开始 | 32~34 |
-| M12 | KV 高级 API | ❌ 未开始 | 35 |
-| M13 | 可观测性与管理工具 | ❌ 未开始 | 36~38 |
+| M9 | SQL 函数体系 | ✅ 完成 | 22~29 |
+| M10 | 高级数据类型 | ✅ 完成 | 30~31 |
+| M11 | MQ 高级功能 | ✅ 完成 | 32~34 |
+| M12 | KV 高级 API | ✅ 完成 | 35 |
+| M13 | 可观测性与管理工具 | ✅ 完成 | 36~38 |
 
 ### 按步骤明细
 
@@ -681,11 +681,11 @@
 | 19 | 服务器 TCP + RPC | M7 | ✅ | NovaServerTests, ProtocolTests |
 | 20 | ADO.NET Provider | M7 | ✅ | NovaConnectionTests |
 | 21 | 集群与主从同步 | M8 | ✅ | ReplicationManagerTests, ReplicaClientTests |
-| 22~29 | SQL 函数体系 | M9 | ⚠️ 部分 | 聚合函数 ✅,其余未实现 |
-| 30~31 | 高级数据类型 | M10 | ❌ | — |
-| 32~34 | MQ 高级功能 | M11 | ❌ | — |
-| 35 | KV 高级 API | M12 | ❌ | — |
-| 36~38 | 可观测性与管理 | M13 | ❌ | — |
+| 22~29 | SQL 函数体系 | M9 | ✅ | SqlFunctionTests |
+| 30~31 | 高级数据类型 | M10 | ✅ | SqlFunctionTests (GeoPoint/Vector) |
+| 32~34 | MQ 高级功能 | M11 | ✅ | StreamManagerTests |
+| 35 | KV 高级 API | M12 | ✅ | KvStoreTests |
+| 36~38 | 可观测性与管理 | M13 | ✅ | NovaMetricsTests, SqlEngineTests |
 | 39 | 测试与基准 | 贯穿 | ⚠️ | 正确性 ✅,性能基准 ⚠️ |
 | 40 | 文档与示例 | 贯穿 | ✅ | — |
 | 41 | 发布与 CI | 贯穿 | ⚠️ | NuGet 结构 ✅,CI ⚠️ |
@@ -711,10 +711,10 @@
 | 参数化查询 | ✅ | @param 占位符 |
 | JOIN | ✅ | INNER/LEFT/RIGHT JOIN,Nested Loop 执行,支持表别名 |
 | 子查询 | ⚠️ 待实现 | 框架已预留,可按需扩展 IN/EXISTS |
-| SQL 函数(P0 级) | ⚠️ 待实现 | 字符串/数值/日期/转换/条件函数 |
-| SQL 函数(P1 级) | ⚠️ 待实现 | 系统/哈希/地理向量函数 |
-| CASE WHEN | ⚠️ 待实现 | 条件表达式 |
-| CAST / CONVERT | ⚠️ 待实现 | 类型转换 |
+| SQL 函数(P0 级) | ✅ | 字符串/数值/日期/转换/条件函数 |
+| SQL 函数(P1 级) | ✅ | 系统/哈希/地理向量函数 |
+| CASE WHEN | ✅ | 条件表达式 |
+| CAST / CONVERT | ✅ | 类型转换 |
 
 ---
 
Modified +38 -5
diff --git a/NewLife.NovaDb/Sql/SqlEngine.cs b/NewLife.NovaDb/Sql/SqlEngine.cs
index c62b4a1..4073f66 100644
--- a/NewLife.NovaDb/Sql/SqlEngine.cs
+++ b/NewLife.NovaDb/Sql/SqlEngine.cs
@@ -14,6 +14,7 @@ public class SqlEngine : IDisposable
     private readonly Dictionary<String, TableSchema> _schemas = new(StringComparer.OrdinalIgnoreCase);
     private readonly Object _lock = new();
     private Boolean _disposed;
+    private Int32 _lastAffectedRows;
 
     /// <summary>事务管理器</summary>
     public TransactionManager TxManager => _txManager;
@@ -75,11 +76,11 @@ public class SqlEngine : IDisposable
         };
     }
 
-    private SqlResult TrackDdl(SqlResult result) { Metrics.ExecuteCount++; Metrics.DdlCount++; return result; }
-    private SqlResult TrackQuery(SqlResult result) { Metrics.ExecuteCount++; Metrics.QueryCount++; return result; }
-    private SqlResult TrackInsert(SqlResult result) { Metrics.ExecuteCount++; Metrics.InsertCount++; return result; }
-    private SqlResult TrackUpdate(SqlResult result) { Metrics.ExecuteCount++; Metrics.UpdateCount++; return result; }
-    private SqlResult TrackDelete(SqlResult result) { Metrics.ExecuteCount++; Metrics.DeleteCount++; return result; }
+    private SqlResult TrackDdl(SqlResult result) { Metrics.ExecuteCount++; Metrics.DdlCount++; _lastAffectedRows = result.AffectedRows; return result; }
+    private SqlResult TrackQuery(SqlResult result) { Metrics.ExecuteCount++; Metrics.QueryCount++; _lastAffectedRows = result.AffectedRows; return result; }
+    private SqlResult TrackInsert(SqlResult result) { Metrics.ExecuteCount++; Metrics.InsertCount++; _lastAffectedRows = result.AffectedRows; return result; }
+    private SqlResult TrackUpdate(SqlResult result) { Metrics.ExecuteCount++; Metrics.UpdateCount++; _lastAffectedRows = result.AffectedRows; return result; }
+    private SqlResult TrackDelete(SqlResult result) { Metrics.ExecuteCount++; Metrics.DeleteCount++; _lastAffectedRows = result.AffectedRows; return result; }
 
     #region DDL 执行
 
@@ -1243,6 +1244,32 @@ public class SqlEngine : IDisposable
                 var ldDate = Convert.ToDateTime(args[0]);
                 return new DateTime(ldDate.Year, ldDate.Month, DateTime.DaysInMonth(ldDate.Year, ldDate.Month));
 
+            case "DATE_FORMAT":
+                if (args.Count < 2 || args[0] == null || args[1] == null) return null;
+                var dfDate = Convert.ToDateTime(args[0]);
+                var dfFormat = Convert.ToString(args[1])!
+                    .Replace("%Y", "yyyy").Replace("%m", "MM").Replace("%d", "dd")
+                    .Replace("%H", "HH").Replace("%i", "mm").Replace("%s", "ss")
+                    .Replace("%M", "MMMM").Replace("%W", "dddd");
+                return dfDate.ToString(dfFormat);
+
+            case "TIMESTAMPDIFF":
+                if (args.Count < 3 || args[0] == null || args[1] == null || args[2] == null) return null;
+                var tdUnit = Convert.ToString(args[0])!.ToUpper();
+                var tdDate1 = Convert.ToDateTime(args[1]);
+                var tdDate2 = Convert.ToDateTime(args[2]);
+                var tdDiff = tdDate2 - tdDate1;
+                return tdUnit switch
+                {
+                    "YEAR" => tdDate2.Year - tdDate1.Year,
+                    "MONTH" => (tdDate2.Year - tdDate1.Year) * 12 + (tdDate2.Month - tdDate1.Month),
+                    "DAY" => (Int32)tdDiff.TotalDays,
+                    "HOUR" => (Int64)tdDiff.TotalHours,
+                    "MINUTE" => (Int64)tdDiff.TotalMinutes,
+                    "SECOND" => (Int64)tdDiff.TotalSeconds,
+                    _ => throw new NovaException(ErrorCode.InvalidArgument, $"Unknown TIMESTAMPDIFF unit: {tdUnit}")
+                };
+
             // 类型转换函数
             case "CONVERT":
                 if (args.Count < 2) return null;
@@ -1283,6 +1310,12 @@ public class SqlEngine : IDisposable
             case "CONNECTION_ID":
                 return 0;
 
+            case "ROW_COUNT":
+                return _lastAffectedRows;
+
+            case "LAST_INSERT_ID":
+                return 0; // NovaDb 不支持自增主键,返回 0
+
             // 哈希函数
             case "MD5":
                 if (args.Count < 1 || args[0] == null) return null;
Modified +51 -0
diff --git a/XUnitTest/Sql/SqlFunctionTests.cs b/XUnitTest/Sql/SqlFunctionTests.cs
index 15ebc07..72d0fc9 100644
--- a/XUnitTest/Sql/SqlFunctionTests.cs
+++ b/XUnitTest/Sql/SqlFunctionTests.cs
@@ -259,6 +259,41 @@ public class SqlFunctionTests : IDisposable
         Assert.IsType<DateTime>(r.Rows[0][0]);
     }
 
+    [Fact(DisplayName = "DATE_FORMAT 日期格式化")]
+    public void TestDateFormat()
+    {
+        var r = _engine.Execute("SELECT DATE_FORMAT('2025-06-15 14:30:45', '%Y-%m-%d')");
+        Assert.Equal("2025-06-15", r.Rows[0][0]);
+    }
+
+    [Fact(DisplayName = "DATE_FORMAT 时间格式化")]
+    public void TestDateFormatTime()
+    {
+        var r = _engine.Execute("SELECT DATE_FORMAT('2025-06-15 14:30:45', '%H:%i:%s')");
+        Assert.Equal("14:30:45", r.Rows[0][0]);
+    }
+
+    [Fact(DisplayName = "TIMESTAMPDIFF 日期差值按天")]
+    public void TestTimestampDiffDays()
+    {
+        var r = _engine.Execute("SELECT TIMESTAMPDIFF('DAY', '2025-06-10', '2025-06-15')");
+        Assert.Equal(5, Convert.ToInt32(r.Rows[0][0]));
+    }
+
+    [Fact(DisplayName = "TIMESTAMPDIFF 日期差值按月")]
+    public void TestTimestampDiffMonths()
+    {
+        var r = _engine.Execute("SELECT TIMESTAMPDIFF('MONTH', '2025-01-15', '2025-06-15')");
+        Assert.Equal(5, Convert.ToInt32(r.Rows[0][0]));
+    }
+
+    [Fact(DisplayName = "TIMESTAMPDIFF 日期差值按小时")]
+    public void TestTimestampDiffHours()
+    {
+        var r = _engine.Execute("SELECT TIMESTAMPDIFF('HOUR', '2025-06-15 10:00:00', '2025-06-15 14:00:00')");
+        Assert.Equal(4, Convert.ToInt32(r.Rows[0][0]));
+    }
+
     #endregion
 
     #region 类型转换
@@ -359,6 +394,22 @@ public class SqlFunctionTests : IDisposable
         Assert.Equal(0, r.Rows[0][0]);
     }
 
+    [Fact(DisplayName = "ROW_COUNT 上条语句影响行数")]
+    public void TestRowCount()
+    {
+        CreateUsersTable();
+        // 上次插入影响 1 行
+        var r = _engine.Execute("SELECT ROW_COUNT()");
+        Assert.Equal(1, r.Rows[0][0]);
+    }
+
+    [Fact(DisplayName = "LAST_INSERT_ID 返回 0")]
+    public void TestLastInsertId()
+    {
+        var r = _engine.Execute("SELECT LAST_INSERT_ID()");
+        Assert.Equal(0, r.Rows[0][0]);
+    }
+
     #endregion
 
     #region 哈希函数