fix(db2): bound large LOB column previews with SUBSTR to cap memory

Refs: #10723
This commit is contained in:
t8y2
2026-10-03 14:40:55 +08:00
parent 4cab7abf83
commit 94f36c57d3
2 changed files with 54 additions and 0 deletions
@@ -92,6 +92,18 @@ fn large_value_preview_kind(
None
}
}
Some(DatabaseType::Db2) => {
if matches!(base.as_str(), "blob" | "binary" | "varbinary" | "longvarbinary") {
Some(LargeValuePreviewKind::Binary)
} else if matches!(base.as_str(), "clob" | "dbclob")
|| (matches!(base.as_str(), "char" | "character" | "varchar" | "graphic" | "vargraphic")
&& declared_data_type_length(data_type).is_some_and(|length| length > preview_size))
{
Some(LargeValuePreviewKind::Text)
} else {
None
}
}
_ => None,
}
}
@@ -149,6 +161,17 @@ fn build_large_value_preview_columns(options: &TableDataSelectSqlOptions) -> Opt
(format!("left({quoted}::text, {prefix_size}) AS {quoted}"), "V")
}
Some(DatabaseType::Postgres) => (format!("left({quoted}, {prefix_size}) AS {quoted}"), "T"),
// DB2 的 SUBSTR 第三参数越界会报 SQL0138N,而 BLOB/CLOB 长度不定,故用
// CASE WHEN LENGTH(..) 把预览长度夹在实际长度内,对短值不报错;SUBSTR 对 BLOB
// 返回 BLOB、对 CLOB/字符列返回 VARCHAR,agent 侧 BLOB 再转 0x 前缀的 hex 字符串。
Some(DatabaseType::Db2) if kind == LargeValuePreviewKind::Binary => (
format!("SUBSTR({quoted}, 1, CASE WHEN LENGTH({quoted}) >= {prefix_size} THEN {prefix_size} ELSE LENGTH({quoted}) END) AS {quoted}"),
"B",
),
Some(DatabaseType::Db2) => (
format!("SUBSTR({quoted}, 1, CASE WHEN LENGTH({quoted}) >= {prefix_size} THEN {prefix_size} ELSE LENGTH({quoted}) END) AS {quoted}"),
"T",
),
_ => return None,
};
let marker = if database_type == Some(DatabaseType::Mysql) {
@@ -1034,6 +1034,37 @@ fn builds_postgres_table_data_large_value_previews() {
assert!(sql.contains("'T:8192' AS \"__DBX_LARGE_VALUE_BYTES_K_2\""));
}
#[test]
fn builds_db2_table_data_large_value_previews_with_bounded_substr() {
let sql = build_table_data_select_sql(TableDataSelectSqlOptions {
database_type: Some(DatabaseType::Db2),
schema: Some("DB2INST1".to_string()),
table_name: "large_rows".to_string(),
primary_keys: vec!["id".to_string()],
columns: vec!["id".to_string(), "doc".to_string(), "raw_value".to_string()],
column_types: vec!["integer".to_string(), "clob".to_string(), "blob".to_string()],
large_value_preview_size: Some(4096),
limit: Some(100),
..Default::default()
});
// DB2 的 SUBSTR 第三参数越界会报 SQL0138N,故用 CASE WHEN LENGTH(..) 把预览长度
// 夹在实际长度内:CLOB → 文本预览、BLOB → 二进制预览,都不对短值报错。
assert!(
sql.contains("SUBSTR(\"doc\", 1, CASE WHEN LENGTH(\"doc\") >= 4097 THEN 4097 ELSE LENGTH(\"doc\") END) AS \"doc\""),
"clob preview sql: {sql}"
);
assert!(sql.contains("'T:4096' AS "), "clob marker sql: {sql}");
assert!(
sql.contains("SUBSTR(\"raw_value\", 1, CASE WHEN LENGTH(\"raw_value\") >= 4097 THEN 4097 ELSE LENGTH(\"raw_value\") END) AS \"raw_value\""),
"blob preview sql: {sql}"
);
assert!(sql.contains("'B:4096' AS "), "blob marker sql: {sql}");
// 主键列不做预览,且不得生成裸 SUBSTR(col,1,n)(会越界报错)
assert!(!sql.contains("SUBSTR(\"id\""), "pk should not be previewed: {sql}");
assert!(!sql.contains("SUBSTR(\"doc\", 1, 4097)"), "must not emit unbounded substr: {sql}");
}
#[test]
fn preserves_postgres_array_types_in_large_value_previews() {
let sql = build_table_data_select_sql(TableDataSelectSqlOptions {