fix: batch_alter_type 转换 NUMERIC 类型前 clamp 超范围值,防止溢出

This commit is contained in:
fawney19
2026-03-09 16:56:41 +08:00
parent 8cb8666456
commit 4999a1a0a8

View File

@@ -167,6 +167,18 @@ def index_exists(index_name: str) -> bool:
# ---------------------------------------------------------------------------
def _numeric_max(type_spec: str) -> float | None:
"""Parse NUMERIC(p,s) and return the maximum absolute value, or None."""
# e.g. "NUMERIC(20,8)" -> precision=20, scale=8 -> max = 10^(20-8) - 10^(-8)
import re
m = re.match(r"NUMERIC\((\d+),(\d+)\)", type_spec, re.IGNORECASE)
if not m:
return None
precision, scale = int(m.group(1)), int(m.group(2))
return 10 ** (precision - scale) - 10 ** (-scale)
def batch_alter_type(
cache: _SchemaCache,
columns: list[tuple[str, str, bool, str | None]],
@@ -177,6 +189,9 @@ def batch_alter_type(
Skips columns that don't exist. Each tuple is
``(table_name, column_name, nullable, server_default)``.
When converting to NUMERIC(p,s), values exceeding the target precision
are clamped before the type cast to prevent overflow errors.
"""
by_table: dict[str, list[tuple[str, str]]] = defaultdict(list)
for table, col, _nullable, _default in columns:
@@ -186,6 +201,17 @@ def batch_alter_type(
bind = op.get_bind()
for table, col_types in by_table.items():
# Clamp out-of-range values before ALTER TYPE
for col, target in col_types:
cap = _numeric_max(target)
if cap is not None:
bind.execute(
sa.text(
f"UPDATE {table} SET {col} = :cap "
f"WHERE {col} IS NOT NULL AND abs({col}) > :cap"
),
{"cap": cap},
)
parts = [
f"ALTER COLUMN {col} TYPE {target} USING {col}::{cast_suffix}"
for col, target in col_types