From 17874acb21fca41945baa70fa063fcbc6719207f Mon Sep 17 00:00:00 2001 From: junjun Date: Tue, 19 Aug 2025 09:48:10 +0800 Subject: [PATCH] feat: support DM datasource --- backend/apps/datasource/crud/datasource.py | 48 ++- backend/apps/db/constant.py | 26 +- backend/apps/db/db.py | 424 ++++++++++++--------- backend/apps/db/type.py | 3 +- backend/pyproject.toml | 1 + backend/template.yaml | 4 +- frontend/src/assets/datasource/icon_dm.png | Bin 0 -> 1382 bytes frontend/src/views/ds/DatasourceForm.vue | 2 +- frontend/src/views/ds/js/ds-type.ts | 5 +- 9 files changed, 307 insertions(+), 206 deletions(-) create mode 100644 frontend/src/assets/datasource/icon_dm.png diff --git a/backend/apps/datasource/crud/datasource.py b/backend/apps/datasource/crud/datasource.py index db547011..801f5a1b 100644 --- a/backend/apps/datasource/crud/datasource.py +++ b/backend/apps/datasource/crud/datasource.py @@ -2,12 +2,14 @@ import datetime import json from typing import List, Optional +import dmPython from fastapi import HTTPException from sqlalchemy import and_, text from sqlmodel import select from apps.datasource.crud.permission import get_column_permission_fields, get_row_permission_filters, is_normal_user from apps.datasource.utils.utils import aes_decrypt +from apps.db.constant import ConnectType from apps.db.constant import DB from apps.db.db import get_engine, get_tables, get_fields, exec_sql from apps.db.engine import get_engine_config, get_engine_conn @@ -45,16 +47,32 @@ def check_status_by_id(session: SessionDep, trans: Trans, ds_id: int, is_raise: def check_status(session: SessionDep, trans: Trans, ds: CoreDatasource, is_raise: bool = False): - conn = get_engine(ds, 10) - try: - with conn.connect() as connection: - SQLBotLogUtil.info("success") - return True - except Exception as e: - SQLBotLogUtil.error(f"Datasource {ds.id} connection failed: {e}") - if is_raise: - raise HTTPException(status_code=500, detail=trans('i18n_ds_invalid') + f': {e.args}') - return False + db = DB.get_db(ds.type) + if db.connect_type == ConnectType.sqlalchemy: + conn = get_engine(ds, 10) + try: + with conn.connect() as connection: + SQLBotLogUtil.info("success") + return True + except Exception as e: + SQLBotLogUtil.error(f"Datasource {ds.id} connection failed: {e}") + if is_raise: + raise HTTPException(status_code=500, detail=trans('i18n_ds_invalid') + f': {e.args}') + return False + else: + conf = DatasourceConf(**json.loads(aes_decrypt(ds.configuration))) + if ds.type == 'dm': + with dmPython.connect(user=conf.username, password=conf.password, server=conf.host, + port=conf.port) as conn, conn.cursor() as cursor: + try: + cursor.execute('select 1').fetchall() + SQLBotLogUtil.info("success") + return True + except Exception as e: + SQLBotLogUtil.error(f"Datasource {ds.id} connection failed: {e}") + if is_raise: + raise HTTPException(status_code=500, detail=trans('i18n_ds_invalid') + f': {e.args}') + return False def check_name(session: SessionDep, trans: Trans, user: CurrentUser, ds: CoreDatasource): @@ -159,7 +177,7 @@ def getFieldsByDs(session: SessionDep, ds: CoreDatasource, table_name: str): def execSql(session: SessionDep, id: int, sql: str): ds = session.exec(select(CoreDatasource).where(CoreDatasource.id == id)).first() - return exec_sql(ds, sql) + return exec_sql(ds, sql, True) def sync_table(session: SessionDep, ds: CoreDatasource, tables: List[CoreTable]): @@ -297,7 +315,11 @@ def preview(session: SessionDep, current_user: CurrentUser, id: int, data: Table sql = f"""SELECT "{'", "'.join(fields)}" FROM "{data.table.table_name}" {where} LIMIT 100""" - return exec_sql(ds, sql) + elif ds.type == "dm": + sql = f"""SELECT "{'", "'.join(fields)}" FROM "{conf.dbSchema}"."{data.table.table_name}" + {where} + LIMIT 100""" + return exec_sql(ds, sql, True) def fieldEnum(session: SessionDep, id: int): @@ -313,7 +335,7 @@ def fieldEnum(session: SessionDep, id: int): db = DB.get_db(ds.type) sql = f"""SELECT DISTINCT {db.prefix}{field.field_name}{db.suffix} FROM {db.prefix}{table.table_name}{db.suffix}""" - res = exec_sql(ds, sql) + res = exec_sql(ds, sql, True) return [item.get(res.get('fields')[0]) for item in res.get('data')] diff --git a/backend/apps/db/constant.py b/backend/apps/db/constant.py index 18141c6e..dc4b5807 100644 --- a/backend/apps/db/constant.py +++ b/backend/apps/db/constant.py @@ -4,18 +4,28 @@ from enum import Enum -class DB(Enum): - mysql = ('mysql', '`', '`') - sqlServer = ('sqlServer', '[', ']') - pg = ('pg', '"', '"') - excel = ('excel', '"', '"') - oracle = ('oracle', '"', '"') - ck = ('ClickHouse', '"', '"') +class ConnectType(Enum): + sqlalchemy = ('sqlalchemy') + py_driver = ('py_driver') - def __init__(self, type, prefix, suffix): + def __init__(self, type_name): + self.type_name = type_name + + +class DB(Enum): + mysql = ('mysql', '`', '`', ConnectType.sqlalchemy) + sqlServer = ('sqlServer', '[', ']', ConnectType.sqlalchemy) + pg = ('pg', '"', '"', ConnectType.sqlalchemy) + excel = ('excel', '"', '"', ConnectType.sqlalchemy) + oracle = ('oracle', '"', '"', ConnectType.sqlalchemy) + ck = ('ck', '"', '"', ConnectType.sqlalchemy) + dm = ('dm', '"', '"', ConnectType.py_driver) + + def __init__(self, type, prefix, suffix, connect_type: ConnectType): self.type = type self.prefix = prefix self.suffix = suffix + self.connect_type = connect_type @classmethod def get_db(cls, type): diff --git a/backend/apps/db/db.py b/backend/apps/db/db.py index 8584f1fc..169ba589 100644 --- a/backend/apps/db/db.py +++ b/backend/apps/db/db.py @@ -3,11 +3,13 @@ import json import urllib.parse from decimal import Decimal +import dmPython from sqlalchemy import create_engine, text, Engine from sqlalchemy.orm import sessionmaker from apps.datasource.models.datasource import DatasourceConf, CoreDatasource, TableSchema, ColumnSchema from apps.datasource.utils.utils import aes_decrypt +from apps.db.constant import DB, ConnectType from apps.db.engine import get_engine_config from apps.system.crud.assistant import get_ds_engine from apps.system.schemas.system_schema import AssistantOutDsSchema @@ -86,189 +88,251 @@ def get_session(ds: CoreDatasource | AssistantOutDsSchema): def get_tables(ds: CoreDatasource): conf = DatasourceConf(**json.loads(aes_decrypt(ds.configuration))) if ds.type != "excel" else get_engine_config() - with get_session(ds) as session: - sql: str = '' - if ds.type == "mysql": - sql = f""" - SELECT - TABLE_NAME, - TABLE_COMMENT - FROM - information_schema.TABLES - WHERE - TABLE_SCHEMA = '{conf.database}' - """ - elif ds.type == "sqlServer": - sql = f""" - SELECT - TABLE_NAME AS [TABLE_NAME], - ISNULL(ep.value, '') AS [TABLE_COMMENT] - FROM - INFORMATION_SCHEMA.TABLES t - LEFT JOIN - sys.extended_properties ep - ON ep.major_id = OBJECT_ID(t.TABLE_SCHEMA + '.' + t.TABLE_NAME) - AND ep.minor_id = 0 - AND ep.name = 'MS_Description' - WHERE - t.TABLE_TYPE IN ('BASE TABLE', 'VIEW') - AND t.TABLE_SCHEMA = '{conf.dbSchema}' - """ - elif ds.type == "pg" or ds.type == "excel": - sql = """ - SELECT c.relname AS TABLE_NAME, - COALESCE(d.description, obj_description(c.oid)) AS TABLE_COMMENT - FROM pg_class c - LEFT JOIN - pg_namespace n ON n.oid = c.relnamespace - LEFT JOIN - pg_description d ON d.objoid = c.oid AND d.objsubid = 0 - WHERE n.nspname = current_schema() - AND c.relkind IN ('r', 'v', 'p', 'm') - AND c.relname NOT LIKE 'pg_%' - AND c.relname NOT LIKE 'sql_%' - ORDER BY c.relname \ - """ - elif ds.type == "oracle": - sql = f""" - SELECT - t.TABLE_NAME AS "TABLE_NAME", - NVL(c.COMMENTS, '') AS "TABLE_COMMENT" - FROM ( - SELECT TABLE_NAME, 'TABLE' AS OBJECT_TYPE - FROM DBA_TABLES - WHERE OWNER = '{conf.dbSchema}' - UNION ALL - SELECT VIEW_NAME AS TABLE_NAME, 'VIEW' AS OBJECT_TYPE - FROM DBA_VIEWS - WHERE OWNER = '{conf.dbSchema}' - ) t - LEFT JOIN DBA_TAB_COMMENTS c - ON t.TABLE_NAME = c.TABLE_NAME - AND c.TABLE_TYPE = t.OBJECT_TYPE - AND c.OWNER = '{conf.dbSchema}' - ORDER BY t.TABLE_NAME - """ - elif ds.type == "ck": - sql = f""" - SELECT name, engine - FROM system.tables - WHERE database = '{conf.database}' - AND engine NOT IN ('Dictionary') - ORDER BY name - """ - with session.execute(text(sql)) as result: - res = result.fetchall() - res_list = [TableSchema(*item) for item in res] - return res_list + db = DB.get_db(ds.type) + if db.connect_type == ConnectType.sqlalchemy: + with get_session(ds) as session: + sql: str = '' + if ds.type == "mysql": + sql = f""" + SELECT + TABLE_NAME, + TABLE_COMMENT + FROM + information_schema.TABLES + WHERE + TABLE_SCHEMA = '{conf.database}' + """ + elif ds.type == "sqlServer": + sql = f""" + SELECT + TABLE_NAME AS [TABLE_NAME], + ISNULL(ep.value, '') AS [TABLE_COMMENT] + FROM + INFORMATION_SCHEMA.TABLES t + LEFT JOIN + sys.extended_properties ep + ON ep.major_id = OBJECT_ID(t.TABLE_SCHEMA + '.' + t.TABLE_NAME) + AND ep.minor_id = 0 + AND ep.name = 'MS_Description' + WHERE + t.TABLE_TYPE IN ('BASE TABLE', 'VIEW') + AND t.TABLE_SCHEMA = '{conf.dbSchema}' + """ + elif ds.type == "pg" or ds.type == "excel": + sql = """ + SELECT c.relname AS TABLE_NAME, + COALESCE(d.description, obj_description(c.oid)) AS TABLE_COMMENT + FROM pg_class c + LEFT JOIN + pg_namespace n ON n.oid = c.relnamespace + LEFT JOIN + pg_description d ON d.objoid = c.oid AND d.objsubid = 0 + WHERE n.nspname = current_schema() + AND c.relkind IN ('r', 'v', 'p', 'm') + AND c.relname NOT LIKE 'pg_%' + AND c.relname NOT LIKE 'sql_%' + ORDER BY c.relname \ + """ + elif ds.type == "oracle": + sql = f""" + SELECT + t.TABLE_NAME AS "TABLE_NAME", + NVL(c.COMMENTS, '') AS "TABLE_COMMENT" + FROM ( + SELECT TABLE_NAME, 'TABLE' AS OBJECT_TYPE + FROM DBA_TABLES + WHERE OWNER = '{conf.dbSchema}' + UNION ALL + SELECT VIEW_NAME AS TABLE_NAME, 'VIEW' AS OBJECT_TYPE + FROM DBA_VIEWS + WHERE OWNER = '{conf.dbSchema}' + ) t + LEFT JOIN DBA_TAB_COMMENTS c + ON t.TABLE_NAME = c.TABLE_NAME + AND c.TABLE_TYPE = t.OBJECT_TYPE + AND c.OWNER = '{conf.dbSchema}' + ORDER BY t.TABLE_NAME + """ + elif ds.type == "ck": + sql = f""" + SELECT name, comment + FROM system.tables + WHERE database = '{conf.database}' + AND engine NOT IN ('Dictionary') + ORDER BY name + """ + with session.execute(text(sql)) as result: + res = result.fetchall() + res_list = [TableSchema(*item) for item in res] + return res_list + else: + if ds.type == 'dm': + with dmPython.connect(user=conf.username, password=conf.password, server=conf.host, + port=conf.port) as conn, conn.cursor() as cursor: + cursor.execute(f"""select table_name, comments + from all_tab_comments + where owner='{conf.dbSchema}' + AND (table_type = 'TABLE' or table_type = 'VIEW') + """) + res = cursor.fetchall() + res_list = [TableSchema(*item) for item in res] + return res_list def get_fields(ds: CoreDatasource, table_name: str = None): conf = DatasourceConf(**json.loads(aes_decrypt(ds.configuration))) if ds.type != "excel" else get_engine_config() - with get_session(ds) as session: - sql: str = '' - if ds.type == "mysql": - sql1 = f""" - SELECT - COLUMN_NAME, - DATA_TYPE, - COLUMN_COMMENT - FROM - INFORMATION_SCHEMA.COLUMNS - WHERE - TABLE_SCHEMA = '{conf.database}' - """ - sql2 = f" AND TABLE_NAME = '{table_name}'" if table_name is not None and table_name != "" else "" - sql = sql1 + sql2 - elif ds.type == "sqlServer": - sql1 = f""" - SELECT - COLUMN_NAME AS [COLUMN_NAME], - DATA_TYPE AS [DATA_TYPE], - ISNULL(EP.value, '') AS [COLUMN_COMMENT] - FROM - INFORMATION_SCHEMA.COLUMNS C - LEFT JOIN - sys.extended_properties EP - ON EP.major_id = OBJECT_ID(C.TABLE_SCHEMA + '.' + C.TABLE_NAME) - AND EP.minor_id = C.ORDINAL_POSITION - AND EP.name = 'MS_Description' - WHERE - C.TABLE_SCHEMA = '{conf.dbSchema}' - """ - sql2 = f" AND C.TABLE_NAME = '{table_name}'" if table_name is not None and table_name != "" else "" - sql = sql1 + sql2 - elif ds.type == "pg" or ds.type == "excel": - sql1 = """ - SELECT a.attname AS COLUMN_NAME, - pg_catalog.format_type(a.atttypid, a.atttypmod) AS DATA_TYPE, - col_description(c.oid, a.attnum) AS COLUMN_COMMENT - FROM pg_catalog.pg_attribute a - JOIN - pg_catalog.pg_class c ON a.attrelid = c.oid - JOIN - pg_catalog.pg_namespace n ON n.oid = c.relnamespace - WHERE n.nspname = current_schema() - AND a.attnum > 0 - AND NOT a.attisdropped \ - """ - sql2 = f" AND c.relname = '{table_name}'" if table_name is not None and table_name != "" else "" - sql = sql1 + sql2 - elif ds.type == "oracle": - sql1 = f""" - SELECT - col.COLUMN_NAME AS "COLUMN_NAME", - (CASE - WHEN col.DATA_TYPE IN ('VARCHAR2', 'CHAR', 'NVARCHAR2', 'NCHAR') - THEN col.DATA_TYPE || '(' || col.DATA_LENGTH || ')' - WHEN col.DATA_TYPE = 'NUMBER' AND col.DATA_PRECISION IS NOT NULL - THEN col.DATA_TYPE || '(' || col.DATA_PRECISION || - CASE WHEN col.DATA_SCALE > 0 THEN ',' || col.DATA_SCALE END || ')' - ELSE col.DATA_TYPE - END) AS "DATA_TYPE", - NVL(com.COMMENTS, '') AS "COLUMN_COMMENT" - FROM - DBA_TAB_COLUMNS col - LEFT JOIN - DBA_COL_COMMENTS com - ON col.OWNER = com.OWNER - AND col.TABLE_NAME = com.TABLE_NAME - AND col.COLUMN_NAME = com.COLUMN_NAME - WHERE - col.OWNER = '{conf.dbSchema}' - """ - sql2 = f" AND col.TABLE_NAME = '{table_name}'" if table_name is not None and table_name != "" else "" - sql = sql1 + sql2 - elif ds.type == "ck": - sql1 = f""" - SELECT - name AS COLUMN_NAME, - type AS DATA_TYPE, - comment AS COLUMN_COMMENT - FROM system.columns - WHERE database = '{conf.database}' - """ - sql2 = f" AND table = '{table_name}'" if table_name is not None and table_name != "" else "" - sql = sql1 + sql2 + db = DB.get_db(ds.type) + if db.connect_type == ConnectType.sqlalchemy: + with get_session(ds) as session: + sql: str = '' + if ds.type == "mysql": + sql1 = f""" + SELECT + COLUMN_NAME, + DATA_TYPE, + COLUMN_COMMENT + FROM + INFORMATION_SCHEMA.COLUMNS + WHERE + TABLE_SCHEMA = '{conf.database}' + """ + sql2 = f" AND TABLE_NAME = '{table_name}'" if table_name is not None and table_name != "" else "" + sql = sql1 + sql2 + elif ds.type == "sqlServer": + sql1 = f""" + SELECT + COLUMN_NAME AS [COLUMN_NAME], + DATA_TYPE AS [DATA_TYPE], + ISNULL(EP.value, '') AS [COLUMN_COMMENT] + FROM + INFORMATION_SCHEMA.COLUMNS C + LEFT JOIN + sys.extended_properties EP + ON EP.major_id = OBJECT_ID(C.TABLE_SCHEMA + '.' + C.TABLE_NAME) + AND EP.minor_id = C.ORDINAL_POSITION + AND EP.name = 'MS_Description' + WHERE + C.TABLE_SCHEMA = '{conf.dbSchema}' + """ + sql2 = f" AND C.TABLE_NAME = '{table_name}'" if table_name is not None and table_name != "" else "" + sql = sql1 + sql2 + elif ds.type == "pg" or ds.type == "excel": + sql1 = """ + SELECT a.attname AS COLUMN_NAME, + pg_catalog.format_type(a.atttypid, a.atttypmod) AS DATA_TYPE, + col_description(c.oid, a.attnum) AS COLUMN_COMMENT + FROM pg_catalog.pg_attribute a + JOIN + pg_catalog.pg_class c ON a.attrelid = c.oid + JOIN + pg_catalog.pg_namespace n ON n.oid = c.relnamespace + WHERE n.nspname = current_schema() + AND a.attnum > 0 + AND NOT a.attisdropped \ + """ + sql2 = f" AND c.relname = '{table_name}'" if table_name is not None and table_name != "" else "" + sql = sql1 + sql2 + elif ds.type == "oracle": + sql1 = f""" + SELECT + col.COLUMN_NAME AS "COLUMN_NAME", + (CASE + WHEN col.DATA_TYPE IN ('VARCHAR2', 'CHAR', 'NVARCHAR2', 'NCHAR') + THEN col.DATA_TYPE || '(' || col.DATA_LENGTH || ')' + WHEN col.DATA_TYPE = 'NUMBER' AND col.DATA_PRECISION IS NOT NULL + THEN col.DATA_TYPE || '(' || col.DATA_PRECISION || + CASE WHEN col.DATA_SCALE > 0 THEN ',' || col.DATA_SCALE END || ')' + ELSE col.DATA_TYPE + END) AS "DATA_TYPE", + NVL(com.COMMENTS, '') AS "COLUMN_COMMENT" + FROM + DBA_TAB_COLUMNS col + LEFT JOIN + DBA_COL_COMMENTS com + ON col.OWNER = com.OWNER + AND col.TABLE_NAME = com.TABLE_NAME + AND col.COLUMN_NAME = com.COLUMN_NAME + WHERE + col.OWNER = '{conf.dbSchema}' + """ + sql2 = f" AND col.TABLE_NAME = '{table_name}'" if table_name is not None and table_name != "" else "" + sql = sql1 + sql2 + elif ds.type == "ck": + sql1 = f""" + SELECT + name AS COLUMN_NAME, + type AS DATA_TYPE, + comment AS COLUMN_COMMENT + FROM system.columns + WHERE database = '{conf.database}' + """ + sql2 = f" AND table = '{table_name}'" if table_name is not None and table_name != "" else "" + sql = sql1 + sql2 - with session.execute(text(sql)) as result: - res = result.fetchall() - res_list = [ColumnSchema(*item) for item in res] - return res_list - - -def exec_sql(ds: CoreDatasource | AssistantOutDsSchema, sql: str): - with get_session(ds) as session: - with session.execute(text(sql)) as result: - try: - columns = [item.lower() for item in result.keys()._keys] + with session.execute(text(sql)) as result: res = result.fetchall() - result_list = [ - {str(columns[i]): float(value) if isinstance(value, Decimal) else value for i, value in - enumerate(tuple_item)} - for tuple_item in res - ] - return {"fields": columns, "data": result_list, - "sql": bytes.decode(base64.b64encode(bytes(sql, 'utf-8')))} - except Exception as ex: - raise ex + res_list = [ColumnSchema(*item) for item in res] + return res_list + else: + if ds.type == 'dm': + with dmPython.connect(user=conf.username, password=conf.password, server=conf.host, + port=conf.port) as conn, conn.cursor() as cursor: + sql1 = f""" + SELECT + c.COLUMN_NAME AS "COLUMN_NAME", + c.DATA_TYPE AS "DATA_TYPE", + COALESCE(com.COMMENTS, '') AS "COMMENTS" + FROM + ALL_TAB_COLS c + LEFT JOIN + ALL_COL_COMMENTS com + ON c.OWNER = com.OWNER + AND c.TABLE_NAME = com.TABLE_NAME + AND c.COLUMN_NAME = com.COLUMN_NAME + WHERE + c.OWNER = '{conf.dbSchema}' + """ + sql2 = f" AND c.TABLE_NAME = '{table_name}'" if table_name is not None and table_name != "" else "" + cursor.execute(sql1 + sql2) + res = cursor.fetchall() + res_list = [ColumnSchema(*item) for item in res] + return res_list + + +def exec_sql(ds: CoreDatasource | AssistantOutDsSchema, sql: str, origin_column=False): + db = DB.get_db(ds.type) + if db.connect_type == ConnectType.sqlalchemy: + with get_session(ds) as session: + with session.execute(text(sql)) as result: + try: + columns = result.keys()._keys if origin_column else [item.lower() for item in result.keys()._keys] + res = result.fetchall() + result_list = [ + {str(columns[i]): float(value) if isinstance(value, Decimal) else value for i, value in + enumerate(tuple_item)} + for tuple_item in res + ] + return {"fields": columns, "data": result_list, + "sql": bytes.decode(base64.b64encode(bytes(sql, 'utf-8')))} + except Exception as ex: + raise ex + else: + conf = DatasourceConf(**json.loads(aes_decrypt(ds.configuration))) + if ds.type == 'dm': + with dmPython.connect(user=conf.username, password=conf.password, server=conf.host, + port=conf.port) as conn, conn.cursor() as cursor: + try: + cursor.execute(sql) + res = cursor.fetchall() + columns = [field[0] for field in cursor.description] if origin_column else [field[0].lower() for + field in + cursor.description] + result_list = [ + {str(columns[i]): float(value) if isinstance(value, Decimal) else value for i, value in + enumerate(tuple_item)} + for tuple_item in res + ] + return {"fields": columns, "data": result_list, + "sql": bytes.decode(base64.b64encode(bytes(sql, 'utf-8')))} + except Exception as ex: + raise ex diff --git a/backend/apps/db/type.py b/backend/apps/db/type.py index e7726ba2..d106a437 100644 --- a/backend/apps/db/type.py +++ b/backend/apps/db/type.py @@ -10,5 +10,6 @@ def db_type_relation() -> Dict: "pg": "PostgreSQL", "excel": "Excel/CSV", "oracle": "Oracle", - "ck": "ClickHouse" + "ck": "ClickHouse", + "dm": "达梦" } diff --git a/backend/pyproject.toml b/backend/pyproject.toml index efed0b3c..bbdf7b9d 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -44,6 +44,7 @@ dependencies = [ "python-calamine>=0.4.0", "xlrd>=2.0.2", "clickhouse-sqlalchemy>=0.3.2", + "dmpython>=2.5.22", ] [[tool.uv.index]] name = "default" diff --git a/backend/template.yaml b/backend/template.yaml index 2b766c3a..b6df4e35 100644 --- a/backend/template.yaml +++ b/backend/template.yaml @@ -27,7 +27,7 @@ template: - SQL查询的字段若是函数字段,如 COUNT(),CAST() 等,必须加上别名 - 计算占比,百分比类型字段,保留两位小数,以%结尾。 - 生成SQL时,必须避免关键字冲突。 - - 如数据库引擎是 PostgreSQL、Oracle、ClickHouse,则在schema、表名、字段名、别名外层加双引号; + - 如数据库引擎是 PostgreSQL、Oracle、ClickHouse、达梦(DM),则在schema、表名、字段名、别名外层加双引号; - 如数据库引擎是 MySQL,则在表名、字段名、别名外层加反引号; - 如数据库引擎是 Microsoft SQL Server,则在schema、表名、字段名、别名外层加方括号。 - 以PostgreSQL为例,查询Schema为TEST表TABLE下所有数据,则生成的SQL为: @@ -208,7 +208,7 @@ template: - 如果存在冗余的过滤条件则进行去重后再生成新SQL。 - 给过滤条件中的字段前加上表别名(如果没有表别名则加表名),如:table.field。 - 生成SQL时,必须避免关键字冲突: - - 如数据库引擎是 PostgreSQL、Oracle,则在schema、表名、字段名、别名外层加双引号; + - 如数据库引擎是 PostgreSQL、Oracle、ClickHouse、达梦(DM),则在schema、表名、字段名、别名外层加双引号; - 如数据库引擎是 MySQL,则在表名、字段名、别名外层加反引号; - 如数据库引擎是 Microsoft SQL Server,则在schema、表名、字段名、别名外层加方括号。 - 生成的SQL使用JSON格式返回: diff --git a/frontend/src/assets/datasource/icon_dm.png b/frontend/src/assets/datasource/icon_dm.png new file mode 100644 index 0000000000000000000000000000000000000000..ada1d7fa9e4685ebc4477301f50871d28d6c919e GIT binary patch literal 1382 zcmV-s1)2JZP)X1^@s6P!BAT00006VoOIv0RI60 z0RN!9r;`8x1sh33K~zY`wN`Cx6jc;m1gsbor7hd{?%T4vU3T73Z6q6l@t1%gUjiba zF)^r!lr#_vK@=^_&g`~;6~l+3XiC(ehz27XLi7jzs;C5|3DBzD&u#@)t4#=^P}cKy zO2P_s*@BbY%*@X0oO92;_q^wD+=%0TW6(%_HRGe}#&A72iEF`Gq_3GSM%Rs_DnmHt z?H=g3(Z``cU~nMt)?lGoD@ik(NwYdgny0xm&vDH>ug;_e{~~F1;@JV0WAkrGm5^&X#7JjL40tgvu>9a924#$8FqWN*gBOtK=;6DR66XpD=1c~;D zP3CRVttI^;pqbr$He{FAJ1kBjiA;N9j6IC=FG_(ZN`d+xQ3P<`=JlInF#&%UN`Ps> zZnxXG<%;0kAAEGTH&71HvkC|$Sb(g*&0ce{3Ce8ClHqjzF!|<=kl~#q!`GL(mqx#=?SGJkL?M&tyHc3m zmB6*MKJ)oD7v&HTG8DwaOWe$Ao#|OT=}%T%pW^Ja${g_xh`byV;+Y7eP!j!+;9?KX z5&Yhg6kahz{O z<45K9F8Ym?hPqhk{!`5PU5fY*D97OhckNAbz7nN+0=srKyab(ThmC^EAV$PdUssbd`z=GzIw-6%C$0Onsv5R3w4&r z@KN-~*Hg8cRWPMBDpM%X6?L%(p+Ku`R^kO*f06ASM%&!daN|3>JbGy4mFHeljgr^7 zYzaYzDP?5nr&j@pHx;FH$zF!Rb3AsPxiyhGfq8R zi;QedR(+(5>SDHAe%{V#xhes`zk&iQ z5Vg@#cUfKnB!l&~a}`E_=~gWyULEZvpcDXsa~Mk_=5%_Rg&1;8OD?<1($OT5!bq4s zqz9+RIp1_5YfNH~y=Kso2xB7)-Rum&o^3AXwKczwo7wu~{WC(hMgj0$m3@lyStmUn z=XLG%Fpg6I@H*lc6gjI_IFc6^7ZuqR5BoYZ4w5^B*f<3Bp3updzT4rrF}q$NjNur! z$y~Q;;ErMLH@9GC`zDs?&FWrgp=I1}%@&mnKF9xm%p2(NEiKA!95Bn-`+Nu9E3Gt! o-sxR6tVCOXTkoc0Kwp3P7hErUS|^)#jQ{`u07*qoM6N<$f - +