|
|
@@ -18,6 +18,21 @@ class TableRef:
|
|
|
|
|
|
|
|
|
class SQLGuard:
|
|
|
+ _common_output_labels = {
|
|
|
+ "date",
|
|
|
+ "datetime",
|
|
|
+ "ctr",
|
|
|
+ "dau",
|
|
|
+ "id",
|
|
|
+ "mid",
|
|
|
+ "pv",
|
|
|
+ "rov",
|
|
|
+ "str",
|
|
|
+ "time",
|
|
|
+ "timestamp",
|
|
|
+ "uid",
|
|
|
+ "uv",
|
|
|
+ }
|
|
|
_forbidden = (
|
|
|
exp.Insert,
|
|
|
exp.Update,
|
|
|
@@ -62,6 +77,37 @@ class SQLGuard:
|
|
|
raise SQLValidationError("SQL 未引用数据表")
|
|
|
return refs
|
|
|
|
|
|
+ @staticmethod
|
|
|
+ def validate_output_labels(sql: str) -> None:
|
|
|
+ statement = parse_one(sql, read="hive")
|
|
|
+ output = statement
|
|
|
+ while isinstance(output, exp.SetOperation):
|
|
|
+ output = output.this
|
|
|
+ if not isinstance(output, exp.Select):
|
|
|
+ raise SQLValidationError("无法识别 SQL 的最终输出列")
|
|
|
+
|
|
|
+ invalid: list[str] = []
|
|
|
+ for projection in output.expressions:
|
|
|
+ if isinstance(projection, exp.Star) or getattr(projection, "is_star", False):
|
|
|
+ invalid.append("*")
|
|
|
+ continue
|
|
|
+ label = projection.alias_or_name
|
|
|
+ normalized = str(label).strip().lower()
|
|
|
+ if not normalized:
|
|
|
+ invalid.append(projection.sql(dialect="hive")[:80])
|
|
|
+ continue
|
|
|
+ if re.search(r"[\u4e00-\u9fff]", normalized):
|
|
|
+ continue
|
|
|
+ if normalized in SQLGuard._common_output_labels:
|
|
|
+ continue
|
|
|
+ invalid.append(str(label))
|
|
|
+ if invalid:
|
|
|
+ raise SQLValidationError(
|
|
|
+ "表格列名必须使用中文业务含义;日期/时间及 DAU、PV、UV、STR、ROV、"
|
|
|
+ "ID 等通用名称可保留。请仅修改最终 SELECT 别名:"
|
|
|
+ + "、".join(invalid)
|
|
|
+ )
|
|
|
+
|
|
|
@staticmethod
|
|
|
def validate_product_efficiency_contract(
|
|
|
sql: str,
|