mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-21 06:00:39 +00:00
fix(db2): bound large LOB column previews with SUBSTR to cap memory
Refs: #10723
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user