feat:修改nl2sql功能
This commit is contained in:
+7
-1
@@ -27,6 +27,7 @@ async def execute_readonly_sql(
|
||||
masks: dict[tuple[str, str], str] | None = None,
|
||||
max_rows: int | None = None,
|
||||
timeout_seconds: float | None = None,
|
||||
parameters: dict[str, object] | None = None,
|
||||
user_id: int = 0,
|
||||
connection_id: int | None = None,
|
||||
kill_query=None,
|
||||
@@ -49,7 +50,12 @@ async def execute_readonly_sql(
|
||||
connection_id=connection_id,
|
||||
)
|
||||
try:
|
||||
execution = session.execute(text(validated_sql.sql))
|
||||
statement = text(validated_sql.sql)
|
||||
execution = (
|
||||
session.execute(statement, parameters)
|
||||
if parameters
|
||||
else session.execute(statement)
|
||||
)
|
||||
result = (
|
||||
await asyncio.wait_for(execution, timeout_seconds)
|
||||
if timeout_seconds is not None
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ def apply_row_scope(sql: str, permission: dict, data_scope: dict | None) -> str:
|
||||
continue
|
||||
values = _values(data_scope, scope["type"])
|
||||
condition = exp.In(
|
||||
this=exp.column(scope["column"]),
|
||||
this=exp.column(scope["column"], table=table.alias_or_name),
|
||||
expressions=[exp.Literal.number(value) for value in values],
|
||||
)
|
||||
statement = statement.where(condition)
|
||||
|
||||
+18
-4
@@ -107,18 +107,26 @@ def resolve_semantics(question: str, *, catalog: dict[str, Any] | None = None) -
|
||||
{
|
||||
"term": item["term"],
|
||||
"field": item["fields"][0],
|
||||
"hint": item.get("metric_hint") or item.get("value_hint", ""),
|
||||
"hint": item["metric_hint"],
|
||||
}
|
||||
# metric_hint(指标口径)与 value_hint(字段取值口径)都要进入
|
||||
# SQL 生成上下文;只过滤 metric_hint 会让枚举值提示永远丢失。
|
||||
for item in matched
|
||||
if "metric_hint" in item or "value_hint" in item
|
||||
if item.get("metric_hint")
|
||||
]
|
||||
value_hints = [
|
||||
{
|
||||
"term": item["term"],
|
||||
"field": item["fields"][0],
|
||||
"hint": item["value_hint"],
|
||||
}
|
||||
for item in matched
|
||||
if item.get("value_hint")
|
||||
]
|
||||
return {
|
||||
"terms": [item["term"] for item in matched],
|
||||
"tables": tables,
|
||||
"fields": fields,
|
||||
"metrics": metrics,
|
||||
"value_hints": value_hints,
|
||||
"relationships": list(catalog.get("relationships", [])),
|
||||
}
|
||||
|
||||
@@ -149,6 +157,11 @@ def build_semantic_context(question: str, schema: dict[str, Any]) -> dict[str, A
|
||||
for metric in resolved["metrics"]
|
||||
if any(metric["field"] == field for _, field in schema_fields if _ in tables)
|
||||
]
|
||||
value_hints = [
|
||||
hint
|
||||
for hint in resolved["value_hints"]
|
||||
if any(hint["field"] == field for _, field in schema_fields if _ in tables)
|
||||
]
|
||||
relationships = [
|
||||
relation
|
||||
for relation in resolved["relationships"]
|
||||
@@ -160,5 +173,6 @@ def build_semantic_context(question: str, schema: dict[str, Any]) -> dict[str, A
|
||||
"tables": tables,
|
||||
"fields": fields,
|
||||
"metrics": metrics,
|
||||
"value_hints": value_hints,
|
||||
"relationships": relationships,
|
||||
}
|
||||
|
||||
@@ -83,7 +83,16 @@ def validate_select_sql(
|
||||
if statement.find(exp.Star):
|
||||
raise SqlSecurityError("配置字段权限时禁止 SELECT *")
|
||||
default_table = next(iter(access_tables), None) if len(access_tables) == 1 else None
|
||||
select_aliases = {
|
||||
expression.alias
|
||||
for expression in statement.expressions
|
||||
if isinstance(expression, exp.Alias) and expression.alias
|
||||
}
|
||||
for column in statement.find_all(exp.Column):
|
||||
# ORDER BY may legally reference an alias defined in SELECT. The
|
||||
# alias is already covered by the expressions validated below.
|
||||
if not column.table and column.name in select_aliases and isinstance(column.parent, exp.Ordered):
|
||||
continue
|
||||
table_name = aliases.get(column.table, column.table) or default_table
|
||||
if table_name is None or column.name not in authorized_columns.get(table_name, set()):
|
||||
raise SqlSecurityError("SQL 访问了未授权字段")
|
||||
|
||||
Reference in New Issue
Block a user