fix(ddic): Data Preview 400 报错携带 SAP 原文 + 纠正 SQL 方言结论(= 需两侧空格,非不支持)

This commit is contained in:
coordinator
2026-09-11 02:00:50 +08:00
parent 53ea2d854d
commit f9d08774fd
6 changed files with 55 additions and 23 deletions
+8 -5
View File
@@ -120,21 +120,24 @@ NW 7.40 的 double-activate 已内置在 sap-cli 中。如果 activate 仍然失
| HTTP 423 (Transport lock) | 对象绑定在传输请求中 | 报告用户,SE09 处理 | | HTTP 423 (Transport lock) | 对象绑定在传输请求中 | 报告用户,SE09 处理 |
| 激活失败仍 inactive | NW 7.40 ADT 限制 | sap-cli 已内置 double-activate,仍失败则报告用户去 SE09 | | 激活失败仍 inactive | NW 7.40 ADT 限制 | sap-cli 已内置 double-activate,仍失败则报告用户去 SE09 |
| `WITH EMPTY KEY` 运行 dump | NW 7.40 不支持 | 修改为 `WITH NON-UNIQUE KEY``WITH DEFAULT KEY` | | `WITH EMPTY KEY` 运行 dump | NW 7.40 不支持 | 修改为 `WITH NON-UNIQUE KEY``WITH DEFAULT KEY` |
| HTTP 400 (Data Preview) `A Boolean expression was expected in <字段>=` | ADT Data Preview freestyle SQL **不支持 `=` 比较**(实测`WHERE f='x'`→400`WHERE f IN ('x')`200 | `IN (...)` `LIKE '...'`,不要用 `=` | | HTTP 400 (Data Preview) `A Boolean expression was expected in <字段>=` | **运算符两侧缺空格**`WHERE f='x'` 解析失败(`=` 被黏在值上);`WHERE f = 'x'`(有空格)实测 200 | 运算符两侧留空格,或一律`IN (...)` / `LIKE '...'` |
| HTTP 400 (Data Preview) `elements in the SELECT LIST list must be separated using commas` | 选择列必须逗号分隔(`SELECT a b` 报错) | 写成 `SELECT a, b FROM t` | | HTTP 400 (Data Preview) `elements in the SELECT LIST list must be separated using commas` | 选择列必须逗号分隔(`SELECT a b` 报错) | 写成 `SELECT a, b FROM t` |
| `info` 的「传输请求」恒为 `(无法获取)` | `_query_transport_request``=` + 单引号查 E071,在该方言下必然 400(异常被吞,故静默失败) | 改为 `WHERE object IN ('...') AND obj_name IN ('...')` | | HTTP 400 (Data Preview) `未知的列名称 "<COL>" until runtime` | SELECT/WHERE 用了**不存在的列名**(如 DD02T 没有 `LANGUAGE`/`MASTERLANG`,语言列是 `DDLANGUAGE`) | 先确认 DDIC 列名再查;报错已携带 SAP 原文 |
| `info` 的「传输请求」恒为 `(无法获取)` | (已修复)旧版 `_query_transport_request` 用无空格 `=` 查 E071 必 400 且异常被吞 | 已改 `IN (...)` 并区分「无」与「无法获取」 |
## ADT Data Preview SQL 方言(NW 7.40 实测,2026-09-11 ## ADT Data Preview SQL 方言(NW 7.40 实测,2026-09-11 二次修订
用于补 ZMM_BIP 系列等元数据时,本系统 `/sap/bc/adt/datapreview/freestyle` 的 SQL 解析器只接受很窄的子集 用于补 ZMM_BIP 系列等元数据时,本系统 `/sap/bc/adt/datapreview/freestyle` 的 SQL 解析器要求
| 能力 | 实测结果 | | 能力 | 实测结果 |
|------|----------| |------|----------|
| `SELECT f1, f2 FROM t`(逗号分隔) | ✅ 200 | | `SELECT f1, f2 FROM t`(逗号分隔) | ✅ 200 |
| `WHERE f = 'a'`(**运算符两侧有空格** | ✅ 200(2026-09-11 复测纠正:早前「不支持 =」的结论是探针写法无空格所致) |
| `WHERE f='a'`**无空格** | ❌ 400 `A Boolean expression was expected` |
| `WHERE f IN ('a','b')` | ✅ 200 | | `WHERE f IN ('a','b')` | ✅ 200 |
| `WHERE f LIKE 'ZMM%'` | ✅ 200 | | `WHERE f LIKE 'ZMM%'` | ✅ 200 |
| `WHERE f = 'a'` | ❌ 400 `A Boolean expression was expected` |
| `SELECT f1 f2`(无逗号) | ❌ 400 要求逗号分隔 | | `SELECT f1 f2`(无逗号) | ❌ 400 要求逗号分隔 |
| `SELECT <不存在的列>` | ❌ 400 `未知的列名称 "X" until runtime`SAP 原文会指明列名) |
| 无 WHERE 全表 | ✅ 200(但行数受行数参数与授权限制) | | 无 WHERE 全表 | ✅ 200(但行数受行数参数与授权限制) |
**可用的元数据来源(本机实测)** **可用的元数据来源(本机实测)**
+6 -4
View File
@@ -120,17 +120,19 @@ python main.py read-table --name ZSAPILOT_OBJ --max-rows 50
- ❌ 把「S4T 演示机通过」表述为「CEM/生产验证通过」 - ❌ 把「S4T 演示机通过」表述为「CEM/生产验证通过」
- ❌ 多会话并发操作同一批对象(派下一会话前先确认前一个已退出) - ❌ 多会话并发操作同一批对象(派下一会话前先确认前一个已退出)
## 🚫 规则 9read-table 的 `--where` 只能写 `IN` / `LIKE` ## 🚫 规则 9read-table 的 `--where` 运算符两侧必须留空格(推荐 `IN` / `LIKE`
`read-table` 走 ADT Data Preview freestyle 解析器,只接受很窄的 SQL 子集。**用 `=` 会返回 400,并以未捕获异常打印堆栈**(不是友好报错) `read-table` 走 ADT Data Preview freestyle 解析器。**运算符两侧无空格会 400**`f='x'``=` 会被黏在值上,报 `A Boolean expression was expected`);有空格的 `=` 实测可用,但多值过滤仍推荐 `IN`。400 现已转为友好报错并携带 SAP 原文,不再打堆栈
| 写法 | 实测结果 | | 写法 | 实测结果 |
|------|----------| |------|----------|
| `--where "TABNAME IN ('ZMM_BIP_001_HEADER')"` | ✅ 200 | | `--where "TABNAME IN ('ZMM_BIP_001_HEADER')"` | ✅ 200 |
| `--where "OBJ_NAME LIKE 'ZMM_BIP%'"` | ✅ 200 | | `--where "OBJ_NAME LIKE 'ZMM_BIP%'"` | ✅ 200 |
| `--where "TABNAME='ZMM_BIP_001_HEADER'"` | ❌ 400 `A Boolean expression was expected` | | `--where "TABNAME = 'ZMM_BIP_001_HEADER'"`**有空格** | ✅ 200(09-11 复测纠正旧结论) |
| `--where "TABNAME='ZMM_BIP_001_HEADER'"`(无空格) | ❌ 400 `A Boolean expression was expected` |
| `--fields "A,B"`(逗号分隔) | ✅ 200 | | `--fields "A,B"`(逗号分隔) | ✅ 200 |
| `--fields "A B"`(空格分隔) | ❌ 400 要求逗号分隔 | | `--fields "A B"`(空格分隔) | ❌ 400 要求逗号分隔 |
| SELECT/WHERE 用不存在的列 | ❌ 400 `未知的列名称 "X" until runtime`DD02T 无 `LANGUAGE`,语言列是 `DDLANGUAGE` |
- 要按多个值过滤,**一次 `IN` 查完**,不要循环单点查。 - 要按多个值过滤,**一次 `IN` 查完**,不要循环单点查。
- 更多方言细节与元数据表对照见 `error-handling.md`「ADT Data Preview SQL 方言」。 - 更多方言细节与元数据表对照见 `error-handling.md`「ADT Data Preview SQL 方言」。
@@ -148,4 +150,4 @@ python main.py read-table --name ZSAPILOT_OBJ --max-rows 50
7. ✅ 我按「本地改 → 同步 → 语法检查 → 循环至 0 错误 → 激活 → 测试」执行了吗? 7. ✅ 我按「本地改 → 同步 → 语法检查 → 循环至 0 错误 → 激活 → 测试」执行了吗?
8. ✅ 我核实过被调函数/结构的存在性与签名了吗(E04/E05)? 8. ✅ 我核实过被调函数/结构的存在性与签名了吗(E04/E05)?
9. ✅ 同一任务只有一个开发者会话在跑吗? 9. ✅ 同一任务只有一个开发者会话在跑吗?
10.`read-table``--where` 我用的是 `IN` / `LIKE` 而不是 `=` 吗?`--fields` 是逗号分隔吗? 10.`read-table``--where` 运算符两侧留空格了吗(`f = 'x'`,不是 `f='x'`)?多值过滤用 `IN`吗?`--fields` 是逗号分隔吗?
+15 -4
View File
@@ -3,6 +3,7 @@
from __future__ import annotations from __future__ import annotations
import logging import logging
import re
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from typing import Any from typing import Any
@@ -840,11 +841,21 @@ class DdicMixin:
resp = self.session.post(url, headers=hdrs, params=params, data=sql.encode("utf-8")) resp = self.session.post(url, headers=hdrs, params=params, data=sql.encode("utf-8"))
logger.info("QUERY TABLE DATA RESPONSE: HTTP %s", resp.status_code) logger.info("QUERY TABLE DATA RESPONSE: HTTP %s", resp.status_code)
if resp.status_code == 400: if resp.status_code == 400:
raise SapCliError( sap_msg = ""
"ADT Data Preview 拒绝该 SQLHTTP 400)。本系统 SQL 方言限制:" try:
"WHERE 只支持 IN(...) / LIKE '...',不支持 '='SELECT 列表必须逗号分隔。" m = re.search(r"<message[^>]*>(.*?)</message>", resp.text, re.S)
"请改用 IN / LIKE 重试。" if m:
sap_msg = m.group(1).strip()
except Exception:
pass
hint = (
"ADT Data Preview 拒绝该 SQLHTTP 400)。常见原因:"
"① WHERE 运算符两侧缺空格(写 f = 'x',不要 f='x';多值过滤推荐 IN);"
"② SELECT 列表未用逗号分隔;③ 列名不存在(如 DD02T 的语言列是 DDLANGUAGE)。"
) )
if sap_msg:
raise SapCliError(f"{hint}\n SAP 原文: {sap_msg}")
raise SapCliError(hint)
resp.raise_for_status() resp.raise_for_status()
root = ET.fromstring(resp.content) root = ET.fromstring(resp.content)
+8 -5
View File
@@ -120,21 +120,24 @@ NW 7.40 的 double-activate 已内置在 sap-cli 中。如果 activate 仍然失
| HTTP 423 (Transport lock) | 对象绑定在传输请求中 | 报告用户,SE09 处理 | | HTTP 423 (Transport lock) | 对象绑定在传输请求中 | 报告用户,SE09 处理 |
| 激活失败仍 inactive | NW 7.40 ADT 限制 | sap-cli 已内置 double-activate,仍失败则报告用户去 SE09 | | 激活失败仍 inactive | NW 7.40 ADT 限制 | sap-cli 已内置 double-activate,仍失败则报告用户去 SE09 |
| `WITH EMPTY KEY` 运行 dump | NW 7.40 不支持 | 修改为 `WITH NON-UNIQUE KEY``WITH DEFAULT KEY` | | `WITH EMPTY KEY` 运行 dump | NW 7.40 不支持 | 修改为 `WITH NON-UNIQUE KEY``WITH DEFAULT KEY` |
| HTTP 400 (Data Preview) `A Boolean expression was expected in <字段>=` | ADT Data Preview freestyle SQL **不支持 `=` 比较**(实测`WHERE f='x'`→400`WHERE f IN ('x')`200 | `IN (...)` `LIKE '...'`,不要用 `=` | | HTTP 400 (Data Preview) `A Boolean expression was expected in <字段>=` | **运算符两侧缺空格**`WHERE f='x'` 解析失败(`=` 被黏在值上);`WHERE f = 'x'`(有空格)实测 200 | 运算符两侧留空格,或一律`IN (...)` / `LIKE '...'` |
| HTTP 400 (Data Preview) `elements in the SELECT LIST list must be separated using commas` | 选择列必须逗号分隔(`SELECT a b` 报错) | 写成 `SELECT a, b FROM t` | | HTTP 400 (Data Preview) `elements in the SELECT LIST list must be separated using commas` | 选择列必须逗号分隔(`SELECT a b` 报错) | 写成 `SELECT a, b FROM t` |
| `info` 的「传输请求」恒为 `(无法获取)` | `_query_transport_request``=` + 单引号查 E071,在该方言下必然 400(异常被吞,故静默失败) | 改为 `WHERE object IN ('...') AND obj_name IN ('...')` | | HTTP 400 (Data Preview) `未知的列名称 "<COL>" until runtime` | SELECT/WHERE 用了**不存在的列名**(如 DD02T 没有 `LANGUAGE`/`MASTERLANG`,语言列是 `DDLANGUAGE`) | 先确认 DDIC 列名再查;报错已携带 SAP 原文 |
| `info` 的「传输请求」恒为 `(无法获取)` | (已修复)旧版 `_query_transport_request` 用无空格 `=` 查 E071 必 400 且异常被吞 | 已改 `IN (...)` 并区分「无」与「无法获取」 |
## ADT Data Preview SQL 方言(NW 7.40 实测,2026-09-11 ## ADT Data Preview SQL 方言(NW 7.40 实测,2026-09-11 二次修订
用于补 ZMM_BIP 系列等元数据时,本系统 `/sap/bc/adt/datapreview/freestyle` 的 SQL 解析器只接受很窄的子集 用于补 ZMM_BIP 系列等元数据时,本系统 `/sap/bc/adt/datapreview/freestyle` 的 SQL 解析器要求
| 能力 | 实测结果 | | 能力 | 实测结果 |
|------|----------| |------|----------|
| `SELECT f1, f2 FROM t`(逗号分隔) | ✅ 200 | | `SELECT f1, f2 FROM t`(逗号分隔) | ✅ 200 |
| `WHERE f = 'a'`(**运算符两侧有空格** | ✅ 200(2026-09-11 复测纠正:早前「不支持 =」的结论是探针写法无空格所致) |
| `WHERE f='a'`**无空格** | ❌ 400 `A Boolean expression was expected` |
| `WHERE f IN ('a','b')` | ✅ 200 | | `WHERE f IN ('a','b')` | ✅ 200 |
| `WHERE f LIKE 'ZMM%'` | ✅ 200 | | `WHERE f LIKE 'ZMM%'` | ✅ 200 |
| `WHERE f = 'a'` | ❌ 400 `A Boolean expression was expected` |
| `SELECT f1 f2`(无逗号) | ❌ 400 要求逗号分隔 | | `SELECT f1 f2`(无逗号) | ❌ 400 要求逗号分隔 |
| `SELECT <不存在的列>` | ❌ 400 `未知的列名称 "X" until runtime`SAP 原文会指明列名) |
| 无 WHERE 全表 | ✅ 200(但行数受行数参数与授权限制) | | 无 WHERE 全表 | ✅ 200(但行数受行数参数与授权限制) |
**可用的元数据来源(本机实测)** **可用的元数据来源(本机实测)**
+6 -4
View File
@@ -120,17 +120,19 @@ python main.py read-table --name ZSAPILOT_OBJ --max-rows 50
- ❌ 把「S4T 演示机通过」表述为「CEM/生产验证通过」 - ❌ 把「S4T 演示机通过」表述为「CEM/生产验证通过」
- ❌ 多会话并发操作同一批对象(派下一会话前先确认前一个已退出) - ❌ 多会话并发操作同一批对象(派下一会话前先确认前一个已退出)
## 🚫 规则 9read-table 的 `--where` 只能写 `IN` / `LIKE` ## 🚫 规则 9read-table 的 `--where` 运算符两侧必须留空格(推荐 `IN` / `LIKE`
`read-table` 走 ADT Data Preview freestyle 解析器,只接受很窄的 SQL 子集。**用 `=` 会返回 400,并以未捕获异常打印堆栈**(不是友好报错) `read-table` 走 ADT Data Preview freestyle 解析器。**运算符两侧无空格会 400**`f='x'``=` 会被黏在值上,报 `A Boolean expression was expected`);有空格的 `=` 实测可用,但多值过滤仍推荐 `IN`。400 现已转为友好报错并携带 SAP 原文,不再打堆栈
| 写法 | 实测结果 | | 写法 | 实测结果 |
|------|----------| |------|----------|
| `--where "TABNAME IN ('ZMM_BIP_001_HEADER')"` | ✅ 200 | | `--where "TABNAME IN ('ZMM_BIP_001_HEADER')"` | ✅ 200 |
| `--where "OBJ_NAME LIKE 'ZMM_BIP%'"` | ✅ 200 | | `--where "OBJ_NAME LIKE 'ZMM_BIP%'"` | ✅ 200 |
| `--where "TABNAME='ZMM_BIP_001_HEADER'"` | ❌ 400 `A Boolean expression was expected` | | `--where "TABNAME = 'ZMM_BIP_001_HEADER'"`**有空格** | ✅ 200(09-11 复测纠正旧结论) |
| `--where "TABNAME='ZMM_BIP_001_HEADER'"`(无空格) | ❌ 400 `A Boolean expression was expected` |
| `--fields "A,B"`(逗号分隔) | ✅ 200 | | `--fields "A,B"`(逗号分隔) | ✅ 200 |
| `--fields "A B"`(空格分隔) | ❌ 400 要求逗号分隔 | | `--fields "A B"`(空格分隔) | ❌ 400 要求逗号分隔 |
| SELECT/WHERE 用不存在的列 | ❌ 400 `未知的列名称 "X" until runtime`DD02T 无 `LANGUAGE`,语言列是 `DDLANGUAGE` |
- 要按多个值过滤,**一次 `IN` 查完**,不要循环单点查。 - 要按多个值过滤,**一次 `IN` 查完**,不要循环单点查。
- 更多方言细节与元数据表对照见 `error-handling.md`「ADT Data Preview SQL 方言」。 - 更多方言细节与元数据表对照见 `error-handling.md`「ADT Data Preview SQL 方言」。
@@ -148,4 +150,4 @@ python main.py read-table --name ZSAPILOT_OBJ --max-rows 50
7. ✅ 我按「本地改 → 同步 → 语法检查 → 循环至 0 错误 → 激活 → 测试」执行了吗? 7. ✅ 我按「本地改 → 同步 → 语法检查 → 循环至 0 错误 → 激活 → 测试」执行了吗?
8. ✅ 我核实过被调函数/结构的存在性与签名了吗(E04/E05)? 8. ✅ 我核实过被调函数/结构的存在性与签名了吗(E04/E05)?
9. ✅ 同一任务只有一个开发者会话在跑吗? 9. ✅ 同一任务只有一个开发者会话在跑吗?
10.`read-table``--where` 我用的是 `IN` / `LIKE` 而不是 `=` 吗?`--fields` 是逗号分隔吗? 10.`read-table``--where` 运算符两侧留空格了吗(`f = 'x'`,不是 `f='x'`)?多值过滤用 `IN`吗?`--fields` 是逗号分隔吗?
+12 -1
View File
@@ -712,7 +712,18 @@ class TestQueryTableData(unittest.TestCase):
with self.assertRaises(SapCliError) as ctx: with self.assertRaises(SapCliError) as ctx:
client.query_table_data("SELECT * FROM t WHERE f='x'") client.query_table_data("SELECT * FROM t WHERE f='x'")
self.assertIn("IN", str(ctx.exception)) self.assertIn("IN", str(ctx.exception))
self.assertIn("LIKE", str(ctx.exception)) self.assertIn("空格", str(ctx.exception))
def test_http_400_carries_sap_message(self):
"""400 报错必须携带 SAP 原文(如「未知的列名称」),不能只给笼统提示。"""
client = _make_client()
client.session.post.return_value = _mock_resp(
400, text='<exc:exception xmlns:exc="x"><message lang="EN">'
'未知的列名称 "LANGUAGE" until runtime</message></exc:exception>')
with self.assertRaises(SapCliError) as ctx:
client.query_table_data("SELECT LANGUAGE FROM dd02t")
self.assertIn("SAP 原文", str(ctx.exception))
self.assertIn("未知的列名称", str(ctx.exception))
# ═══════════════════════════════════════════ # ═══════════════════════════════════════════