Skip to content

Commit e50b71a

Browse files
committed
feat(datasource): Support Mssql
1 parent 27db349 commit e50b71a

File tree

6 files changed

+94
-25
lines changed

6 files changed

+94
-25
lines changed

backend/apps/datasource/crud/datasource.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,9 @@ def sync_table(session: SessionDep, ds: CoreDatasource, tables: List[CoreTable])
132132
if len(id_list) > 0:
133133
session.query(CoreTable).filter(and_(CoreTable.ds_id == ds.id, CoreTable.id.not_in(id_list))).delete(
134134
synchronize_session=False)
135+
session.query(CoreField).filter(and_(CoreField.ds_id == ds.id, CoreField.table_id.not_in(id_list))).delete(
136+
synchronize_session=False)
137+
session.commit()
135138

136139

137140
def sync_fields(session: SessionDep, ds: CoreDatasource, table: CoreTable, fields: List[ColumnSchema]):
@@ -161,6 +164,7 @@ def sync_fields(session: SessionDep, ds: CoreDatasource, table: CoreTable, field
161164
if len(id_list) > 0:
162165
session.query(CoreField).filter(and_(CoreField.table_id == table.id, CoreField.id.not_in(id_list))).delete(
163166
synchronize_session=False)
167+
session.commit()
164168

165169

166170
def update_table_and_fields(session: SessionDep, data: EditObj):
@@ -174,4 +178,10 @@ def preview(session: SessionDep, id: int, data: EditObj):
174178
sql: str = ""
175179
if ds.type == "mysql":
176180
sql = f"""SELECT {", ".join([f.field_name for f in data.fields if f.checked])} FROM {data.table.table_name} LIMIT 100"""
181+
elif ds.type == "sqlServer":
182+
sql = f"""
183+
SELECT {", ".join([f.field_name for f in data.fields if f.checked])} FROM {data.table.table_name}
184+
ORDER BY {data.fields[0].field_name}
185+
OFFSET 0 ROWS FETCH NEXT 100 ROWS ONLY
186+
"""
177187
return exec_sql(ds, sql)

backend/apps/datasource/models/datasource.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ class DatasourceConf(BaseModel):
6767
database: str = ''
6868
driver: str = ''
6969
extraJdbc: str = ''
70+
dbSchema: str = ''
7071

7172

7273
class TableSchema:

backend/apps/db/db.py

Lines changed: 63 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ def get_uri(ds: CoreDatasource):
1717
db_url = f"mysql+pymysql://{urllib.parse.quote(conf.username)}:{urllib.parse.quote(conf.password)}@{conf.host}:{conf.port}/{urllib.parse.quote(conf.database)}?{urllib.parse.quote(conf.extraJdbc)}"
1818
else:
1919
db_url = f"mysql+pymysql://{urllib.parse.quote(conf.username)}:{urllib.parse.quote(conf.password)}@{conf.host}:{conf.port}/{urllib.parse.quote(conf.database)}"
20+
elif ds.type == "sqlServer":
21+
if conf.extraJdbc is not None and conf.extraJdbc != '':
22+
db_url = f"mssql+pymssql://{urllib.parse.quote(conf.username)}:{urllib.parse.quote(conf.password)}@{conf.host}:{conf.port}/{urllib.parse.quote(conf.database)}?{urllib.parse.quote(conf.extraJdbc)}"
23+
else:
24+
db_url = f"mssql+pymssql://{urllib.parse.quote(conf.username)}:{urllib.parse.quote(conf.password)}@{conf.host}:{conf.port}/{urllib.parse.quote(conf.database)}"
2025
else:
2126
raise 'The datasource type not support.'
2227
return db_url
@@ -33,17 +38,36 @@ def get_tables(ds: CoreDatasource):
3338
conf = DatasourceConf(**json.loads(aes_decrypt(ds.configuration)))
3439
session = get_session(ds)
3540
result: Result[Any]
41+
sql: str = ''
3642
try:
3743
if ds.type == "mysql":
38-
sql = f"""SELECT
39-
TABLE_NAME,
40-
TABLE_COMMENT
41-
FROM
42-
information_schema.TABLES
43-
WHERE
44-
TABLE_SCHEMA = '{conf.database}';"""
45-
result = session.execute(text(sql))
44+
sql = f"""
45+
SELECT
46+
TABLE_NAME,
47+
TABLE_COMMENT
48+
FROM
49+
information_schema.TABLES
50+
WHERE
51+
TABLE_SCHEMA = '{conf.database}';
52+
"""
53+
elif ds.type == "sqlServer":
54+
sql = f"""
55+
SELECT
56+
TABLE_NAME AS [TABLE_NAME],
57+
ISNULL(ep.value, '') AS [TABLE_COMMENT]
58+
FROM
59+
INFORMATION_SCHEMA.TABLES t
60+
LEFT JOIN
61+
sys.extended_properties ep
62+
ON ep.major_id = OBJECT_ID(t.TABLE_SCHEMA + '.' + t.TABLE_NAME)
63+
AND ep.minor_id = 0
64+
AND ep.name = 'MS_Description'
65+
WHERE
66+
t.TABLE_TYPE IN ('BASE TABLE', 'VIEW')
67+
AND t.TABLE_SCHEMA = '{conf.dbSchema}';
68+
"""
4669

70+
result = session.execute(text(sql))
4771
res = result.fetchall()
4872
res_list = [TableSchema(*item) for item in res]
4973
return res_list
@@ -58,20 +82,41 @@ def get_fields(ds: CoreDatasource, table_name: str = None):
5882
conf = DatasourceConf(**json.loads(aes_decrypt(ds.configuration)))
5983
session = get_session(ds)
6084
result: Result[Any]
85+
sql: str = ''
6186
try:
6287
if ds.type == "mysql":
63-
sql1 = f"""SELECT
64-
COLUMN_NAME,
65-
DATA_TYPE,
66-
COLUMN_COMMENT
67-
FROM
68-
INFORMATION_SCHEMA.COLUMNS
69-
WHERE
70-
TABLE_SCHEMA = '{conf.database}'"""
71-
sql2 = f""" AND TABLE_NAME = '{table_name}';""" if table_name is not None and table_name != "" else ";"
88+
sql1 = f"""
89+
SELECT
90+
COLUMN_NAME,
91+
DATA_TYPE,
92+
COLUMN_COMMENT
93+
FROM
94+
INFORMATION_SCHEMA.COLUMNS
95+
WHERE
96+
TABLE_SCHEMA = '{conf.database}'
97+
"""
98+
sql2 = f" AND TABLE_NAME = '{table_name}';" if table_name is not None and table_name != "" else ";"
99+
sql = sql1 + sql2
100+
elif ds.type == "sqlServer":
101+
sql1 = f"""
102+
SELECT
103+
COLUMN_NAME AS [COLUMN_NAME],
104+
DATA_TYPE AS [DATA_TYPE],
105+
ISNULL(EP.value, '') AS [COLUMN_COMMENT]
106+
FROM
107+
INFORMATION_SCHEMA.COLUMNS C
108+
LEFT JOIN
109+
sys.extended_properties EP
110+
ON EP.major_id = OBJECT_ID(C.TABLE_SCHEMA + '.' + C.TABLE_NAME)
111+
AND EP.minor_id = C.ORDINAL_POSITION
112+
AND EP.name = 'MS_Description'
113+
WHERE
114+
C.TABLE_SCHEMA = '{conf.dbSchema}'
115+
"""
116+
sql2 = f"AND C.TABLE_NAME = '{table_name}';" if table_name is not None and table_name != "" else ";"
72117
sql = sql1 + sql2
73-
result = session.execute(text(sql))
74118

119+
result = session.execute(text(sql))
75120
res = result.fetchall()
76121
res_list = [ColumnSchema(*item) for item in res]
77122
return res_list

backend/pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ dependencies = [
2828
"dashscope>=1.14.0,<2.0.0",
2929
"pymysql (>=1.1.1,<2.0.0)",
3030
"cryptography (>=44.0.3,<45.0.0)",
31-
"llama_index>=0.12.35"
31+
"llama_index>=0.12.35",
32+
"pymssql (>=2.3.4,<3.0.0)"
3233
]
3334
[[tool.uv.index]]
3435
url = "https://pypi.tuna.tsinghua.edu.cn/simple"

frontend/src/views/ds/form.vue

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
<el-input v-model="form.description" :rows="2" type="textarea" />
2121
</el-form-item>
2222
<el-form-item label="Type">
23-
<el-select v-model="form.type" placeholder="Select Type">
23+
<el-select v-model="form.type" placeholder="Select Type" :disabled="!isCreate">
2424
<el-option
2525
v-for="item in dsType"
2626
:key="item.value"
@@ -47,10 +47,14 @@
4747
<el-form-item label="Extra JDBC String">
4848
<el-input v-model="config.extraJdbc" />
4949
</el-form-item>
50+
<el-form-item label="Schema" v-if="form.type === 'sqlServer'">
51+
<el-input v-model="config.dbSchema" />
52+
<el-button link type="primary" :icon="Plus" v-if="false">Get Schema</el-button>
53+
</el-form-item>
5054
</el-form>
5155
</div>
5256
<div v-show="active === 2" class="container">
53-
<el-scrollbar height="480px">
57+
<el-scrollbar>
5458
<el-checkbox-group v-model="checkList">
5559
<el-row :gutter="10">
5660
<el-col v-for="item in tableList" :key="item.value" :span="12">
@@ -76,6 +80,7 @@ import { datasourceApi } from '@/api/datasource'
7680
import { encrypted, decrypted } from './js/aes'
7781
import { ElMessage } from 'element-plus'
7882
import type { FormInstance, FormRules } from 'element-plus'
83+
import { Plus } from '@element-plus/icons-vue'
7984
8085
const dsFormRef = ref<FormInstance>()
8186
const emit = defineEmits(['refresh'])
@@ -94,7 +99,8 @@ const rules = reactive<FormRules>({
9499
95100
const dialogVisible = ref<boolean>(false)
96101
const dsType = [
97-
{label:"MySQL", value:"mysql"}
102+
{label:"MySQL", value:"mysql"},
103+
{label:"SQL Server", value:"sqlServer"}
98104
]
99105
const form = ref<any>({
100106
name:'',
@@ -109,7 +115,8 @@ const config = ref<any>({
109115
username:'',
110116
password:'',
111117
database:'',
112-
extraJdbc:''
118+
extraJdbc:'',
119+
dbSchema:''
113120
})
114121
115122
const close = () => {
@@ -138,6 +145,7 @@ const open = (item: any, editTable: boolean = false) => {
138145
config.value.password = configuration.password
139146
config.value.database = configuration.database
140147
config.value.extraJdbc = configuration.extraJdbc
148+
config.value.dbSchema = configuration.dbSchema
141149
}
142150
143151
if (editTable) {
@@ -171,6 +179,7 @@ const open = (item: any, editTable: boolean = false) => {
171179
password:'',
172180
database:'',
173181
extraJdbc:'',
182+
dbSchema:''
174183
}
175184
}
176185
dialogVisible.value = true
@@ -218,7 +227,8 @@ const buildConf = () => {
218227
username:config.value.username,
219228
password:config.value.password,
220229
database:config.value.database,
221-
extraJdbc:config.value.extraJdbc
230+
extraJdbc:config.value.extraJdbc,
231+
dbSchema:config.value.dbSchema
222232
}))
223233
}
224234
@@ -262,7 +272,7 @@ defineExpose({ open })
262272
</script>
263273
<style lang="less" scoped>
264274
.container{
265-
height: 500px;
275+
height: 600px;
266276
margin-top: 20px;
267277
}
268278
</style>

frontend/src/views/ds/index.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
<div class="connection-icon">
2828
<Icon>
2929
<MysqlDs v-if="ds.type === 'mysql'"/>
30+
<SQLServerDs v-else-if="ds.type === 'sqlServer'"/>
3031
</Icon>
3132
</div>
3233
<div class="connection-details">
@@ -52,6 +53,7 @@ import IconOpeAdd from '@/assets/svg/operate/ope-add.svg'
5253
import IconOpeEdit from '@/assets/svg/operate/ope-edit.svg'
5354
import IconOpeDelete from '@/assets/svg/operate/ope-delete.svg'
5455
import MysqlDs from '@/assets/svg/ds/mysql-ds.svg'
56+
import SQLServerDs from '@/assets/svg/ds/sqlServer-ds.svg'
5557
import { Search, List, CreditCard } from '@element-plus/icons-vue'
5658
import { ref, onMounted } from 'vue'
5759
import DsForm from './form.vue'

0 commit comments

Comments
 (0)