2026-09-13 16:19:24 +08:00
|
|
|
"""NL2SQL 行级权限条件构造与注入。"""
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
from sqlglot import exp, parse_one
|
|
|
|
|
from sqlglot.errors import ParseError
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RowScopeError(ValueError):
|
|
|
|
|
"""行级权限范围缺失或格式不合法。"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _values(data_scope: dict, scope_type: str) -> list[int]:
|
|
|
|
|
values = data_scope.get(scope_type)
|
|
|
|
|
if not isinstance(values, list) or not values or any(
|
|
|
|
|
isinstance(value, bool) or not isinstance(value, int) for value in values
|
|
|
|
|
):
|
|
|
|
|
raise RowScopeError(f"缺少有效的 {scope_type} 行范围")
|
|
|
|
|
return sorted(set(values))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def apply_row_scope(sql: str, permission: dict, data_scope: dict | None) -> str:
|
|
|
|
|
"""按照服务端权限快照为每个受限表注入 IN 条件。"""
|
|
|
|
|
scopes = permission.get("row_scopes", {})
|
|
|
|
|
if not scopes:
|
|
|
|
|
return sql
|
|
|
|
|
if not isinstance(data_scope, dict):
|
|
|
|
|
raise RowScopeError("缺少行范围参数")
|
|
|
|
|
try:
|
|
|
|
|
statement = parse_one(sql, read="mysql")
|
|
|
|
|
except ParseError as exc:
|
|
|
|
|
raise RowScopeError("SQL 解析失败") from exc
|
2026-09-14 13:00:15 +08:00
|
|
|
for table in list(statement.find_all(exp.Table)):
|
2026-09-13 16:19:24 +08:00
|
|
|
scope = scopes.get(table.name)
|
|
|
|
|
if not scope:
|
|
|
|
|
continue
|
|
|
|
|
values = _values(data_scope, scope["type"])
|
|
|
|
|
condition = exp.In(
|
2026-09-14 10:57:48 +08:00
|
|
|
this=exp.column(scope["column"], table=table.alias_or_name),
|
2026-09-13 16:19:24 +08:00
|
|
|
expressions=[exp.Literal.number(value) for value in values],
|
|
|
|
|
)
|
2026-09-14 13:00:15 +08:00
|
|
|
owner = table.find_ancestor(exp.Select) or statement
|
|
|
|
|
owner.where(condition, copy=False)
|
2026-09-13 16:19:24 +08:00
|
|
|
return statement.sql(dialect="mysql")
|