mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 03:31:58 +00:00
ee7b8b2f97
为什么:剖析是全站唯一主动查业务库的地方,以前只在守卫放行和字符串片段两层上测过,从没在真库上跑过。
本机四个容器(PG 16、MySQL 8.4、MariaDB 11、Oracle Free 23)实测,旧的 IN 列表写法在 PostgreSQL 上整数子列
对文字主键直接报 operator does not exist: character varying = integer,关系一条都核对不了。
C1 方言修正:
- IN 列表的字面量按被指向列的类型族写(key_literals):父列文字写 '12',父列数字只收整数写法的值,父列是时间、
对不上的不拼并在备注里写「两端类型不同」;子表是 CHAR(n) 时去掉补齐的尾随空格。
- 列类型改存按方言编译的完整写法(VARCHAR2(20 CHAR)、NUMBER(10, 0)、ENUM('a','b')、TIMESTAMP WITH TIME ZONE),
编译不了退回 str()。SQLAlchemy 把 Oracle 的 VARCHAR2 反射成通用 VARCHAR(容器实测),照数据字典写成 VARCHAR2。
ENUM / SET 另记 enum_values;Oracle 小写带引号的表和列记 quote_name;各索引(含主键)的首列记进
index_first_columns(下一波取值观察判断能不能对大表发窗口查询)。新探查的快照哈希会变,旧运行封存的不受影响。
- catalog._type_family 认 ENUM / SET 是文字、INTERVAL 不是数字,去掉引号里的取值再认;新增 column_family
(有 enum_values 的是文字,PostgreSQL 枚举编译出来只有类型名);is_date_column 不再把 ENUM('date') 认成日期列。
- 码值候选:ENUM / SET 不看列名直接算候选,ENUM 列定义里这次没出现的取值也写进码值(含义待填);VARCHAR2(20 CHAR)
的长度按 20 算;CHAR 码值去尾随空格;MySQL 上写明按排序规则分组、比较(可能不分大小写)。
- 统计信息估出的行数不超过整表统计上限时再跑一次有上限的计数确认,统计过期的大表不再被当成小表做整表统计。
- 日期列的最小值、最大值只看 0001-01-02 到 9999-12-31 之间的值,零日期、占位日期、PG 的 ±infinity 不计入,
照实写「另有 N 个无效日期未计入」,日期范围里带 invalid 个数(界面显示)。
- 新增取值观察用的方言方法 date_literal / day_trunc / sample_from,以及按日行数、时间窗口里的取值分布、块采样三类
查询的拼法(窗口上端截在 9999-12-31 之前,不让 infinity 多出一天)。
- 说明文字不再说「抽样」:大表看的是「按存储顺序取前 N 行」;设置项「抽样键值数」改叫「核对键值数」。
C2 验证:
- tests/test_profile_dialect_golden.py:五种方言 × 全部剖析和观察查询的完整 SQL 钉在 tests/fixtures/dialect/,
sqlglot 逐条解析、过只读守卫,再用 qualify + annotate_types 核对 IN 两边类型族一致(附旧写法的对照)。
- scripts/dialect-it.sh(不进 CI):本机起四个容器,建虚构夹具(保留字列、Oracle 小写带引号的表、CHAR 码值、
MySQL ENUM、零日期 / infinity / 占位日期、做旧的统计信息、超上限的大表、带索引的日期列),用 AgentLab 自己的
探查和剖析代码跑全部查询,断言零报错、结论和手算一致。四种库都通过;MariaDB 插入时自己更新行数估算,统计信息
做不旧,照实打印。把 IN 列表改回旧写法回跑,PG 上照样报出上面那条错误。跑完删容器、留镜像。
63 lines
2.6 KiB
Bash
Executable File
63 lines
2.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# 方言的容器集成测试(下一轮 03 细案 C2):本机 docker 起 PostgreSQL 16、MySQL 8.4、MariaDB 11、Oracle Free 23 四个容器,
|
||
# 建虚构的夹具(保留字列、Oracle 小写带引号的表、CHAR 码值、MySQL ENUM、零日期 / 无穷大 / 占位日期、做旧的统计信息、
|
||
# 超过整表统计上限的大表、带索引的日期列),用 AgentLab 自己的探查和剖析代码跑全部剖析查询和取值观察的新方法,
|
||
# 断言零报错、结论和手算的一致。
|
||
#
|
||
# 为什么不进 CI:要拉几 GB 的镜像、Oracle 起一次要一两分钟。要用户同意才在本机跑。跑完删容器、留镜像。
|
||
#
|
||
# 用法:scripts/dialect-it.sh 四种库都跑
|
||
# DIALECT_IT_ONLY=pg,mysql scripts/dialect-it.sh 只跑其中几种(pg、mysql、mariadb、oracle)
|
||
# PYTHON=/path/to/python scripts/dialect-it.sh 指定装着后端依赖的 Python(缺省 python3)
|
||
#
|
||
# 夹具全是虚构的通用名字(stores、orders、events、visits),不连任何业务库。
|
||
set -euo pipefail
|
||
|
||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||
PY="${PYTHON:-python3}"
|
||
ONLY="${DIALECT_IT_ONLY:-pg,mysql,mariadb,oracle}"
|
||
PREFIX="agentlab-dialect-it"
|
||
PASS="itpass"
|
||
|
||
started=()
|
||
cleanup() {
|
||
for name in "${started[@]}"; do
|
||
docker rm -f "$name" >/dev/null 2>&1 || true
|
||
done
|
||
}
|
||
trap cleanup EXIT
|
||
|
||
want() { [[ ",$ONLY," == *",$1,"* ]]; }
|
||
|
||
run() {
|
||
local name="$PREFIX-$1"; shift
|
||
docker rm -f "$name" >/dev/null 2>&1 || true
|
||
docker run -d --name "$name" "$@" >/dev/null
|
||
started+=("$name")
|
||
}
|
||
|
||
host_port() { docker port "$PREFIX-$1" "$2" | head -1 | sed 's/.*://'; }
|
||
|
||
if want pg; then
|
||
run pg -e POSTGRES_PASSWORD="$PASS" -e POSTGRES_DB=it -p 127.0.0.1::5432 postgres:16
|
||
fi
|
||
if want mysql; then
|
||
run mysql -e MYSQL_ROOT_PASSWORD="$PASS" -e MYSQL_DATABASE=it -p 127.0.0.1::3306 mysql:8.4
|
||
fi
|
||
if want mariadb; then
|
||
run mariadb -e MARIADB_ROOT_PASSWORD="$PASS" -e MARIADB_DATABASE=it -p 127.0.0.1::3306 mariadb:11
|
||
fi
|
||
if want oracle; then
|
||
run oracle -e ORACLE_PASSWORD="$PASS" -e APP_USER=it -e APP_USER_PASSWORD="$PASS" -p 127.0.0.1::1521 \
|
||
gvenzl/oracle-free:23-slim
|
||
fi
|
||
|
||
export DIALECT_IT_PASSWORD="$PASS" DIALECT_IT_ONLY="$ONLY"
|
||
want pg && export DIALECT_IT_PG_PORT="$(host_port pg 5432/tcp)"
|
||
want mysql && export DIALECT_IT_MYSQL_PORT="$(host_port mysql 3306/tcp)"
|
||
want mariadb && export DIALECT_IT_MARIADB_PORT="$(host_port mariadb 3306/tcp)"
|
||
want oracle && export DIALECT_IT_ORACLE_PORT="$(host_port oracle 1521/tcp)"
|
||
|
||
# 等库起来由 Python 那边按连接重试(Oracle 最慢,最多等 6 分钟)
|
||
PYTHONPATH="$ROOT/backend" "$PY" "$ROOT/scripts/dialect_it.py"
|