mirror of
https://gh-proxy.org/https://github.com/klinsmaya/SQLBot.git
synced 2026-11-08 01:06:59 +08:00
- Add DataFormat.normalize_qualified_sql_column_keys* so driver keys like "_u2.table_name" also expose "table_name", matching chart columns[].value. - Apply after SQL execution (LLM save path), format_json_list_data (API/history), and get_chart_data_ds (live refresh). Hive prompts: Document explicit table aliases for duplicate column names in JOINs. Frontend: Add datasource form i18n key "file_path" (en/zh-CN/zh-TW/ko-KR). Update Hive/SQLite datasource icons. Remove hive from haveSchema list.
180 lines
7.4 KiB
Python
180 lines
7.4 KiB
Python
import pandas as pd
|
|
|
|
from apps.chat.models.chat_model import AxisObj
|
|
|
|
|
|
class DataFormat:
|
|
@staticmethod
|
|
def safe_convert_to_string(df):
|
|
df_copy = df.copy()
|
|
|
|
for col in df_copy.columns:
|
|
# 使用map避免ambiguous truth value问题
|
|
df_copy[col] = df_copy[col].map(
|
|
# 关键:在数字字符串前添加零宽空格,阻止pandas的自动格式化
|
|
lambda x: "" if pd.isna(x) else "\u200b" + str(x)
|
|
)
|
|
|
|
return df_copy
|
|
|
|
@staticmethod
|
|
def normalize_qualified_sql_column_keys(row: dict) -> dict:
|
|
"""Add unqualified keys for names like ``alias.column`` (Hive/MySQL return shape).
|
|
|
|
Chart bindings use the bare column name (``table_name``) while drivers may return
|
|
``_u2.table_name``. Only adds ``short`` when absent to avoid clobbering real duplicates.
|
|
"""
|
|
if not row:
|
|
return row
|
|
out = dict(row)
|
|
for k, v in row.items():
|
|
ks = str(k)
|
|
if "." not in ks:
|
|
continue
|
|
short = ks.rsplit(".", 1)[-1]
|
|
if short not in out:
|
|
out[short] = v
|
|
return out
|
|
|
|
@staticmethod
|
|
def normalize_qualified_sql_column_keys_in_object_array(obj_array: list) -> list:
|
|
if not obj_array:
|
|
return obj_array
|
|
return [
|
|
DataFormat.normalize_qualified_sql_column_keys(obj) if isinstance(obj, dict) else obj
|
|
for obj in obj_array
|
|
]
|
|
|
|
@staticmethod
|
|
def convert_large_numbers_in_object_array(obj_array, int_threshold=1e15, float_threshold=1e10):
|
|
"""处理对象数组,将每个对象中的大数字转换为字符串"""
|
|
|
|
def format_float_without_scientific(value):
|
|
"""格式化浮点数,避免科学记数法"""
|
|
if value == 0:
|
|
return "0"
|
|
formatted = f"{value:.15f}"
|
|
if '.' in formatted:
|
|
formatted = formatted.rstrip('0').rstrip('.')
|
|
return formatted
|
|
|
|
def process_object(obj):
|
|
"""处理单个对象"""
|
|
if not isinstance(obj, dict):
|
|
return obj
|
|
|
|
processed_obj = {}
|
|
for key, value in obj.items():
|
|
if isinstance(value, (int, float)):
|
|
# 只转换大数字
|
|
if isinstance(value, int) and abs(value) >= int_threshold:
|
|
processed_obj[key] = str(value)
|
|
elif isinstance(value, float) and (abs(value) >= float_threshold or abs(value) < 1e-6):
|
|
processed_obj[key] = format_float_without_scientific(value)
|
|
else:
|
|
processed_obj[key] = value
|
|
elif isinstance(value, dict):
|
|
# 处理嵌套对象
|
|
processed_obj[key] = process_object(value)
|
|
elif isinstance(value, list):
|
|
# 处理对象中的数组
|
|
processed_obj[key] = [process_item(item) for item in value]
|
|
else:
|
|
processed_obj[key] = value
|
|
return processed_obj
|
|
|
|
def process_item(item):
|
|
"""处理数组中的项目"""
|
|
if isinstance(item, dict):
|
|
return process_object(item)
|
|
return item
|
|
|
|
return [process_item(obj) for obj in obj_array]
|
|
|
|
@staticmethod
|
|
def convert_object_array_for_pandas(column_list: list, data_list: list):
|
|
_fields_list = []
|
|
for field_idx, field in enumerate(column_list):
|
|
_fields_list.append(field.name)
|
|
|
|
md_data = []
|
|
for inner_data in data_list:
|
|
_row = []
|
|
for field_idx, field in enumerate(column_list):
|
|
value = inner_data.get(field.value)
|
|
_row.append(value)
|
|
md_data.append(_row)
|
|
return md_data, _fields_list
|
|
|
|
@staticmethod
|
|
def convert_data_fields_for_pandas(chart: dict, fields: list, data: list):
|
|
_fields = {}
|
|
if chart.get('columns'):
|
|
for _column in chart.get('columns'):
|
|
if _column:
|
|
_fields[_column.get('value')] = _column.get('name')
|
|
if chart.get('axis'):
|
|
if chart.get('axis').get('x'):
|
|
_fields[chart.get('axis').get('x').get('value')] = chart.get('axis').get('x').get('name')
|
|
if chart.get('axis').get('y'):
|
|
# _fields[chart.get('axis').get('y').get('value')] = chart.get('axis').get('y').get('name')
|
|
y_axis = chart.get('axis').get('y')
|
|
if isinstance(y_axis, list):
|
|
# y轴是数组的情况(多指标字段)
|
|
for y_item in y_axis:
|
|
if isinstance(y_item, dict) and 'value' in y_item and 'name' in y_item:
|
|
_fields[y_item.get('value')] = y_item.get('name')
|
|
elif isinstance(y_axis, dict):
|
|
# y轴是对象的情况(单指标字段)
|
|
if 'value' in y_axis and 'name' in y_axis:
|
|
_fields[y_axis.get('value')] = y_axis.get('name')
|
|
if chart.get('axis').get('series'):
|
|
_fields[chart.get('axis').get('series').get('value')] = chart.get('axis').get('series').get(
|
|
'name')
|
|
_column_list = []
|
|
for field in fields:
|
|
_column_list.append(
|
|
AxisObj(name=field if not _fields.get(field) else _fields.get(field), value=field))
|
|
|
|
md_data, _fields_list = DataFormat.convert_object_array_for_pandas(_column_list, data)
|
|
|
|
return md_data, _fields_list
|
|
|
|
@staticmethod
|
|
def format_pd_data(column_list: list, data_list: list, col_formats: dict = None):
|
|
# 预处理数据并记录每列的格式类型
|
|
# 格式类型:'text'(文本)、'number'(数字)、'default'(默认)
|
|
_fields_list = []
|
|
|
|
if col_formats is None:
|
|
col_formats = {}
|
|
for field_idx, field in enumerate(column_list):
|
|
_fields_list.append(field.name)
|
|
col_formats[field_idx] = 'default' # 默认不特殊处理
|
|
|
|
data = []
|
|
|
|
for _data in data_list:
|
|
_row = []
|
|
for field_idx, field in enumerate(column_list):
|
|
value = _data.get(field.value)
|
|
if value is not None:
|
|
# 检查是否为数字且需要特殊处理
|
|
if isinstance(value, (int, float)):
|
|
# 整数且超过15位 → 转字符串并标记为文本列
|
|
if isinstance(value, int) and len(str(abs(value))) > 15:
|
|
value = str(value)
|
|
col_formats[field_idx] = 'text'
|
|
# 小数且超过15位有效数字 → 转字符串并标记为文本列
|
|
elif isinstance(value, float):
|
|
decimal_str = format(value, '.16f').rstrip('0').rstrip('.')
|
|
if len(decimal_str) > 15:
|
|
value = str(value)
|
|
col_formats[field_idx] = 'text'
|
|
# 其他数字列标记为数字格式(避免科学记数法)
|
|
elif col_formats[field_idx] != 'text':
|
|
col_formats[field_idx] = 'number'
|
|
_row.append(value)
|
|
data.append(_row)
|
|
|
|
return data, _fields_list, col_formats |