chore: publish secure public mirror

This commit is contained in:
qiangayi
2026-08-14 15:35:50 +08:00
commit 37e018e0e0
20 changed files with 7008 additions and 0 deletions
+254
View File
@@ -0,0 +1,254 @@
# BOS 数据字典与系统地图
> 本文件由 SKILL.md 整理拆分而来,保留原说明内容。
## 数据字典(AD_TABLE / AD_COLUMN / AD_REFBYTABLE)
### 通用查询规则
处理 BOS 业务单据(如零售单、采购单、调拨单、销售单等)时,必须遵循以下查询路径:
**1. 术语 → 表名(AD_TABLE)**
遇到不懂的业务术语(如"零售单提交"、"采购入库"),先查 AD_TABLE:
```sql
SELECT ID,
NAME,
TABLENAME,
DESCRIPTION,
MASK,
PROC_SUBMIT,
HAS_TRIG_BD,
TRIG_BD,
HAS_TRIG_AC,
TRIG_AC,
HAS_TRIG_AM,
TRIG_AM
FROM AD_TABLE
WHERE NAME LIKE '%关键词%' OR DESCRIPTION LIKE '%关键词%'
```
- `NAME`:表名(英文,如 M_RETAIL)
- `DESCRIPTION`:业务说明(中文,如"零售单")
- `MASK`:表支持的业务动作代码,可由多个字母组成:`A`=新增、`M`=修改、`D`=删除、`Q`=查询、`U`=取消提交、`V`=作废、`S`=提交。分析表单功能、按钮或流程时,应以该字段实际包含的代码判断该表声明支持的动作,不要仅凭表名推断。
- `PROC_SUBMIT`:该单据的提交存储过程名称(非空即代表该表有提交逻辑)
- `HAS_TRIG_BD` / `TRIG_BD`:`HAS_TRIG_BD='Y'` 时,`TRIG_BD` 是删除前触发的存储过程,简称 `bd`
- `HAS_TRIG_AC` / `TRIG_AC`:`HAS_TRIG_AC='Y'` 时,`TRIG_AC` 是新增后触发的存储过程,简称 `ac`
- `HAS_TRIG_AM` / `TRIG_AM`:`HAS_TRIG_AM='Y'` 时,`TRIG_AM` 是修改后触发的存储过程,简称 `am`
分析 BOS 业务表时,`PROC_SUBMIT` 和这些 `TRIG_*` 字段都属于业务逻辑入口。不要只看物理触发器或过程源码;先从 `AD_TABLE` 判断表单事件过程是否配置,再按过程名继续查源码、依赖和影响表。
**2. 字段含义(AD_COLUMN)**
不理解某个字段的业务含义时,查 AD_COLUMN:
```sql
SELECT DBNAME, NAME, DESCRIPTION, COLTYPE
FROM AD_COLUMN
WHERE AD_TABLE_ID = <AD_TABLE.ID>
ORDER BY ORDERNO
```
**3. 主表 → 明细表(AD_REFBYTABLE)**
对于单据类表,其明细表、付款表等子表必须通过 AD_REFBYTABLE 查找,不要靠猜测命名规则:
```sql
SELECT t1.NAME AS MAIN_TABLE,
t2.NAME AS DETAIL_TABLE,
c.DBNAME AS REF_COLUMN,
r.ASSOCTYPE
FROM AD_REFBYTABLE r
LEFT JOIN AD_TABLE t1 ON r.AD_TABLE_ID = t1.ID
LEFT JOIN AD_TABLE t2 ON r.AD_REFBY_TABLE_ID = t2.ID
LEFT JOIN AD_COLUMN c ON r.AD_REFBY_COLUMN_ID = c.ID
WHERE t1.NAME = '<AD_TABLE.NAME>'
```
- `ASSOCTYPE`:"n"=一对多明细,"1"=强关联/嵌入
- 得到明细表名后,再通过 AD_COLUMN 查看各明细表的字段含义
### 核心表说明
| 表 | 说明 |
|------|------|
| `AD_TABLE` | 所有业务表的元数据注册表,每条记录对应一张业务表 |
| `AD_COLUMN` | 所有业务字段的元数据注册表,含字段名(DBNAME)、显示名(NAME)、描述(DESCRIPTION)、类型(COLTYPE)等 |
| `AD_REFBYTABLE` | **主表与明细表的关联关系**,记录主表→明细表的外键引用 |
### AD_REFBYTABLE 关联字段
| 字段 | 说明 |
|------|------|
| `AD_TABLE_ID` | 主表ID → 关联 AD_TABLE.ID |
| `AD_REFBY_TABLE_ID` | 明细表ID → 关联 AD_TABLE.ID |
| `AD_REFBY_COLUMN_ID` | 明细表中的外键列ID → 关联 AD_COLUMN.ID,通过 AD_COLUMN.DBNAME 获取实际列名 |
| `ASSOCTYPE` | 关联类型("1"=强关联/嵌入, "n"=一对多明细) |
### 查询主表→明细表关系的 SQL 模板
```sql
SELECT t1.NAME AS MAIN_TABLE,
t2.NAME AS DETAIL_TABLE,
c.DBNAME AS REF_COLUMN,
r.ASSOCTYPE
FROM AD_REFBYTABLE r
LEFT JOIN AD_TABLE t1 ON r.AD_TABLE_ID = t1.ID
LEFT JOIN AD_TABLE t2 ON r.AD_REFBY_TABLE_ID = t2.ID
LEFT JOIN AD_COLUMN c ON r.AD_REFBY_COLUMN_ID = c.ID
WHERE t1.NAME = 'M_RETAIL'
```
### 示例:M_RETAIL 的明细表
| 主表 | 明细表 | 外键列 | 关联类型 |
|------|--------|--------|----------|
| M_RETAIL | M_RETAILITEM | M_RETAIL_ID | n |
| M_RETAIL | M_RETAILPAYITEM | M_RETAIL_ID | n |
| M_RETAIL | M_RETAIL_PRO_ITEM | M_RETAIL_ID | n |
| M_RETAIL | M_RETAIL_RATEITEM | M_RETAIL_ID | n |
| M_RETAIL | M_RETAIL_DISITEM | M_RETAIL_ID | n |
| M_RETAIL | M_RETAIL_VOUCHERSITEM | M_RETAIL_ID | n |
| M_RETAIL | M_RETAIL_BATCHITEM | M_RETAIL_ID | n |
### 查询字段描述的 SQL 模板
```sql
SELECT DBNAME, NAME, DESCRIPTION, COLTYPE
FROM AD_COLUMN
WHERE AD_TABLE_ID = (SELECT ID FROM AD_TABLE WHERE NAME = 'M_RETAILITEM')
ORDER BY ORDERNO
```
### BOS 系统地图分析(AD_TABLE_TEXT 经验)
`BOSNDS3.AD_TABLE_TEXT` 是 BOS 内部的表配置导出脚本生成器。分析该过程可以反推出 BOS 元数据体系:菜单、模块、业务表、字段、主子表、按钮动作、提交过程、权限目录和报表模板主要由数据字典表驱动。
当用户要求分析“BOS 系统菜单 / 模块目录 / 表结构地图 / 某个业务模块结构”时,优先按下面的元数据链路整理,不要只看物理表:
```text
AD_SUBSYSTEM
-> AD_TABLECATEGORY
-> AD_ACCORDION
-> AD_TABLE
-> AD_COLUMN
-> AD_REFBYTABLE
-> AD_ACTION
-> DIRECTORY
-> AD_CXTAB / AD_CXTAB_JPARA / AD_CXTAB_DIMENSION / AD_CXTAB_FACT
```
核心含义:
- `AD_SUBSYSTEM`:子系统。
- `AD_TABLECATEGORY`:表类别 / 模块分类。
- `AD_ACCORDION`:折叠菜单 / 菜单分组。
- `AD_TABLE`:业务表注册表,含 `DESCRIPTION`、`MASK`(读写业务动作)、`PROC_SUBMIT`、`HAS_TRIG_BD/TRIG_BD`、`HAS_TRIG_AC/TRIG_AC`、`HAS_TRIG_AM/TRIG_AM`、`DIRECTORY_ID`、`AD_TABLECATEGORY_ID`、`AD_ACCORDION_ID` 等。
- `AD_COLUMN`:字段元数据,含字段名、显示名、业务描述、控件类型、引用列、默认值、限定值组等。
- `AD_REFBYTABLE`:主表到明细表、子表的关系。
- `AD_ACTION`:表动作、按钮、URL、存储过程脚本入口。
- `DIRECTORY`:数据权限目录挂载点。
- `AD_CXTAB` 相关表:公开查询 / 报表模板及其参数、维度、指标。
常用系统地图 SQL:
```sql
SELECT ss.NAME AS SUBSYSTEM,
tc.NAME AS TABLE_CATEGORY,
ac.NAME AS ACCORDION,
t.ID AS AD_TABLE_ID,
t.NAME AS TABLE_NAME,
t.DESCRIPTION,
t.MASK,
t.PROC_SUBMIT,
t.HAS_TRIG_BD,
t.TRIG_BD,
t.HAS_TRIG_AC,
t.TRIG_AC,
t.HAS_TRIG_AM,
t.TRIG_AM,
d.NAME AS DIRECTORY_NAME
FROM AD_TABLE t
LEFT JOIN AD_TABLECATEGORY tc ON tc.ID = t.AD_TABLECATEGORY_ID
LEFT JOIN AD_SUBSYSTEM ss ON ss.ID = tc.AD_SUBSYSTEM_ID
LEFT JOIN AD_ACCORDION ac ON ac.ID = t.AD_ACCORDION_ID
LEFT JOIN DIRECTORY d ON d.ID = t.DIRECTORY_ID
WHERE t.NAME LIKE '%关键词%' OR t.DESCRIPTION LIKE '%关键词%'
ORDER BY ss.NAME, tc.ORDERNO, ac.ORDERNO, t.ORDERNO, t.NAME
```
分析单个业务表时,至少同时检查:
- `AD_TABLE`:表归属、提交过程、表单事件过程(bd/ac/am)、权限目录。
- `AD_COLUMN`:字段业务含义和值域。
- `AD_REFBYTABLE`:明细表和外键列。
- `AD_ACTION`:按钮和动作脚本。
- `ALL_TRIGGERS` / `USER_OBJECTS`:触发器、同名前缀过程或函数。
注意:这套元数据能还原 BOS 业务结构,不等于完整物理数据库模型。复杂过程内部逻辑、动态 SQL、触发器副作用和前端特殊页面仍需要结合源码继续分析。
## 数据权限系统
### GET_USERSPERMSQL 函数
数据库内置 `get_userspermsql` 函数,用于获取用户的数据权限过滤条件。
**函数签名:**
```sql
FUNCTION get_userspermsql(
p_users_id NUMBER, -- 用户ID (USERS.ID)
p_tableid NUMBER, -- 表ID (AD_TABLE.ID)
p_col VARCHAR2 -- 字段名 (AD_COLUMN.DBNAME),可为NULL
) RETURN CLOB -- 返回权限过滤SQL
```
**返回值:**
- 空字符串:全权限(无需过滤)
- SQL条件:如 `C_STORE_ID IN (...)`,需拼接到 WHERE 子句
**权限模型:**
```
用户 → GROUPUSER → 用户组 → GROUPPERM → 安全目录 → 权限SQL
↓
AD_TABLE.DIRECTORY_ID
```
### 使用权限过滤查询
**1. 查询用户权限:**
```bash
python oracle_skill.py perm 940 12983
```
返回:
```json
{
"user_id": "940",
"table_id": "12983",
"perm_sql": "M_OTHER_INOUT.C_STORE_ID IN(...)",
"has_restriction": true
}
```
**2. 带权限过滤的查询:**
```bash
python oracle_skill.py qperm 940 12983 "SELECT * FROM M_OTHER_INOUT WHERE BILLDATE=20260501"
```
自动拼接权限条件后执行:
```sql
SELECT * FROM M_OTHER_INOUT
WHERE BILLDATE=20260501
AND M_OTHER_INOUT.C_STORE_ID IN(...)
```
### 常用表ID参考
| 表名 | AD_TABLE.ID | 说明 |
|------|-------------|------|
| M_RETAIL | 12964 | 零售单主表 |
| M_RETAILITEM | 12965 | 零售明细 |
| M_OTHER_INOUT | 12983 | 其他出入库 |
| C_STORE_CHKDAY | 18182 | 门店盘点日 |
+175
View File
@@ -0,0 +1,175 @@
# 命令、登录与调用方式
> 本文件由 SKILL.md 整理拆分而来,保留原说明内容。
## 概述
通过中转服务(Transit Server)查询远程 Oracle 数据库服务器的存储过程元数据,无需开放数据库端口。
## 架构
```
AI Skill → HTTP → Transit Server (:6357) → WebSocket → Agent → Oracle
```
## 前置条件
1. 中转服务已部署并运行(默认地址: https://ts.henlo.net)
2. Agent 已部署到数据库服务器并连接中转服务
3. 本地已安装 Python 依赖:`pip install requests`
## 配置
配置文件位于技能目录下:`D:\work\恒诺\长期支持\跳板查询\demo\ai-skill\config.json`
```json
{
"transit_url": "https://ts.henlo.net",
"server_id": "server-001",
"access_token": "",
"expires_at": ""
}
```
使用查询命令前先登录中转机:
```bash
python oracle_skill.py login <secretKey> [clientCode]
```
登录成功后只保存中转机签发的 `access_token` 和过期时间,不保存 BOS `secretKey`。`clientCode` 是客户服务器编号,非必填;不传时中转机会默认选择 BOS 返回的第一个可用 client。
同一个 `secretKey` 同时只允许一个设备在线。另一台设备重新登录后,当前设备的 token 会立即失效,需要重新执行 `login <secretKey> [clientCode]`。
## 调用方式
使用 `scripts/oracle_skill.py` 脚本:
```bash
python scripts/oracle_skill.py <command> [args]
```
### 可用命令
| 命令 | 说明 | 示例 |
|------|------|------|
| `analyze <schema> <proc>` | 完整分析存储过程 | `python oracle_skill.py analyze BOS M_RETAIL_SUBMIT` |
| `list <schema>` | 列出 schema 下所有存储过程 | `python oracle_skill.py list BOS` |
| `source <schema> <proc>` | 获取存储过程源码 | `python oracle_skill.py source BOS M_RETAIL_SUBMIT` |
| `deps <schema> <proc>` | 获取依赖(表/存储过程) | `python oracle_skill.py deps BOS M_RETAIL_SUBMIT` |
| `tables <schema> <proc>` | 获取相关表结构 | `python oracle_skill.py tables BOS M_RETAIL_SUBMIT` |
| `describe <schema> <table>` | 查询表结构(列、索引、行数) | `python oracle_skill.py describe bosnds3 xcx_so` |
| `discover [schema] [domain]` | 发现核心业务域(NL2SQL前置) | `python oracle_skill.py discover BOSNDS3 RETAIL` |
| `nl2sql [schema] [domain]` | 生成 NL2SQL Schema 字典 | `python oracle_skill.py nl2sql BOSNDS3 RETAIL` |
| `query <SQL>` | 执行 SELECT 查询,生产表必须带过滤条件和行数限制 | `python oracle_skill.py query "SELECT * FROM M_RETAIL WHERE BILLDATE=20260501 AND ROWNUM<=20"` |
| `perm <userId> <tableId> [col]` | 查询用户数据权限 | `python oracle_skill.py perm 940 12983` |
| `qperm <userId> <tableId> <SQL>` | 带权限过滤的查询 | `python oracle_skill.py qperm 940 12983 "SELECT * FROM M_OTHER_INOUT"` |
| `login <secretKey> [clientCode]` | 登录中转机 + 选择客户服务器,clientCode 非必填 | `python oracle_skill.py login mykey HENLO` |
| `logout` | 登出中转机并清除本地 token | `python oracle_skill.py logout` |
| `status` | 查看中转机登录状态 + 当前 client | `python oracle_skill.py status` |
| `switch <clientCode|clientName>` | 按 code 或名称切换 client;找不到时自动刷新 client 列表 | `python oracle_skill.py switch HENLO` |
| `clients` | 无感刷新当前用户最新授权的 client 列表及在线状态;只使用当前登录 token | `python oracle_skill.py clients` |
| `agent_update [clientCode] [timeout]` | 触发客户 Agent 自动更新,升级包由 Agent 从 OSS 下载 | `python oracle_skill.py agent_update HENLO 300` |
| `awr_status [clientCode]` | 查看 AWR 授权和可用状态,不生成报告 | `python oracle_skill.py awr_status WEIRUI` |
| `awr_list [clientCode]` | 列出 Agent 已生成的 AWR 报告 | `python oracle_skill.py awr_list WEIRUI` |
| `awr_download <clientCode> <yyyyMMdd>` | 下载 Agent 原始 AWR HTML | `python oracle_skill.py awr_download WEIRUI 20260720` |
| `servers` | 列出在线 Agent | `python oracle_skill.py servers` |
| `health` | 检查中转服务状态 | `python oracle_skill.py health` |
| `tablespace / tablespaces` | 查询表空间使用情况 | `python oracle_skill.py tablespace` |
### 交互模式
不传参数直接运行,进入交互模式:
```bash
python scripts/oracle_skill.py
```
## 分析报告内容
`analyze` 命令返回完整分析报告,包含:
- **源码**:完整 PL/SQL 代码
- **依赖**:涉及的表、视图、嵌套存储过程/函数
- **表结构**:相关表的列信息(列名、类型、长度、可空)
- **触发器**:相关表上的触发器列表
## 直接 HTTP 调用
不通过脚本,直接调用中转服务 API:
### 分析存储过程
```bash
curl -X POST https://ts.henlo.net/api/query \
-H "Content-Type: application/json" \
-d '{
"server_id": "server-001",
"action": "analyze_procedure",
"schema": "BOS",
"name": "M_RETAIL_SUBMIT",
"timeout": 60
}'
```
### 查询表结构
```bash
curl -X POST https://ts.henlo.net/api/query \
-H "Content-Type: application/json" \
-d '{
"server_id": "server-001",
"action": "describe_table",
"schema": "bosnds3",
"name": "xcx_so",
"timeout": 30
}'
```
### action 类型
| action | 说明 |
|--------|------|
| `analyze_procedure` | 完整分析 |
| `list_procedures` | 列出存储过程 |
| `get_source` | 源码 |
| `get_dependencies` | 依赖 |
| `get_tables` | 表结构(通过存储过程依赖) |
| `describe_table` | 查询表结构(列、索引、行数、注释) |
| `execute_query` | 执行 SELECT 查询(只读)—— 表空间查询也复用此 action |
| `schema_discovery` | 发现核心业务域 |
| `generate_nl2sql_schema` | 生成 NL2SQL Schema 字典 |
## 故障排查
| 问题 | 排查 |
|------|------|
| `无法连接中转服务` | 检查 transit_url 地址和端口 |
| `agent not found` | 确认 Agent 已启动并连接成功 |
| `请求超时` | Agent 可能卡住,增大 timeout 参数 |
| Oracle 连接失败 | 检查 Agent 的 config.json 中 Oracle 配置 |
## Agent 版本查询
自然语言可说:查询所有客户的 Agent 版本号,或查询指定客户版本号。
```powershell
python scripts/oracle_skill.py version agent HENLO RENBEN --json
python scripts/oracle_skill.py version agent --all --json
```
## Skill Git 更新流程
Skill 使用 Gitea Git 仓库作为唯一更新来源,不使用 OSS 或 `skill_update` 命令。源码仓库完成修改、提交和推送后,在已安装目录执行:
```powershell
cd C:\Users\qiang\.codex\skills\oracle-jump-query
git pull --ff-only origin main
python scripts/oracle_skill.py capabilities --json
```
在线 Agent 返回当前版本;离线客户返回最后一次心跳版本和离线状态。
+692
View File
@@ -0,0 +1,692 @@
---
name: oracle-jump-query
description: Oracle 跳板查询技能。通过中转服务查询远程 Oracle 数据库的元数据(存储过程源码、依赖、表结构、触发器、直接查表),连接数据库为只读,禁止任何写操作。当用户需要分析 Oracle 存储过程、查找依赖表、查询表结构、查看业务逻辑时触发。
---
# Oracle 跳板查询
> **版本:v1.5.8** · [更新日志](./CHANGELOG.md)
## 概述
通过中转服务(Transit Server)查询远程 Oracle 数据库服务器的存储过程元数据,无需开放数据库端口。
## 架构
```
AI Skill → HTTP → Transit Server (:6357) → WebSocket → Agent → Oracle
```
## 前置条件
1. 中转服务已部署并运行(IP: 47.97.171.89, Port: 6357)
2. Agent 已部署到数据库服务器并连接中转服务
3. 本地已安装 Python 依赖:`pip install requests`
## 配置
配置文件位于技能目录下:`D:\work\恒诺\长期支持\跳板查询\demo\ai-skill\config.json`
```json
{
"transit_url": "http://47.97.171.89:6357",
"server_id": "server-001",
"access_token": "",
"expires_at": ""
}
```
使用查询命令前先登录中转机:
```bash
python oracle_skill.py login <secretKey> [clientCode]
```
登录成功后只保存中转机签发的 `access_token` 和过期时间,不保存 BOS `secretKey`。`clientCode` 是客户服务器编号,非必填;不传时中转机会默认选择 BOS 返回的第一个可用 client。
同一个 `secretKey` 同时只允许一个设备在线。另一台设备重新登录后,当前设备的 token 会立即失效,需要重新执行 `login <secretKey> [clientCode]`。
## 调用方式
使用 `scripts/oracle_skill.py` 脚本:
```bash
python scripts/oracle_skill.py <command> [args]
```
### 可用命令
| 命令 | 说明 | 示例 |
|------|------|------|
| `analyze <schema> <proc>` | 完整分析存储过程 | `python oracle_skill.py analyze BOS M_RETAIL_SUBMIT` |
| `list <schema>` | 列出 schema 下所有存储过程 | `python oracle_skill.py list BOS` |
| `source <schema> <proc>` | 获取存储过程源码 | `python oracle_skill.py source BOS M_RETAIL_SUBMIT` |
| `deps <schema> <proc>` | 获取依赖(表/存储过程) | `python oracle_skill.py deps BOS M_RETAIL_SUBMIT` |
| `tables <schema> <proc>` | 获取相关表结构 | `python oracle_skill.py tables BOS M_RETAIL_SUBMIT` |
| `describe <schema> <table>` | 查询表结构(列、索引、行数) | `python oracle_skill.py describe bosnds3 xcx_so` |
| `discover [schema] [domain]` | 发现核心业务域(NL2SQL前置) | `python oracle_skill.py discover BOSNDS3 RETAIL` |
| `nl2sql [schema] [domain]` | 生成 NL2SQL Schema 字典 | `python oracle_skill.py nl2sql BOSNDS3 RETAIL` |
| `query <SQL>` | 执行 SELECT 查询,生产表必须带过滤条件和行数限制 | `python oracle_skill.py query "SELECT * FROM M_RETAIL WHERE BILLDATE=20260501 AND ROWNUM<=20"` |
| `perm <userId> <tableId> [col]` | 查询用户数据权限 | `python oracle_skill.py perm 940 12983` |
| `qperm <userId> <tableId> <SQL>` | 带权限过滤的查询 | `python oracle_skill.py qperm 940 12983 "SELECT * FROM M_OTHER_INOUT"` |
| `login <secretKey> [clientCode]` | 登录中转机 + 选择客户服务器,clientCode 非必填 | `python oracle_skill.py login mykey HENLO` |
| `logout` | 登出中转机并清除本地 token | `python oracle_skill.py logout` |
| `status` | 查看中转机登录状态 + 当前 client | `python oracle_skill.py status` |
| `switch <clientCode>` | 切换 client | `python oracle_skill.py switch OTHER_CLIENT` |
| `clients` | 获取当前用户可访问 client 及在线状态 | `python oracle_skill.py clients` |
| `servers` | 列出在线 Agent | `python oracle_skill.py servers` |
| `health` | 检查中转服务状态 | `python oracle_skill.py health` |
| `tablespace / tablespaces` | 查询表空间使用情况 | `python oracle_skill.py tablespace` |
### 交互模式
不传参数直接运行,进入交互模式:
```bash
python scripts/oracle_skill.py
```
## 生产查询限制
直接查询业务表时,必须带明确过滤条件,不要直接查询整表。生产环境数据量很大,`SELECT * FROM <table>` 这类无条件查询可能返回大量数据、拖慢 Agent 或影响数据库。
生成或执行 `query` / `qperm` SQL 时遵守:
- 必须有 `WHERE` 条件,优先使用日期、单号、门店、客户、状态、主键等业务过滤条件。
- 必须加行数限制,例如 `ROWNUM <= 20`;需要分页时分批查询。
- 不要为了“先看看数据”执行无条件整表查询。
- 统计查询也要限定业务范围,例如日期区间、单据状态、门店范围。
- 如果用户没有给出过滤条件,先追问条件,或先用 `describe` / `AD_COLUMN` / `AD_TABLE` 查询结构和字段含义。
推荐示例:
```sql
SELECT *
FROM M_RETAIL
WHERE BILLDATE = 20260501
AND STATUS = '2'
AND ROWNUM <= 20
```
避免示例:
```sql
SELECT * FROM M_RETAIL
```
## 常用业务对象说明
当用户使用商品、款号、条码、SKU、库存等业务词时,默认按以下对象理解:
- “商品”“款号”通常指 `M_PRODUCT` 表。
- “条码”“SKU”通常指 `M_PRODUCT_ALIAS` 表。
- 查询条码/SKU 时,首查 `M_PRODUCT_ALIAS.NO` 字段。
- 款号和条码是一对多关系:一个 `M_PRODUCT` 可以对应多条 `M_PRODUCT_ALIAS`。
- 如果涉及库存查询,通常查询 `V_FA_STORAGE` 视图。
- `V_FA_STORAGE` 数据量很大,必须同时带店仓条件和款号/条码条件,不允许只查整张库存视图。
库存查询过滤规则:
- 店仓条件应优先使用门店、仓库、店仓 ID 或店仓编码等字段,先通过 `describe` 或 `AD_COLUMN` 确认实际字段名。
- 款号条件走 `M_PRODUCT`;条码/SKU 条件走 `M_PRODUCT_ALIAS`。
- 通过条码查库存时,应先用 `M_PRODUCT_ALIAS.NO` 找到对应商品,再关联或过滤 `V_FA_STORAGE`。
- 必须加 `ROWNUM` 或分页限制;如果用户没有提供店仓或款号/条码,先追问,不要直接查询库存视图。
示例思路:
```sql
-- 伪示例:实际字段名需先 describe / AD_COLUMN 确认
SELECT *
FROM V_FA_STORAGE s
WHERE s.C_STORE_ID = :store_id
AND s.M_PRODUCT_ID = :product_id
AND ROWNUM <= 20
```
## 分析报告内容
`analyze` 命令返回完整分析报告,包含:
- **源码**:完整 PL/SQL 代码
- **依赖**:涉及的表、视图、嵌套存储过程/函数
- **表结构**:相关表的列信息(列名、类型、长度、可空)
- **触发器**:相关表上的触发器列表
## 直接 HTTP 调用
不通过脚本,直接调用中转服务 API:
### 分析存储过程
```bash
curl -X POST http://47.97.171.89:6357/api/query \
-H "Content-Type: application/json" \
-d '{
"server_id": "server-001",
"action": "analyze_procedure",
"schema": "BOS",
"name": "M_RETAIL_SUBMIT",
"timeout": 60
}'
```
### 查询表结构
```bash
curl -X POST http://47.97.171.89:6357/api/query \
-H "Content-Type: application/json" \
-d '{
"server_id": "server-001",
"action": "describe_table",
"schema": "bosnds3",
"name": "xcx_so",
"timeout": 30
}'
```
### action 类型
| action | 说明 |
|--------|------|
| `analyze_procedure` | 完整分析 |
| `list_procedures` | 列出存储过程 |
| `get_source` | 源码 |
| `get_dependencies` | 依赖 |
| `get_tables` | 表结构(通过存储过程依赖) |
| `describe_table` | 查询表结构(列、索引、行数、注释) |
| `execute_query` | 执行 SELECT 查询(只读)—— 表空间查询也复用此 action |
| `schema_discovery` | 发现核心业务域 |
| `generate_nl2sql_schema` | 生成 NL2SQL Schema 字典 |
## 数据字典(AD_TABLE / AD_COLUMN / AD_REFBYTABLE)
### 通用查询规则
处理 BOS 业务单据(如零售单、采购单、调拨单、销售单等)时,必须遵循以下查询路径:
**1. 术语 → 表名(AD_TABLE)**
遇到不懂的业务术语(如"零售单提交"、"采购入库"),先查 AD_TABLE:
```sql
SELECT ID, NAME, TABLENAME, DESCRIPTION, PROC_SUBMIT
FROM AD_TABLE
WHERE NAME LIKE '%关键词%' OR DESCRIPTION LIKE '%关键词%'
```
- `NAME`:表名(英文,如 M_RETAIL)
- `DESCRIPTION`:业务说明(中文,如"零售单")
- `PROC_SUBMIT`:该单据的提交存储过程名称(非空即代表该表有提交逻辑)
**2. 字段含义(AD_COLUMN)**
不理解某个字段的业务含义时,查 AD_COLUMN:
```sql
SELECT DBNAME, NAME, DESCRIPTION, COLTYPE
FROM AD_COLUMN
WHERE AD_TABLE_ID = <AD_TABLE.ID>
ORDER BY ORDERNO
```
**3. 主表 → 明细表(AD_REFBYTABLE)**
对于单据类表,其明细表、付款表等子表必须通过 AD_REFBYTABLE 查找,不要靠猜测命名规则:
```sql
SELECT t1.NAME AS MAIN_TABLE,
t2.NAME AS DETAIL_TABLE,
c.DBNAME AS REF_COLUMN,
r.ASSOCTYPE
FROM AD_REFBYTABLE r
LEFT JOIN AD_TABLE t1 ON r.AD_TABLE_ID = t1.ID
LEFT JOIN AD_TABLE t2 ON r.AD_REFBY_TABLE_ID = t2.ID
LEFT JOIN AD_COLUMN c ON r.AD_REFBY_COLUMN_ID = c.ID
WHERE t1.NAME = '<AD_TABLE.NAME>'
```
- `ASSOCTYPE`:"n"=一对多明细,"1"=强关联/嵌入
- 得到明细表名后,再通过 AD_COLUMN 查看各明细表的字段含义
### 核心表说明
| 表 | 说明 |
|------|------|
| `AD_TABLE` | 所有业务表的元数据注册表,每条记录对应一张业务表 |
| `AD_COLUMN` | 所有业务字段的元数据注册表,含字段名(DBNAME)、显示名(NAME)、描述(DESCRIPTION)、类型(COLTYPE)等 |
| `AD_REFBYTABLE` | **主表与明细表的关联关系**,记录主表→明细表的外键引用 |
### AD_REFBYTABLE 关联字段
| 字段 | 说明 |
|------|------|
| `AD_TABLE_ID` | 主表ID → 关联 AD_TABLE.ID |
| `AD_REFBY_TABLE_ID` | 明细表ID → 关联 AD_TABLE.ID |
| `AD_REFBY_COLUMN_ID` | 明细表中的外键列ID → 关联 AD_COLUMN.ID,通过 AD_COLUMN.DBNAME 获取实际列名 |
| `ASSOCTYPE` | 关联类型("1"=强关联/嵌入, "n"=一对多明细) |
### 查询主表→明细表关系的 SQL 模板
```sql
SELECT t1.NAME AS MAIN_TABLE,
t2.NAME AS DETAIL_TABLE,
c.DBNAME AS REF_COLUMN,
r.ASSOCTYPE
FROM AD_REFBYTABLE r
LEFT JOIN AD_TABLE t1 ON r.AD_TABLE_ID = t1.ID
LEFT JOIN AD_TABLE t2 ON r.AD_REFBY_TABLE_ID = t2.ID
LEFT JOIN AD_COLUMN c ON r.AD_REFBY_COLUMN_ID = c.ID
WHERE t1.NAME = 'M_RETAIL'
```
### 示例:M_RETAIL 的明细表
| 主表 | 明细表 | 外键列 | 关联类型 |
|------|--------|--------|----------|
| M_RETAIL | M_RETAILITEM | M_RETAIL_ID | n |
| M_RETAIL | M_RETAILPAYITEM | M_RETAIL_ID | n |
| M_RETAIL | M_RETAIL_PRO_ITEM | M_RETAIL_ID | n |
| M_RETAIL | M_RETAIL_RATEITEM | M_RETAIL_ID | n |
| M_RETAIL | M_RETAIL_DISITEM | M_RETAIL_ID | n |
| M_RETAIL | M_RETAIL_VOUCHERSITEM | M_RETAIL_ID | n |
| M_RETAIL | M_RETAIL_BATCHITEM | M_RETAIL_ID | n |
### 查询字段描述的 SQL 模板
```sql
SELECT DBNAME, NAME, DESCRIPTION, COLTYPE
FROM AD_COLUMN
WHERE AD_TABLE_ID = (SELECT ID FROM AD_TABLE WHERE NAME = 'M_RETAILITEM')
ORDER BY ORDERNO
```
### BOS 系统地图分析(AD_TABLE_TEXT 经验)
`BOSNDS3.AD_TABLE_TEXT` 是 BOS 内部的表配置导出脚本生成器。分析该过程可以反推出 BOS 元数据体系:菜单、模块、业务表、字段、主子表、按钮动作、提交过程、权限目录和报表模板主要由数据字典表驱动。
当用户要求分析“BOS 系统菜单 / 模块目录 / 表结构地图 / 某个业务模块结构”时,优先按下面的元数据链路整理,不要只看物理表:
```text
AD_SUBSYSTEM
-> AD_TABLECATEGORY
-> AD_ACCORDION
-> AD_TABLE
-> AD_COLUMN
-> AD_REFBYTABLE
-> AD_ACTION
-> DIRECTORY
-> AD_CXTAB / AD_CXTAB_JPARA / AD_CXTAB_DIMENSION / AD_CXTAB_FACT
```
核心含义:
- `AD_SUBSYSTEM`:子系统。
- `AD_TABLECATEGORY`:表类别 / 模块分类。
- `AD_ACCORDION`:折叠菜单 / 菜单分组。
- `AD_TABLE`:业务表注册表,含 `DESCRIPTION`、`PROC_SUBMIT`、`DIRECTORY_ID`、`AD_TABLECATEGORY_ID`、`AD_ACCORDION_ID` 等。
- `AD_COLUMN`:字段元数据,含字段名、显示名、业务描述、控件类型、引用列、默认值、限定值组等。
- `AD_REFBYTABLE`:主表到明细表、子表的关系。
- `AD_ACTION`:表动作、按钮、URL、存储过程脚本入口。
- `DIRECTORY`:数据权限目录挂载点。
- `AD_CXTAB` 相关表:公开查询 / 报表模板及其参数、维度、指标。
常用系统地图 SQL:
```sql
SELECT ss.NAME AS SUBSYSTEM,
tc.NAME AS TABLE_CATEGORY,
ac.NAME AS ACCORDION,
t.ID AS AD_TABLE_ID,
t.NAME AS TABLE_NAME,
t.DESCRIPTION,
t.PROC_SUBMIT,
d.NAME AS DIRECTORY_NAME
FROM AD_TABLE t
LEFT JOIN AD_TABLECATEGORY tc ON tc.ID = t.AD_TABLECATEGORY_ID
LEFT JOIN AD_SUBSYSTEM ss ON ss.ID = tc.AD_SUBSYSTEM_ID
LEFT JOIN AD_ACCORDION ac ON ac.ID = t.AD_ACCORDION_ID
LEFT JOIN DIRECTORY d ON d.ID = t.DIRECTORY_ID
WHERE t.NAME LIKE '%关键词%' OR t.DESCRIPTION LIKE '%关键词%'
ORDER BY ss.NAME, tc.ORDERNO, ac.ORDERNO, t.ORDERNO, t.NAME
```
分析单个业务表时,至少同时检查:
- `AD_TABLE`:表归属、提交过程、权限目录。
- `AD_COLUMN`:字段业务含义和值域。
- `AD_REFBYTABLE`:明细表和外键列。
- `AD_ACTION`:按钮和动作脚本。
- `ALL_TRIGGERS` / `USER_OBJECTS`:触发器、同名前缀过程或函数。
注意:这套元数据能还原 BOS 业务结构,不等于完整物理数据库模型。复杂过程内部逻辑、动态 SQL、触发器副作用和前端特殊页面仍需要结合源码继续分析。
## 表空间查询
查询 Oracle 表空间使用情况。
### 自动降级策略
脚本按优先级尝试**5 种方式**,无需人工切换:
| 优先级 | 方式 | 数据来源 | 条件 | 输出字段 |
|:---:|------|----------|------|----------|
| 1️⃣ | 直接 DBA 查询 | `DBA_DATA_FILES` + `DBA_FREE_SPACE` | 用户有 `SELECT_CATALOG_ROLE` | 全部 8 个字段 |
| 2️⃣ | DBA 专用函数 | `sys.get_tablespace_usage()`(返回 SYS_REFCURSOR) | DBA 已执行 CREATE FUNCTION + GRANT | 全部 5 个字段(自动解析) |
| 3️⃣ | DBA 授权 VIEW(无前缀) | `HENLO_TS_USAGE` 视图 | DBA 已执行授权 SQL | 全部 8 个字段 |
| 4️⃣ | DBA 授权 VIEW(sys.前缀) | `SYS.HENLO_TS_USAGE` 视图 | DBA 以 SYS 身份创建时自动处理 | 全部 8 个字段 |
| 5️⃣ | 用户视图 | `USER_FREE_SPACE` | 始终可用 | 仅表空间名称 + 剩余MB |
> **经验**:DBA 以 SYS 身份登录创建的 VIEW 会归属到 `SYS` schema,需用 `sys.HENLO_TS_USAGE` 查询。脚本会自动先试无前缀、再试 `sys.` 前缀。
### 给 DBA 的授权 SQL
如果当前用户缺少系统视图权限,脚本会**自动输出**以下 SQL。将其交给 DBA 在 Oracle 中执行一次:
```sql
CREATE OR REPLACE VIEW SYS.HENLO_TS_USAGE AS
SELECT
df.tablespace_name AS "表空间名称",
ROUND(df.total_bytes / 1048576, 1) AS "总大小MB",
ROUND((df.total_bytes - NVL(fs.free_bytes, 0)) / 1048576, 1) AS "已用MB",
ROUND(NVL(fs.free_bytes, 0) / 1048576, 1) AS "剩余MB",
ROUND((df.total_bytes - NVL(fs.free_bytes, 0)) / df.total_bytes * 100, 1) AS "使用率%",
df.autoextensible AS "自动拓展",
ROUND(df.max_bytes / 1048576, 1) AS "最大可拓展MB",
ROUND((df.total_bytes - NVL(fs.free_bytes, 0)) / NULLIF(df.max_bytes, 0) * 100, 1) AS "最大使用率%"
FROM (
SELECT
tablespace_name,
SUM(bytes) AS total_bytes,
SUM(CASE WHEN autoextensible = 'YES' THEN maxbytes ELSE bytes END) AS max_bytes,
MAX(CASE WHEN autoextensible = 'YES' THEN 'YES' ELSE 'NO' END) AS autoextensible
FROM dba_data_files
GROUP BY tablespace_name
) df
LEFT JOIN (
SELECT tablespace_name, SUM(bytes) AS free_bytes
FROM dba_free_space
GROUP BY tablespace_name
) fs ON df.tablespace_name = fs.tablespace_name
ORDER BY 6 DESC;
GRANT SELECT ON SYS.HENLO_TS_USAGE TO bosnds3;
```
> **原理**:Oracle 视图默认使用**定义者权限**(`DEFINER`),以视图创建者的 DBA 身份执行,普通用户仅需视图的 `SELECT` 权限。这比授予 `SELECT_CATALOG_ROLE` 更安全——只暴露这一个视图的数据。
DBA 执行授权后,再次运行 `tablespace` 命令即可自动使用完整查询。
### 输出字段
| 字段 | 说明 |
|------|------|
| 表空间名称 | 表空间名称 |
| 总大小MB | 当前所有数据文件总大小 |
| 已用MB | 已使用的空间 |
| 剩余MB | 剩余可用空间 |
| 使用率% | 已用 / 总大小 × 100% |
| 自动拓展 | YES=自动扩展,NO=不自动扩展 |
| 最大可拓展MB | 开启自动扩展后的最大可达大小 |
| 最大使用率% | 已用 / 最大可拓展 × 100%(参考阈值 5%) |
### 触发语法
用户可以说:
- 「帮我查询下表空间」
- 「查看表空间使用情况」
- 「表空间是否够用」
```bash
python oracle_skill.py tablespace
# 或
python oracle_skill.py tablespaces
```
### 阈值规则
| 使用率 | 状态 | 建议 |
|--------|------|------|
| < 80% | ✅ 正常 | 无需处理 |
| 80~95% | 🔶 关注 | 评估未来增长,制定扩容计划 |
| ≥ 95% | ⚠️ 紧急 | 尽快扩容或清理空间! |
> **智能告警规则**:脚本逐行解析表空间数据,自动判断衡量指标。
> 若表空间**已开启自动扩展**(`自动拓展=YES`),按 **最大使用率%** 判断(已用/最大可拓展);
> 若**未开启**(`NO`),按当前 **使用率%** 判断(已用/当前总大小)。
>
> 这让告警更准确。例如:SYSTEM 使用率 99.4% 但最大使用率仅 3.7%,说明只是当前数据文件写满了,自动扩展后即释放空间,并非真正的存储瓶颈。
### 使用场景
1. **查主表的明细表**:通过 AD_REFBYTABLE 找到主表关联的所有明细表及外键列
2. **查字段含义**:通过 AD_COLUMN 的 DESCRIPTION 字段了解业务含义
3. **构建 JOIN 查询**:根据外键列名自动拼接主表→明细表的 JOIN 条件
4. **理解业务模型**:通过表间关系理解业务单据结构(如零售单→零售明细→支付明细)
### 4. 统计数据场景的过滤条件
在 BOS 系统中,进行统计数据查询时,必须遵循以下业务规则:
**业务单据通用过滤条件**:
- `ISACTIVE = 'Y'`:单据有效(Y=可用,N=已删除/作废)
- `STATUS = '2'`:单据已提交(1=未提交,2=已提交,3=审批中)
**查询模板**:
```sql
-- 查询有效且已提交的业务单据
SELECT * FROM <业务表名>
WHERE ISACTIVE = 'Y' AND STATUS = '2'
-- 其他业务条件...
```
**字段含义**:
| 字段 | 值 | 业务含义 |
|------|-----|----------|
| `ISACTIVE` | 'Y' | 单据有效,可参与统计 |
| `ISACTIVE` | 'N' | 单据已删除/作废,应排除 |
| `STATUS` | '1' | 草稿/未提交,应排除 |
| `STATUS` | '2' | **已提交**,可参与统计 |
| `STATUS` | '3' | 审批中,通常排除(除非统计审批流程) |
**应用场景**:
- 统计销售额、销售量 → 必须用 `ISACTIVE='Y' AND STATUS='2'`
- 查询历史订单 → 必须用 `ISACTIVE='Y' AND STATUS='2'`
- 分析业务趋势 → 必须用 `ISACTIVE='Y' AND STATUS='2'`
**注意事项**:
1. 明细表(如 M_RETAILITEM)通常继承主表状态,但有时也有自己的状态字段
2. 部分表可能使用 `ISSTOP` 代替 `ISACTIVE`,逻辑相反('Y'=停用)
3. 查询前应先通过 AD_COLUMN 确认字段名和值域
## 数据权限系统
### GET_USERSPERMSQL 函数
数据库内置 `get_userspermsql` 函数,用于获取用户的数据权限过滤条件。
**函数签名:**
```sql
FUNCTION get_userspermsql(
p_users_id NUMBER, -- 用户ID (USERS.ID)
p_tableid NUMBER, -- 表ID (AD_TABLE.ID)
p_col VARCHAR2 -- 字段名 (AD_COLUMN.DBNAME),可为NULL
) RETURN CLOB -- 返回权限过滤SQL
```
**返回值:**
- 空字符串:全权限(无需过滤)
- SQL条件:如 `C_STORE_ID IN (...)`,需拼接到 WHERE 子句
**权限模型:**
```
用户 → GROUPUSER → 用户组 → GROUPPERM → 安全目录 → 权限SQL
↓
AD_TABLE.DIRECTORY_ID
```
### 使用权限过滤查询
**1. 查询用户权限:**
```bash
python oracle_skill.py perm 940 12983
```
返回:
```json
{
"user_id": "940",
"table_id": "12983",
"perm_sql": "M_OTHER_INOUT.C_STORE_ID IN(...)",
"has_restriction": true
}
```
**2. 带权限过滤的查询:**
```bash
python oracle_skill.py qperm 940 12983 "SELECT * FROM M_OTHER_INOUT WHERE BILLDATE=20260501"
```
自动拼接权限条件后执行:
```sql
SELECT * FROM M_OTHER_INOUT
WHERE BILLDATE=20260501
AND M_OTHER_INOUT.C_STORE_ID IN(...)
```
### 常用表ID参考
| 表名 | AD_TABLE.ID | 说明 |
|------|-------------|------|
| M_RETAIL | 12964 | 零售单主表 |
| M_RETAILITEM | 12965 | 零售明细 |
| M_OTHER_INOUT | 12983 | 其他出入库 |
| C_STORE_CHKDAY | 18182 | 门店盘点日 |
## 故障排查
| 问题 | 排查 |
|------|------|
| `无法连接中转服务` | 检查 transit_url 地址和端口 |
| `agent not found` | 确认 Agent 已启动并连接成功 |
| `请求超时` | Agent 可能卡住,增大 timeout 参数 |
| Oracle 连接失败 | 检查 Agent 的 config.json 中 Oracle 配置 |
## Oracle 日常运维监控触发
当用户说“给我 HENLO 的 Oracle 运维报告”、“生成 HENLO 数据库日报”、“做一次 HENLO Oracle 巡检”这类请求时,调用:
```bash
python scripts/oracle_skill.py ops_report HENLO
```
当用户询问单项运维状态时,调用 `ops <item> [clientCode]`:
```bash
python scripts/oracle_skill.py ops blocking_locks HENLO
python scripts/oracle_skill.py ops active_slow_sql HENLO
python scripts/oracle_skill.py ops ora_errors HENLO
```
常用自然语言映射:
| 用户意图 | 命令 |
|---|---|
| 有没有锁阻塞 | `ops blocking_locks <clientCode>` |
| 当前有没有慢 SQL | `ops active_slow_sql <clientCode>` |
| 最近 ORA 错误 | `ops ora_errors <clientCode>` |
| UNDO 风险 | `ops undo <clientCode>` |
| IO 等待 | `ops io_waits <clientCode>` |
| 内存使用 | `ops memory <clientCode>` |
| 失效对象 | `ops invalid_objects <clientCode>` |
涉及 SYS/DBA 权限视图时,Agent 会优先调用 `SYS.HENLO_ORA_MONITOR`;如果不可用,再尝试当前登录账号下的 `HENLO_ORA_MONITOR`。如果两个函数都不存在或未授权,输出两种安装方案:有 SYS 账号时安装到 SYS 并授权;没有 SYS 账号但当前登录账号已有系统视图查询权限时,安装到当前账号后重试。
## 服务器体检报告触发
当用户说“给我 HENLO 的服务器体检报告”、“查询 HENLO 的服务器状态”、“看看 HENLO 的备份情况”这类请求时,调用:
```bash
python scripts/oracle_skill.py checkup HENLO
```
如果用户没有指定客户服务器编号,则调用:
```bash
python scripts/oracle_skill.py checkup
```
输出字段固定为 `clientCode`、`info_cpu_rate`、`info_memory_rate`、`info_memory`、`bak_path`、`bak_lastdate`、`computer`。`clientCode` 是客户服务器编号,非必填;不传时使用当前登录 client。
## 新增业务记录 SQL 生成规则
当用户要求分析或生成新增业务表记录的 SQL 示例时,仍然保持只读安全边界:skill 可以生成 SQL 和说明,但不能直接通过查询通道执行 INSERT、UPDATE、DELETE、MERGE、DDL 等写操作。
生成新增记录 SQL 时遵守以下默认规则:
- 主表或子表新增记录的主键 `ID` 使用 `get_sequences('<表名称>')` 取值。
- 如果目标表存在 `DOCNO` 字段,先查询 `AD_COLUMN` 中该字段是否配置 `SEQUENCENAME` 单据编号生成器。
- 如果 `DOCNO` 对应的 `SEQUENCENAME` 有值,则单据编号使用 `Get_SequenceNo('<SEQUENCENAME>', 37)` 取值。
- 如果目标表存在 `AD_CLIENT_ID` 字段,默认值使用 `37`。
- 如果目标表存在 `AD_ORG_ID` 字段,默认值使用 `27`。
- 子表的 `AD_CLIENT_ID` 和 `AD_ORG_ID` 默认取父表记录中对应字段值,不单独写固定值。
- 新增业务表单时,如果目标表存在 `STATUS`、`STATUSERID`、`STATUSTIME` 字段,通常只给 `STATUS` 赋默认值 `'1'`,不对 `STATUSERID`、`STATUSTIME` 赋值。
- `STATUS='1'` 表示草稿/未提交;提交人、提交时间等字段由提交动作或提交存储过程处理。
- 如果用户明确提到“提交”,通常不是直接把 `STATUS` 改成 `'2'`,而是应先查询 `AD_TABLE.PROC_SUBMIT`,按该表配置的提交存储过程理解提交逻辑。
查询 `DOCNO` 编号生成器示例:
```sql
SELECT SEQUENCENAME
FROM AD_COLUMN
WHERE AD_TABLE_ID = (SELECT ID FROM AD_TABLE WHERE NAME = '<表名称>')
AND UPPER(DBNAME) = 'DOCNO'
```
## 字段值域映射规则(AD_COLUMN / AD_LIMITVALUE)
当用户要求解释字段含义、生成查询条件、分析业务类型,或生成新增业务记录 SQL 示例时,如果目标字段在 `AD_COLUMN` 中的 `OBTAINMANNER` 值为 `select`,必须继续通过 `AD_LIMITVALUE_GROUP_ID` 查询 `AD_LIMITVALUE`,拿到显示值与数据库实际值的映射关系。
关键规则:
- `AD_COLUMN.OBTAINMANNER = 'select'` 表示该字段是受限值列表。
- `AD_COLUMN.AD_LIMITVALUE_GROUP_ID` 关联 `AD_LIMITVALUE.AD_LIMITVALUE_GROUP_ID`。
- 用户通常说的是显示值,例如“券核销”。
- SQL 查询条件和新增记录 SQL 必须使用数据库实际值,例如 `VOU_USED`。
- 不要把显示值直接写入业务表字段,除非 `AD_LIMITVALUE` 查询结果证明显示值就是实际值。
示例:`XH_ORDER_FTP.BILLTYPE` 字段,当显示值为“券核销”时,对应的数据库实际值是 `VOU_USED`。因此用户说“类型为券核销”时,应生成:
```sql
XH_ORDER_FTP.BILLTYPE = 'VOU_USED'
```
查询字段值域映射模板:
```sql
SELECT c.DBNAME,
c.NAME,
c.OBTAINMANNER,
c.AD_LIMITVALUE_GROUP_ID,
v.VALUE,
v.NAME AS DISPLAY_NAME
FROM AD_COLUMN c
LEFT JOIN AD_LIMITVALUE v ON v.AD_LIMITVALUE_GROUP_ID = c.AD_LIMITVALUE_GROUP_ID
WHERE c.AD_TABLE_ID = (SELECT ID FROM AD_TABLE WHERE NAME = '<表名称>')
AND UPPER(c.DBNAME) = UPPER('<字段名>')
ORDER BY v.ORDERNO, v.ID
```
使用时先查字段:
```sql
SELECT ID, DBNAME, NAME, DESCRIPTION, OBTAINMANNER, AD_LIMITVALUE_GROUP_ID
FROM AD_COLUMN
WHERE AD_TABLE_ID = (SELECT ID FROM AD_TABLE WHERE NAME = 'XH_ORDER_FTP')
AND UPPER(DBNAME) = 'BILLTYPE'
```
如果 `OBTAINMANNER='select'` 且 `AD_LIMITVALUE_GROUP_ID` 有值,再查:
```sql
SELECT VALUE, NAME AS DISPLAY_NAME
FROM AD_LIMITVALUE
WHERE AD_LIMITVALUE_GROUP_ID = <AD_LIMITVALUE_GROUP_ID>
ORDER BY ORDERNO, ID
```
+203
View File
@@ -0,0 +1,203 @@
# 表空间、服务器体检与 Oracle 运维监控
> 本文件由 SKILL.md 整理拆分而来,保留原说明内容。
## 表空间查询
查询 Oracle 表空间使用情况。
### 自动降级策略
脚本按优先级尝试**5 种方式**,无需人工切换:
| 优先级 | 方式 | 数据来源 | 条件 | 输出字段 |
|:---:|------|----------|------|----------|
| 1️⃣ | 直接 DBA 查询 | `DBA_DATA_FILES` + `DBA_FREE_SPACE` | 用户有 `SELECT_CATALOG_ROLE` | 全部 8 个字段 |
| 2️⃣ | DBA 专用函数 | `sys.get_tablespace_usage()`(返回 SYS_REFCURSOR) | DBA 已执行 CREATE FUNCTION + GRANT | 全部 5 个字段(自动解析) |
| 3️⃣ | DBA 授权 VIEW(无前缀) | `HENLO_TS_USAGE` 视图 | DBA 已执行授权 SQL | 全部 8 个字段 |
| 4️⃣ | DBA 授权 VIEW(sys.前缀) | `SYS.HENLO_TS_USAGE` 视图 | DBA 以 SYS 身份创建时自动处理 | 全部 8 个字段 |
| 5️⃣ | 用户视图 | `USER_FREE_SPACE` | 始终可用 | 仅表空间名称 + 剩余MB |
> **经验**:DBA 以 SYS 身份登录创建的 VIEW 会归属到 `SYS` schema,需用 `sys.HENLO_TS_USAGE` 查询。脚本会自动先试无前缀、再试 `sys.` 前缀。
### 给 DBA 的授权 SQL
如果当前用户缺少系统视图权限,脚本会**自动输出**以下 SQL。将其交给 DBA 在 Oracle 中执行一次:
```sql
CREATE OR REPLACE VIEW SYS.HENLO_TS_USAGE AS
SELECT
df.tablespace_name AS "表空间名称",
ROUND(df.total_bytes / 1048576, 1) AS "总大小MB",
ROUND((df.total_bytes - NVL(fs.free_bytes, 0)) / 1048576, 1) AS "已用MB",
ROUND(NVL(fs.free_bytes, 0) / 1048576, 1) AS "剩余MB",
ROUND((df.total_bytes - NVL(fs.free_bytes, 0)) / df.total_bytes * 100, 1) AS "使用率%",
df.autoextensible AS "自动拓展",
ROUND(df.max_bytes / 1048576, 1) AS "最大可拓展MB",
ROUND((df.total_bytes - NVL(fs.free_bytes, 0)) / NULLIF(df.max_bytes, 0) * 100, 1) AS "最大使用率%"
FROM (
SELECT
tablespace_name,
SUM(bytes) AS total_bytes,
SUM(CASE WHEN autoextensible = 'YES' THEN maxbytes ELSE bytes END) AS max_bytes,
MAX(CASE WHEN autoextensible = 'YES' THEN 'YES' ELSE 'NO' END) AS autoextensible
FROM dba_data_files
GROUP BY tablespace_name
) df
LEFT JOIN (
SELECT tablespace_name, SUM(bytes) AS free_bytes
FROM dba_free_space
GROUP BY tablespace_name
) fs ON df.tablespace_name = fs.tablespace_name
ORDER BY 6 DESC;
GRANT SELECT ON SYS.HENLO_TS_USAGE TO bosnds3;
```
> **原理**:Oracle 视图默认使用**定义者权限**(`DEFINER`),以视图创建者的 DBA 身份执行,普通用户仅需视图的 `SELECT` 权限。这比授予 `SELECT_CATALOG_ROLE` 更安全——只暴露这一个视图的数据。
DBA 执行授权后,再次运行 `tablespace` 命令即可自动使用完整查询。
### 输出字段
| 字段 | 说明 |
|------|------|
| 表空间名称 | 表空间名称 |
| 总大小MB | 当前所有数据文件总大小 |
| 已用MB | 已使用的空间 |
| 剩余MB | 剩余可用空间 |
| 使用率% | 已用 / 总大小 × 100% |
| 自动拓展 | YES=自动扩展,NO=不自动扩展 |
| 最大可拓展MB | 开启自动扩展后的最大可达大小 |
| 最大使用率% | 已用 / 最大可拓展 × 100%(参考阈值 5%) |
### 触发语法
用户可以说:
- 「帮我查询下表空间」
- 「查看表空间使用情况」
- 「表空间是否够用」
```bash
python oracle_skill.py tablespace
# 或
python oracle_skill.py tablespaces
```
### 阈值规则
| 使用率 | 状态 | 建议 |
|--------|------|------|
| < 80% | ✅ 正常 | 无需处理 |
| 80~95% | 🔶 关注 | 评估未来增长,制定扩容计划 |
| ≥ 95% | ⚠️ 紧急 | 尽快扩容或清理空间! |
> **智能告警规则**:脚本逐行解析表空间数据,自动判断衡量指标。
> 若表空间**已开启自动扩展**(`自动拓展=YES`),按 **最大使用率%** 判断(已用/最大可拓展);
> 若**未开启**(`NO`),按当前 **使用率%** 判断(已用/当前总大小)。
>
> 这让告警更准确。例如:SYSTEM 使用率 99.4% 但最大使用率仅 3.7%,说明只是当前数据文件写满了,自动扩展后即释放空间,并非真正的存储瓶颈。
### 使用场景
1. **查主表的明细表**:通过 AD_REFBYTABLE 找到主表关联的所有明细表及外键列
2. **查字段含义**:通过 AD_COLUMN 的 DESCRIPTION 字段了解业务含义
3. **构建 JOIN 查询**:根据外键列名自动拼接主表→明细表的 JOIN 条件
4. **理解业务模型**:通过表间关系理解业务单据结构(如零售单→零售明细→支付明细)
### 4. 统计数据场景的过滤条件
在 BOS 系统中,进行统计数据查询时,必须遵循以下业务规则:
**业务单据通用过滤条件**:
- `ISACTIVE = 'Y'`:单据有效(Y=可用,N=已删除/作废)
- `STATUS = '2'`:单据已提交(1=未提交,2=已提交,3=审批中)
**查询模板**:
```sql
-- 查询有效且已提交的业务单据
SELECT * FROM <业务表名>
WHERE ISACTIVE = 'Y' AND STATUS = '2'
-- 其他业务条件...
```
**字段含义**:
| 字段 | 值 | 业务含义 |
|------|-----|----------|
| `ISACTIVE` | 'Y' | 单据有效,可参与统计 |
| `ISACTIVE` | 'N' | 单据已删除/作废,应排除 |
| `STATUS` | '1' | 草稿/未提交,应排除 |
| `STATUS` | '2' | **已提交**,可参与统计 |
| `STATUS` | '3' | 审批中,通常排除(除非统计审批流程) |
**应用场景**:
- 统计销售额、销售量 → 必须用 `ISACTIVE='Y' AND STATUS='2'`
- 查询历史订单 → 必须用 `ISACTIVE='Y' AND STATUS='2'`
- 分析业务趋势 → 必须用 `ISACTIVE='Y' AND STATUS='2'`
**注意事项**:
1. 明细表(如 M_RETAILITEM)通常继承主表状态,但有时也有自己的状态字段
2. 部分表可能使用 `ISSTOP` 代替 `ISACTIVE`,逻辑相反('Y'=停用)
3. 查询前应先通过 AD_COLUMN 确认字段名和值域
## Oracle 日常运维监控触发
当用户说“给我 HENLO 的 Oracle 运维报告”、“生成 HENLO 数据库日报”、“做一次 HENLO Oracle 巡检”这类请求时,调用:
```bash
python scripts/oracle_skill.py ops_report HENLO
```
当用户询问单项运维状态时,调用 `ops <item> [clientCode]`:
```bash
python scripts/oracle_skill.py ops blocking_locks HENLO
python scripts/oracle_skill.py ops active_slow_sql HENLO
python scripts/oracle_skill.py ops ora_errors HENLO
```
常用自然语言映射:
| 用户意图 | 命令 |
|---|---|
| 有没有锁阻塞 | `ops blocking_locks <clientCode>` |
| 当前有没有慢 SQL | `ops active_slow_sql <clientCode>` |
| 最近 ORA 错误 | `ops ora_errors <clientCode>` |
| UNDO 风险 | `ops undo <clientCode>` |
| IO 等待 | `ops io_waits <clientCode>` |
| 内存使用 | `ops memory <clientCode>` |
| 失效对象 | `ops invalid_objects <clientCode>` |
涉及 SYS/DBA 权限视图时,Agent 会优先调用 `SYS.HENLO_ORA_MONITOR`;如果不可用,再尝试当前登录账号下的 `HENLO_ORA_MONITOR`。如果两个函数都不存在或未授权,输出两种安装方案:有 SYS 账号时安装到 SYS 并授权;没有 SYS 账号但当前登录账号已有系统视图查询权限时,安装到当前账号后重试。
## 服务器巡检报告触发
当用户说“给我 HENLO 的服务器体检报告”、“查询 HENLO 的服务器状态”、“看看 HENLO 的备份情况”、“生成巡检 HTML”这类请求时,统一使用归档巡检入口:
```bash
python scripts/oracle_skill.py inspection_report HENLO
python scripts/oracle_skill.py inspection_latest HENLO
python scripts/oracle_skill.py inspection_latest HENLO --refresh
python scripts/oracle_skill.py inspection_report HENLO --markdown
```
`inspection_report` 会实时请求 Agent 的 `inspection_report_data` 并归档到 `TS_INSPECTION_REPORT`;`inspection_latest` 默认读取最近一次历史快照,加 `--refresh` 时重新采集并返回。三个巡检命令成功后均默认使用固定格式模板导出 HTML,保证每次结构一致,只替换数据内容;增加 `--markdown`(或 `--md`)可同时导出固定格式 Markdown。
当前巡检数据包括服务器 IP、操作系统版本、启动时间、运行时长、关键进程、TCP 连接状态、Oracle ACTIVE/总会话数,以及 Buffer Cache、Library Cache 命中率。TCP 连接统计与 Oracle 会话数是两个独立指标,不混合计算。
## AWR 报告
AWR 仅在客户 DBA 明确确认 Diagnostics Pack 授权,且 Agent 同时配置 `awr.enabled=true`、`awr.license_confirmed=true` 后启用。Skill 不执行授权 SQL,不随日常巡检隐式生成 AWR,也不在本地重新渲染 HTML。
当 `awr_status` 返回 `permission_required` 时,Skill 会从目标 Agent 的 `version` action 解析实际 Oracle schema,并返回已替换账号的 `HENLO_AWR_EXPORT` 创建/授权 SQL,供 DBA 在确认 Diagnostics Pack 授权后手工执行。无法解析有效 schema 时停止输出 SQL,不保留 `<AGENT_SCHEMA>` 占位符。
`HENLO_AWR_EXPORT` v1.1 对 Oracle AWR 报告流中的空输出行进行保护,避免把 `LENGTH(NULL)` 传给 `DBMS_LOB.WRITEAPPEND` 触发 `ORA-06502`;每个输出行后追加换行,保持 HTML 原始行边界。若旧版函数已生成 `skipped` 元数据,先由 DBA 手工升级函数,再升级并重启 Agent `1.8.12+` 触发一次恢复重试。
```powershell
python scripts/oracle_skill.py awr_status WEIRUI
python scripts/oracle_skill.py awr_list WEIRUI
python scripts/oracle_skill.py awr_download WEIRUI 20260720
```
Agent 默认在东八区每日 01:00 尝试生成前一日 AWR,并按配置保留本地文件。`awr_download` 经 transit-server 权限校验后原样下载 Agent 文件,默认保存到 `outputs/AWR-<client>-<date>.html`,最大 50 MB。AWR 失败不影响服务器基础巡检、Oracle 会话和缓存命中率等常规项目。
+152
View File
@@ -0,0 +1,152 @@
# 生产查询限制与常用业务规则
> 本文件由 SKILL.md 整理拆分而来,保留原说明内容。
## 生产查询限制
直接查询业务表时,必须带明确过滤条件,不要直接查询整表。生产环境数据量很大,`SELECT * FROM <table>` 这类无条件查询可能返回大量数据、拖慢 Agent 或影响数据库。
生成或执行 `query` / `qperm` SQL 时遵守:
- 必须有 `WHERE` 条件,优先使用日期、单号、门店、客户、状态、主键等业务过滤条件。
- 必须加行数限制,例如 `ROWNUM <= 20`;需要分页时分批查询。
- 不要为了“先看看数据”执行无条件整表查询。
- 统计查询也要限定业务范围,例如日期区间、单据状态、门店范围。
- 如果用户没有给出过滤条件,先追问条件,或先用 `describe` / `AD_COLUMN` / `AD_TABLE` 查询结构和字段含义。
推荐示例:
```sql
SELECT *
FROM M_RETAIL
WHERE BILLDATE = 20260501
AND STATUS = '2'
AND ROWNUM <= 20
```
避免示例:
```sql
SELECT * FROM M_RETAIL
```
## 常用业务对象说明
当用户使用商品、款号、条码、SKU、库存等业务词时,默认按以下对象理解:
- “商品”“款号”通常指 `M_PRODUCT` 表。
- “条码”“SKU”通常指 `M_PRODUCT_ALIAS` 表。
- 查询条码/SKU 时,首查 `M_PRODUCT_ALIAS.NO` 字段。
- 款号和条码是一对多关系:一个 `M_PRODUCT` 可以对应多条 `M_PRODUCT_ALIAS`。
- 商品明细表中常见的三个商品维度字段:
- `M_PRODUCT_ID`:款号 / 商品,关联 `M_PRODUCT.ID`。
- `M_PRODUCTALIAS_ID`:条码 / SKU,关联 `M_PRODUCT_ALIAS.ID`。
- `M_ATTRIBUTESETINSTANCE_ID`:色码属性 ASI,关联 `M_ATTRIBUTESETINSTANCE.ID`。
- `M_ATTRIBUTESETINSTANCE` 用于记录商品色码属性:
- `VALUE1`:颜色名称。
- `VALUE1_CODE`:颜色编号。
- `VALUE1_ID`:颜色表 ID,关联 `M_COLOR.ID`。
- `VALUE2`:尺码名称。
- `VALUE2_CODE`:尺码编号。
- `VALUE2_ID`:尺码表 ID,关联 `M_SIZE.ID`。
- 如果涉及库存查询,通常查询 `V_FA_STORAGE` 视图。
- `V_FA_STORAGE` 数据量很大,必须同时带店仓条件和款号/条码条件,不允许只查整张库存视图。
库存查询过滤规则:
- 店仓条件应优先使用门店、仓库、店仓 ID 或店仓编码等字段,先通过 `describe` 或 `AD_COLUMN` 确认实际字段名。
- 款号条件走 `M_PRODUCT`;条码/SKU 条件走 `M_PRODUCT_ALIAS`。
- 颜色、尺码、色码属性条件优先走 `M_ATTRIBUTESETINSTANCE`,再分别通过 `VALUE1_ID` 关联 `M_COLOR`、通过 `VALUE2_ID` 关联 `M_SIZE`。
- 通过条码查库存时,应先用 `M_PRODUCT_ALIAS.NO` 找到对应商品,再关联或过滤 `V_FA_STORAGE`。
- 必须加 `ROWNUM` 或分页限制;如果用户没有提供店仓或款号/条码,先追问,不要直接查询库存视图。
示例思路:
```sql
-- 伪示例:实际字段名需先 describe / AD_COLUMN 确认
SELECT *
FROM V_FA_STORAGE s
WHERE s.C_STORE_ID = :store_id
AND s.M_PRODUCT_ID = :product_id
AND ROWNUM <= 20
```
## 新增业务记录 SQL 生成规则
当用户要求分析或生成新增业务表记录的 SQL 示例时,仍然保持只读安全边界:skill 可以生成 SQL 和说明,但不能直接通过查询通道执行 INSERT、UPDATE、DELETE、MERGE、DDL 等写操作。
生成新增记录 SQL 时遵守以下默认规则:
- 主表或子表新增记录的主键 `ID` 使用 `get_sequences('<表名称>')` 取值。
- 如果目标表存在 `DOCNO` 字段,先查询 `AD_COLUMN` 中该字段是否配置 `SEQUENCENAME` 单据编号生成器。
- 如果 `DOCNO` 对应的 `SEQUENCENAME` 有值,则单据编号使用 `Get_SequenceNo('<SEQUENCENAME>', 37)` 取值。
- 如果目标表存在 `AD_CLIENT_ID` 字段,默认值使用 `37`。
- 如果目标表存在 `AD_ORG_ID` 字段,默认值使用 `27`。
- 子表的 `AD_CLIENT_ID` 和 `AD_ORG_ID` 默认取父表记录中对应字段值,不单独写固定值。
- 新增业务表单时,如果目标表存在 `STATUS`、`STATUSERID`、`STATUSTIME` 字段,通常只给 `STATUS` 赋默认值 `'1'`,不对 `STATUSERID`、`STATUSTIME` 赋值。
- `STATUS='1'` 表示草稿/未提交;提交人、提交时间等字段由提交动作或提交存储过程处理。
- 如果用户明确提到“提交”,通常不是直接把 `STATUS` 改成 `'2'`,而是应先查询 `AD_TABLE.PROC_SUBMIT`,按该表配置的提交存储过程理解提交逻辑。
- 生成提交 SQL 或说明提交流程时,如果已知 `userId`,调用 `AD_TABLE.PROC_SUBMIT` 前需要先更新主单据修改人和修改时间,再调用提交存储过程;修改人、修改时间字段名必须先通过 `AD_COLUMN` 或表结构确认,不要凭经验硬写字段名。
查询 `DOCNO` 编号生成器示例:
```sql
SELECT SEQUENCENAME
FROM AD_COLUMN
WHERE AD_TABLE_ID = (SELECT ID FROM AD_TABLE WHERE NAME = '<表名称>')
AND UPPER(DBNAME) = 'DOCNO'
```
## 字段值域映射规则(AD_COLUMN / AD_LIMITVALUE)
当用户要求解释字段含义、生成查询条件、分析业务类型,或生成新增业务记录 SQL 示例时,如果目标字段在 `AD_COLUMN` 中的 `OBTAINMANNER` 值为 `select`,必须继续通过 `AD_LIMITVALUE_GROUP_ID` 查询 `AD_LIMITVALUE`,拿到显示值与数据库实际值的映射关系。
关键规则:
- `AD_COLUMN.OBTAINMANNER = 'select'` 表示该字段是受限值列表。
- `AD_COLUMN.AD_LIMITVALUE_GROUP_ID` 关联 `AD_LIMITVALUE.AD_LIMITVALUE_GROUP_ID`。
- 用户通常说的是显示值,例如“券核销”。
- SQL 查询条件和新增记录 SQL 必须使用数据库实际值,例如 `VOU_USED`。
- 不要把显示值直接写入业务表字段,除非 `AD_LIMITVALUE` 查询结果证明显示值就是实际值。
示例:`XH_ORDER_FTP.BILLTYPE` 字段,当显示值为“券核销”时,对应的数据库实际值是 `VOU_USED`。因此用户说“类型为券核销”时,应生成:
```sql
XH_ORDER_FTP.BILLTYPE = 'VOU_USED'
```
查询字段值域映射模板:
```sql
SELECT c.DBNAME,
c.NAME,
c.OBTAINMANNER,
c.AD_LIMITVALUE_GROUP_ID,
v.VALUE,
v.NAME AS DISPLAY_NAME
FROM AD_COLUMN c
LEFT JOIN AD_LIMITVALUE v ON v.AD_LIMITVALUE_GROUP_ID = c.AD_LIMITVALUE_GROUP_ID
WHERE c.AD_TABLE_ID = (SELECT ID FROM AD_TABLE WHERE NAME = '<表名称>')
AND UPPER(c.DBNAME) = UPPER('<字段名>')
ORDER BY v.ORDERNO, v.ID
```
使用时先查字段:
```sql
SELECT ID, DBNAME, NAME, DESCRIPTION, OBTAINMANNER, AD_LIMITVALUE_GROUP_ID
FROM AD_COLUMN
WHERE AD_TABLE_ID = (SELECT ID FROM AD_TABLE WHERE NAME = 'XH_ORDER_FTP')
AND UPPER(DBNAME) = 'BILLTYPE'
```
如果 `OBTAINMANNER='select'` 且 `AD_LIMITVALUE_GROUP_ID` 有值,再查:
```sql
SELECT VALUE, NAME AS DISPLAY_NAME
FROM AD_LIMITVALUE
WHERE AD_LIMITVALUE_GROUP_ID = <AD_LIMITVALUE_GROUP_ID>
ORDER BY ORDERNO, ID
```
+18
View File
@@ -0,0 +1,18 @@
# HENLO Oracle SYS functions
These scripts are DBA-operated SQL artifacts. The Agent never executes `GRANT`, DDL, or any write operation.
Apply order:
1. `henlo_ora_monitor.sql`
2. `henlo_awr_export.sql` only when the DBA has confirmed Diagnostics Pack licensing and AWR access.
Replace `<AGENT_SCHEMA>` with the read-only Oracle account used by the Agent. Record the applied version in the database change log.
Generate the complete current bundle with:
```powershell
python scripts/oracle_skill.py sys_functions <AGENT_SCHEMA>
```
The command reads every `.sql` file in this directory and replaces the schema placeholder. Any new SYS function must be added here so it is automatically included.
@@ -0,0 +1,23 @@
-- HENLO_AWR_EXPORT v1.1
-- DBA only. Requires Diagnostics Pack authorization.
CREATE OR REPLACE FUNCTION SYS.HENLO_AWR_EXPORT(
P_BEGIN_SNAP NUMBER, P_END_SNAP NUMBER, P_DBID NUMBER DEFAULT NULL, P_INSTANCE NUMBER DEFAULT NULL
) RETURN CLOB AUTHID DEFINER IS
L_DBID NUMBER := P_DBID;
L_INSTANCE NUMBER := P_INSTANCE;
L_HTML CLOB;
BEGIN
IF L_DBID IS NULL THEN SELECT DBID INTO L_DBID FROM V$DATABASE; END IF;
IF L_INSTANCE IS NULL THEN SELECT INSTANCE_NUMBER INTO L_INSTANCE FROM V$INSTANCE; END IF;
DBMS_LOB.CREATETEMPORARY(L_HTML, TRUE);
FOR R IN (SELECT OUTPUT FROM TABLE(DBMS_WORKLOAD_REPOSITORY.AWR_REPORT_HTML(L_DBID, L_INSTANCE, P_BEGIN_SNAP, P_END_SNAP))) LOOP
IF R.OUTPUT IS NOT NULL THEN
DBMS_LOB.WRITEAPPEND(L_HTML, LENGTH(R.OUTPUT), R.OUTPUT);
END IF;
DBMS_LOB.WRITEAPPEND(L_HTML, 1, CHR(10));
END LOOP;
RETURN L_HTML;
END;
/
GRANT EXECUTE ON SYS.HENLO_AWR_EXPORT TO <AGENT_SCHEMA>;
GRANT SELECT ON SYS.DBA_HIST_SNAPSHOT TO <AGENT_SCHEMA>;
@@ -0,0 +1,134 @@
-- HENLO_ORA_MONITOR v3.0
-- DBA only. Replace <AGENT_SCHEMA> before execution.
-- Returns JSON in a CLOB. This return contract is required by Agent 1.8.6+.
CREATE OR REPLACE FUNCTION SYS.HENLO_ORA_MONITOR(
P_ITEM VARCHAR2, P_MINUTES NUMBER DEFAULT 60, P_TOP_N NUMBER DEFAULT 20
) RETURN CLOB AUTHID DEFINER IS
L_ITEM VARCHAR2(64) := LOWER(TRIM(P_ITEM));
L_SQL VARCHAR2(32767);
L_TITLE VARCHAR2(200);
L_RESULT CLOB;
L_ROWS CLOB;
L_COLS CLOB;
L_CUR INTEGER;
L_COL_COUNT INTEGER;
L_DESC DBMS_SQL.DESC_TAB2;
L_VALUE VARCHAR2(4000);
L_ROW_COUNT NUMBER := 0;
L_TOP_N NUMBER := LEAST(GREATEST(NVL(P_TOP_N, 20), 1), 100);
L_EXEC INTEGER;
FUNCTION ESC(P_TEXT VARCHAR2) RETURN VARCHAR2 IS
BEGIN
IF P_TEXT IS NULL THEN RETURN ''; END IF;
RETURN REPLACE(REPLACE(REPLACE(REPLACE(P_TEXT, '\', '\\'), '"', '\"'), CHR(13), ' '), CHR(10), ' ');
END;
PROCEDURE APPEND_TEXT(P_TEXT VARCHAR2) IS
BEGIN
DBMS_LOB.WRITEAPPEND(L_RESULT, LENGTH(P_TEXT), P_TEXT);
END;
BEGIN
IF L_ITEM = 'capabilities' THEN
L_TITLE := 'Monitor capabilities';
L_SQL := 'SELECT ''3.0'' function_version, ''active_slow_sql,history_top_sql,fullscan_sql,plan_heavy_sql,blocking_locks,long_transactions,inactive_sessions,session_counts,cache_hit,datafiles,undo,memory,io_waits,background_process,ora_errors,invalid_objects,tablespace,instance_status,awr_snapshots'' supported_items FROM dual';
ELSIF L_ITEM = 'instance_status' THEN
L_TITLE := 'Instance status';
L_SQL := 'SELECT i.status, d.open_mode, d.log_mode, TO_CHAR(i.startup_time,''YYYY-MM-DD HH24:MI:SS'') startup_time, i.instance_name FROM v$instance i CROSS JOIN v$database d';
ELSIF L_ITEM = 'active_slow_sql' THEN
L_TITLE := 'Active slow SQL';
L_SQL := 'SELECT * FROM (SELECT s.sid, s.serial# serial_no, s.username, s.machine, s.program, q.sql_id, SUBSTR(q.sql_text,1,500) sql_text, s.last_call_et elapsed_sec, s.status, s.event, s.wait_class FROM v$session s LEFT JOIN v$sql q ON s.sql_id=q.sql_id WHERE s.status=''ACTIVE'' AND s.type<>''BACKGROUND'' ORDER BY s.last_call_et DESC) WHERE ROWNUM<=' || L_TOP_N;
ELSIF L_ITEM = 'history_top_sql' THEN
L_TITLE := 'AWR top SQL';
L_SQL := 'SELECT * FROM (SELECT s.sql_id, ROUND(SUM(s.elapsed_time_delta)/1000000,2) total_elapsed_s, ROUND(SUM(s.cpu_time_delta)/1000000,2) total_cpu_s, SUM(s.executions_delta) executions, SUBSTR(MAX(t.sql_text),1,500) sql_text FROM dba_hist_sqlstat s JOIN dba_hist_snapshot sn ON s.snap_id=sn.snap_id AND s.dbid=sn.dbid AND s.instance_number=sn.instance_number JOIN dba_hist_sqltext t ON s.sql_id=t.sql_id AND s.dbid=t.dbid WHERE sn.begin_interval_time>=SYSDATE-(NVL(' || NVL(P_MINUTES,60) || ',60)/1440) GROUP BY s.sql_id ORDER BY total_elapsed_s DESC) WHERE ROWNUM<=' || L_TOP_N;
ELSIF L_ITEM = 'fullscan_sql' THEN
L_TITLE := 'Possible full scan SQL';
L_SQL := 'SELECT * FROM (SELECT sql_id, executions, rows_processed, SUBSTR(sql_text,1,500) sql_text FROM v$sql WHERE executions>100 AND LOWER(sql_text) LIKE ''%select%from%'' AND LOWER(sql_text) NOT LIKE ''%where%'' ORDER BY rows_processed DESC) WHERE ROWNUM<=' || L_TOP_N;
ELSIF L_ITEM = 'plan_heavy_sql' THEN
L_TITLE := 'High buffer SQL';
L_SQL := 'SELECT * FROM (SELECT sql_id, plan_hash_value, buffer_gets, disk_reads, executions, ROUND(buffer_gets/DECODE(executions,0,1,executions),2) avg_buffer_gets FROM v$sql WHERE executions>0 ORDER BY buffer_gets DESC) WHERE ROWNUM<=' || L_TOP_N;
ELSIF L_ITEM = 'blocking_locks' THEN
L_TITLE := 'Blocking locks';
L_SQL := 'SELECT * FROM (SELECT l.sid blocker_sid, s1.serial# blocker_serial, s1.username blocker_user, w.sid waiter_sid, s2.serial# waiter_serial, s2.username waiter_user, l.lmode locked_mode, s2.event wait_event FROM v$lock l LEFT JOIN v$session s1 ON l.sid=s1.sid LEFT JOIN v$lock w ON l.id1=w.id1 AND l.id2=w.id2 AND w.request>0 LEFT JOIN v$session s2 ON w.sid=s2.sid WHERE l.block=1) WHERE ROWNUM<=' || L_TOP_N;
ELSIF L_ITEM = 'long_transactions' THEN
L_TITLE := 'Long transactions';
L_SQL := 'SELECT * FROM (SELECT s.sid, s.serial# serial_no, s.username, t.start_time, t.used_ublk, t.used_urec, s.sql_id FROM v$transaction t JOIN v$session s ON t.ses_addr=s.saddr ORDER BY t.start_time) WHERE ROWNUM<=' || L_TOP_N;
ELSIF L_ITEM = 'inactive_sessions' THEN
L_TITLE := 'Inactive sessions';
L_SQL := 'SELECT * FROM (SELECT sid, serial# serial_no, username, machine, program, last_call_et FROM v$session WHERE status=''INACTIVE'' AND last_call_et>1800 ORDER BY last_call_et DESC) WHERE ROWNUM<=' || L_TOP_N;
ELSIF L_ITEM = 'session_counts' THEN
L_TITLE := 'Session counts';
L_SQL := 'SELECT SUM(CASE WHEN status=''ACTIVE'' AND type<>''BACKGROUND'' THEN 1 ELSE 0 END) active_sessions, SUM(CASE WHEN type<>''BACKGROUND'' THEN 1 ELSE 0 END) total_sessions FROM v$session';
ELSIF L_ITEM = 'cache_hit' THEN
L_TITLE := 'Cache hit ratio';
L_SQL := 'SELECT ''BUFFER_CACHE'' metric, ROUND((1-pr.value/NULLIF(cg.value+bg.value,0))*100,2) hit_percent FROM v$sysstat pr,v$sysstat cg,v$sysstat bg WHERE pr.name=''physical reads'' AND cg.name=''consistent gets'' AND bg.name=''db block gets'' UNION ALL SELECT ''LIBRARY_CACHE'',ROUND(SUM(pinhits)/NULLIF(SUM(pins),0)*100,2) FROM v$librarycache';
ELSIF L_ITEM = 'datafiles' THEN
L_TITLE := 'Datafiles';
L_SQL := 'SELECT * FROM (SELECT file_name,tablespace_name,ROUND(bytes/1024/1024/1024,2) curr_gb,ROUND(maxbytes/1024/1024/1024,2) max_gb,ROUND(bytes/DECODE(maxbytes,0,bytes,maxbytes)*100,2) file_used_pct FROM dba_data_files WHERE maxbytes>0 ORDER BY file_used_pct DESC) WHERE ROWNUM<=' || L_TOP_N;
ELSIF L_ITEM = 'undo' THEN
L_TITLE := 'Undo usage';
L_SQL := 'SELECT * FROM (SELECT tablespace_name,status,ROUND(SUM(bytes)/1024/1024/1024,2) gb FROM dba_undo_extents GROUP BY tablespace_name,status ORDER BY gb DESC) WHERE ROWNUM<=' || L_TOP_N;
ELSIF L_ITEM = 'memory' THEN
L_TITLE := 'SGA and PGA';
L_SQL := 'SELECT metric,value_mb FROM (SELECT ''PGA_USED'' metric,ROUND(SUM(pga_used_mem)/1024/1024,2) value_mb FROM v$process UNION ALL SELECT ''PGA_ALLOC'',ROUND(SUM(pga_alloc_mem)/1024/1024,2) FROM v$process UNION ALL SELECT component,ROUND(current_size/1024/1024,2) FROM v$sga_dynamic_components) WHERE ROWNUM<=' || L_TOP_N;
ELSIF L_ITEM = 'io_waits' THEN
L_TITLE := 'IO waits';
L_SQL := 'SELECT * FROM (SELECT event,wait_class,total_waits,ROUND(time_waited/100,2) time_waited_sec,ROUND(average_wait/100,2) avg_wait_ms FROM v$system_event WHERE wait_class IN (''User I/O'',''System I/O'') ORDER BY time_waited_sec DESC) WHERE ROWNUM<=' || L_TOP_N;
ELSIF L_ITEM = 'background_process' THEN
L_TITLE := 'Background processes';
L_SQL := 'SELECT * FROM (SELECT pname,status,program,spid FROM v$process WHERE background=1 ORDER BY pname) WHERE ROWNUM<=' || L_TOP_N;
ELSIF L_ITEM = 'ora_errors' THEN
L_TITLE := 'Recent ORA errors';
L_SQL := 'SELECT * FROM (SELECT originating_timestamp origin_timestamp,SUBSTR(message_text,1,1000) message_text FROM v$diag_alert_ext WHERE originating_timestamp>=SYSTIMESTAMP-NUMTODSINTERVAL(NVL(' || NVL(P_MINUTES,60) || ',60),''MINUTE'') AND message_text LIKE ''%ORA-%'' ORDER BY originating_timestamp DESC) WHERE ROWNUM<=' || L_TOP_N;
ELSIF L_ITEM = 'invalid_objects' THEN
L_TITLE := 'Invalid objects';
L_SQL := 'SELECT * FROM (SELECT object_type,owner,object_name,status FROM dba_objects WHERE status<>''VALID'' ORDER BY owner,object_type,object_name) WHERE ROWNUM<=' || L_TOP_N;
ELSIF L_ITEM = 'tablespace' THEN
L_TITLE := 'Tablespace usage';
L_SQL := 'SELECT * FROM (SELECT df.tablespace_name,ROUND(df.total_bytes/1024/1024/1024,2) total_gb,ROUND((df.total_bytes-NVL(fs.free_bytes,0))/1024/1024/1024,2) used_gb,ROUND(NVL(fs.free_bytes,0)/1024/1024/1024,2) free_gb,ROUND((df.total_bytes-NVL(fs.free_bytes,0))/df.total_bytes*100,2) used_pct FROM (SELECT tablespace_name,SUM(bytes) total_bytes FROM dba_data_files GROUP BY tablespace_name) df LEFT JOIN (SELECT tablespace_name,SUM(bytes) free_bytes FROM dba_free_space GROUP BY tablespace_name) fs ON df.tablespace_name=fs.tablespace_name ORDER BY used_pct DESC) WHERE ROWNUM<=' || L_TOP_N;
ELSIF L_ITEM = 'awr_snapshots' THEN
L_TITLE := 'AWR snapshots';
L_SQL := 'SELECT * FROM (SELECT snap_id,dbid,instance_number,begin_interval_time,end_interval_time FROM dba_hist_snapshot ORDER BY snap_id DESC) WHERE ROWNUM<=' || L_TOP_N;
ELSE
RETURN '{"success":false,"item":"' || ESC(L_ITEM) || '","title":"Unknown","status":"failed","columns":[],"rows":[],"warnings":[],"error":"unknown item"}';
END IF;
DBMS_LOB.CREATETEMPORARY(L_RESULT, TRUE);
DBMS_LOB.CREATETEMPORARY(L_ROWS, TRUE);
DBMS_LOB.CREATETEMPORARY(L_COLS, TRUE);
L_CUR := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(L_CUR, L_SQL, DBMS_SQL.NATIVE);
DBMS_SQL.DESCRIBE_COLUMNS2(L_CUR, L_COL_COUNT, L_DESC);
FOR I IN 1..L_COL_COUNT LOOP
DBMS_SQL.DEFINE_COLUMN(L_CUR, I, L_VALUE, 4000);
IF I > 1 THEN DBMS_LOB.WRITEAPPEND(L_COLS, 1, ','); END IF;
DBMS_LOB.WRITEAPPEND(L_COLS, LENGTH(L_DESC(I).COL_NAME) + 2, '"' || ESC(L_DESC(I).COL_NAME) || '"');
END LOOP;
L_EXEC := DBMS_SQL.EXECUTE(L_CUR);
WHILE DBMS_SQL.FETCH_ROWS(L_CUR) > 0 LOOP
L_ROW_COUNT := L_ROW_COUNT + 1;
IF L_ROW_COUNT > 1 THEN DBMS_LOB.WRITEAPPEND(L_ROWS, 1, ','); END IF;
DBMS_LOB.WRITEAPPEND(L_ROWS, 1, '[');
FOR I IN 1..L_COL_COUNT LOOP
DBMS_SQL.COLUMN_VALUE(L_CUR, I, L_VALUE);
IF I > 1 THEN DBMS_LOB.WRITEAPPEND(L_ROWS, 1, ','); END IF;
L_VALUE := '"' || ESC(SUBSTR(L_VALUE, 1, 1000)) || '"';
DBMS_LOB.WRITEAPPEND(L_ROWS, LENGTH(L_VALUE), L_VALUE);
END LOOP;
DBMS_LOB.WRITEAPPEND(L_ROWS, 1, ']');
END LOOP;
DBMS_SQL.CLOSE_CURSOR(L_CUR);
APPEND_TEXT('{"success":true,"item":"' || ESC(L_ITEM) || '","title":"' || ESC(L_TITLE) || '","status":"normal","columns":[');
DBMS_LOB.APPEND(L_RESULT, L_COLS);
APPEND_TEXT('],"rows":[');
DBMS_LOB.APPEND(L_RESULT, L_ROWS);
APPEND_TEXT('],"warnings":[]}');
RETURN L_RESULT;
EXCEPTION
WHEN OTHERS THEN
IF DBMS_SQL.IS_OPEN(L_CUR) THEN DBMS_SQL.CLOSE_CURSOR(L_CUR); END IF;
RETURN '{"success":false,"item":"' || ESC(L_ITEM) || '","title":"' || ESC(NVL(L_TITLE,L_ITEM)) || '","status":"failed","columns":[],"rows":[],"warnings":[],"error":"' || ESC(SQLERRM) || '"}';
END;
/
GRANT EXECUTE ON SYS.HENLO_ORA_MONITOR TO <AGENT_SCHEMA>;