Skip to content

Commit 6529442

Browse files
committed
feat(datasource): Support oracle
1 parent 44262a3 commit 6529442

File tree

9 files changed

+142
-26
lines changed

9 files changed

+142
-26
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
"""006_add_ds_field
2+
3+
Revision ID: e6276ddab06e
4+
Revises: 0a6f11be9be4
5+
Create Date: 2025-05-22 11:43:40.176878
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
import sqlmodel.sql.sqltypes
11+
from sqlalchemy.dialects import postgresql
12+
13+
# revision identifiers, used by Alembic.
14+
revision = 'e6276ddab06e'
15+
down_revision = '0a6f11be9be4'
16+
branch_labels = None
17+
depends_on = None
18+
19+
20+
def upgrade():
21+
# ### commands auto generated by Alembic - please adjust! ###
22+
op.add_column('core_datasource', sa.Column('type_name', sqlmodel.sql.sqltypes.AutoString(length=64), nullable=True))
23+
# ### end Alembic commands ###
24+
25+
26+
def downgrade():
27+
# ### commands auto generated by Alembic - please adjust! ###
28+
op.drop_column('core_datasource', 'type_name')
29+
# ### end Alembic commands ###

backend/apps/datasource/crud/datasource.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from ..crud.table import delete_table_by_ds_id, update_table
1616
from ..models.datasource import CoreDatasource, CreateDatasource, CoreTable, CoreField, ColumnSchema, EditObj, \
1717
DatasourceConf
18+
from apps.db.type import db_type_relation
1819

1920

2021
def get_datasource_list(session: SessionDep):
@@ -41,6 +42,7 @@ def create_ds(session: SessionDep, user: CurrentUser, create_ds: CreateDatasourc
4142
# status = check_status(session, ds)
4243
ds.create_by = user.id
4344
ds.status = "Success"
45+
ds.type_name = db_type_relation()[ds.type]
4446
record = CoreDatasource(**ds.model_dump())
4547
session.add(record)
4648
session.flush()
@@ -202,4 +204,8 @@ def preview(session: SessionDep, id: int, data: EditObj):
202204
OFFSET 0 ROWS FETCH NEXT 100 ROWS ONLY"""
203205
elif ds.type == "pg" or ds.type == "excel":
204206
sql = f"""SELECT "{'", "'.join([f.field_name for f in data.fields if f.checked])}" FROM "{conf.dbSchema}"."{data.table.table_name}" LIMIT 100"""
207+
elif ds.type == "oracle":
208+
sql = f"""SELECT "{'", "'.join([f.field_name for f in data.fields if f.checked])}" FROM "{conf.dbSchema}"."{data.table.table_name}"
209+
ORDER BY "{data.fields[0].field_name}"
210+
OFFSET 0 ROWS FETCH NEXT 100 ROWS ONLY"""
205211
return exec_sql(ds, sql)

backend/apps/datasource/models/datasource.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class CoreDatasource(SQLModel, table=True):
1212
name: str = Field(max_length=128, nullable=False)
1313
description: str = Field(max_length=512, nullable=True)
1414
type: str = Field(max_length=64)
15+
type_name: str = Field(max_length=64, nullable=True)
1516
configuration: str = Field(sa_column=Column(Text))
1617
create_time: datetime = Field(sa_column=Column(DateTime(timezone=True), nullable=True))
1718
create_by: int = Field(sa_column=Column(BigInteger()))
@@ -71,6 +72,7 @@ class DatasourceConf(BaseModel):
7172
dbSchema: str = ''
7273
filename: str = ''
7374
sheets: List = ''
75+
mode: str = ''
7476

7577
def to_dict(self):
7678
return {
@@ -83,7 +85,8 @@ def to_dict(self):
8385
"extraJdbc": self.extraJdbc,
8486
"dbSchema": self.dbSchema,
8587
"filename": self.filename,
86-
"sheets": self.sheets
88+
"sheets": self.sheets,
89+
"mode": self.mode
8790
}
8891

8992

backend/apps/db/db.py

Lines changed: 65 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,30 @@ def get_uri(ds: CoreDatasource):
1616
db_url: str
1717
if ds.type == "mysql":
1818
if conf.extraJdbc is not None and conf.extraJdbc != '':
19-
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)}"
19+
db_url = f"mysql+pymysql://{urllib.parse.quote(conf.username)}:{urllib.parse.quote(conf.password)}@{conf.host}:{conf.port}/{urllib.parse.quote(conf.database)}?{conf.extraJdbc}"
2020
else:
2121
db_url = f"mysql+pymysql://{urllib.parse.quote(conf.username)}:{urllib.parse.quote(conf.password)}@{conf.host}:{conf.port}/{urllib.parse.quote(conf.database)}"
2222
elif ds.type == "sqlServer":
2323
if conf.extraJdbc is not None and conf.extraJdbc != '':
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)}?{urllib.parse.quote(conf.extraJdbc)}"
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)}?{conf.extraJdbc}"
2525
else:
2626
db_url = f"mssql+pymssql://{urllib.parse.quote(conf.username)}:{urllib.parse.quote(conf.password)}@{conf.host}:{conf.port}/{urllib.parse.quote(conf.database)}"
2727
elif ds.type == "pg" or ds.type == "excel":
2828
if conf.extraJdbc is not None and conf.extraJdbc != '':
29-
db_url = f"postgresql+psycopg2://{urllib.parse.quote(conf.username)}:{urllib.parse.quote(conf.password)}@{conf.host}:{conf.port}/{urllib.parse.quote(conf.database)}?{urllib.parse.quote(conf.extraJdbc)}"
29+
db_url = f"postgresql+psycopg2://{urllib.parse.quote(conf.username)}:{urllib.parse.quote(conf.password)}@{conf.host}:{conf.port}/{urllib.parse.quote(conf.database)}?{conf.extraJdbc}"
3030
else:
3131
db_url = f"postgresql+psycopg2://{urllib.parse.quote(conf.username)}:{urllib.parse.quote(conf.password)}@{conf.host}:{conf.port}/{urllib.parse.quote(conf.database)}"
32+
elif ds.type == "oracle":
33+
if conf.mode == "service_name":
34+
if conf.extraJdbc is not None and conf.extraJdbc != '':
35+
db_url = f"oracle+oracledb://{urllib.parse.quote(conf.username)}:{urllib.parse.quote(conf.password)}@{conf.host}:{conf.port}?service_name={urllib.parse.quote(conf.database)}&{conf.extraJdbc}"
36+
else:
37+
db_url = f"oracle+oracledb://{urllib.parse.quote(conf.username)}:{urllib.parse.quote(conf.password)}@{conf.host}:{conf.port}?service_name={urllib.parse.quote(conf.database)}"
38+
else:
39+
if conf.extraJdbc is not None and conf.extraJdbc != '':
40+
db_url = f"oracle+oracledb://{urllib.parse.quote(conf.username)}:{urllib.parse.quote(conf.password)}@{conf.host}:{conf.port}/{urllib.parse.quote(conf.database)}?{conf.extraJdbc}"
41+
else:
42+
db_url = f"oracle+oracledb://{urllib.parse.quote(conf.username)}:{urllib.parse.quote(conf.password)}@{conf.host}:{conf.port}/{urllib.parse.quote(conf.database)}"
3243
else:
3344
raise 'The datasource type not support.'
3445
return db_url
@@ -65,7 +76,7 @@ def get_tables(ds: CoreDatasource):
6576
FROM
6677
information_schema.TABLES
6778
WHERE
68-
TABLE_SCHEMA = '{conf.database}';
79+
TABLE_SCHEMA = '{conf.database}'
6980
"""
7081
elif ds.type == "sqlServer":
7182
sql = f"""
@@ -81,7 +92,7 @@ def get_tables(ds: CoreDatasource):
8192
AND ep.name = 'MS_Description'
8293
WHERE
8394
t.TABLE_TYPE IN ('BASE TABLE', 'VIEW')
84-
AND t.TABLE_SCHEMA = '{conf.dbSchema}';
95+
AND t.TABLE_SCHEMA = '{conf.dbSchema}'
8596
"""
8697
elif ds.type == "pg" or ds.type == "excel":
8798
sql = """
@@ -99,7 +110,27 @@ def get_tables(ds: CoreDatasource):
99110
AND c.relkind IN ('r', 'v')
100111
AND c.relname NOT LIKE 'pg_%'
101112
AND c.relname NOT LIKE 'sql_%'
102-
ORDER BY c.relname;
113+
ORDER BY c.relname
114+
"""
115+
elif ds.type == "oracle":
116+
sql = f"""
117+
SELECT
118+
t.TABLE_NAME AS "TABLE_NAME",
119+
NVL(c.COMMENTS, '') AS "TABLE_COMMENT"
120+
FROM (
121+
SELECT TABLE_NAME, 'TABLE' AS OBJECT_TYPE
122+
FROM DBA_TABLES
123+
WHERE OWNER = '{conf.dbSchema}'
124+
UNION ALL
125+
SELECT VIEW_NAME AS TABLE_NAME, 'VIEW' AS OBJECT_TYPE
126+
FROM DBA_VIEWS
127+
WHERE OWNER = '{conf.dbSchema}'
128+
) t
129+
LEFT JOIN DBA_TAB_COMMENTS c
130+
ON t.TABLE_NAME = c.TABLE_NAME
131+
AND c.TABLE_TYPE = t.OBJECT_TYPE
132+
AND c.OWNER = '{conf.dbSchema}'
133+
ORDER BY t.TABLE_NAME
103134
"""
104135

105136
result = session.execute(text(sql))
@@ -130,7 +161,7 @@ def get_fields(ds: CoreDatasource, table_name: str = None):
130161
WHERE
131162
TABLE_SCHEMA = '{conf.database}'
132163
"""
133-
sql2 = f" AND TABLE_NAME = '{table_name}';" if table_name is not None and table_name != "" else ";"
164+
sql2 = f" AND TABLE_NAME = '{table_name}'" if table_name is not None and table_name != "" else ""
134165
sql = sql1 + sql2
135166
elif ds.type == "sqlServer":
136167
sql1 = f"""
@@ -148,7 +179,7 @@ def get_fields(ds: CoreDatasource, table_name: str = None):
148179
WHERE
149180
C.TABLE_SCHEMA = '{conf.dbSchema}'
150181
"""
151-
sql2 = f" AND C.TABLE_NAME = '{table_name}';" if table_name is not None and table_name != "" else ";"
182+
sql2 = f" AND C.TABLE_NAME = '{table_name}'" if table_name is not None and table_name != "" else ""
152183
sql = sql1 + sql2
153184
elif ds.type == "pg" or ds.type == "excel":
154185
sql1 = """
@@ -167,7 +198,32 @@ def get_fields(ds: CoreDatasource, table_name: str = None):
167198
AND a.attnum > 0
168199
AND NOT a.attisdropped
169200
"""
170-
sql2 = f" AND c.relname = '{table_name}';" if table_name is not None and table_name != "" else ";"
201+
sql2 = f" AND c.relname = '{table_name}'" if table_name is not None and table_name != "" else ""
202+
sql = sql1 + sql2
203+
elif ds.type == "oracle":
204+
sql1 = f"""
205+
SELECT
206+
col.COLUMN_NAME AS "COLUMN_NAME",
207+
(CASE
208+
WHEN col.DATA_TYPE IN ('VARCHAR2', 'CHAR', 'NVARCHAR2', 'NCHAR')
209+
THEN col.DATA_TYPE || '(' || col.DATA_LENGTH || ')'
210+
WHEN col.DATA_TYPE = 'NUMBER' AND col.DATA_PRECISION IS NOT NULL
211+
THEN col.DATA_TYPE || '(' || col.DATA_PRECISION ||
212+
CASE WHEN col.DATA_SCALE > 0 THEN ',' || col.DATA_SCALE END || ')'
213+
ELSE col.DATA_TYPE
214+
END) AS "DATA_TYPE",
215+
NVL(com.COMMENTS, '') AS "COLUMN_COMMENT"
216+
FROM
217+
DBA_TAB_COLUMNS col
218+
LEFT JOIN
219+
DBA_COL_COMMENTS com
220+
ON col.OWNER = com.OWNER
221+
AND col.TABLE_NAME = com.TABLE_NAME
222+
AND col.COLUMN_NAME = com.COLUMN_NAME
223+
WHERE
224+
col.OWNER = '{conf.dbSchema}'
225+
"""
226+
sql2 = f" AND col.TABLE_NAME = '{table_name}'" if table_name is not None and table_name != "" else ""
171227
sql = sql1 + sql2
172228

173229
result = session.execute(text(sql))

backend/apps/db/type.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Author: Junjun
2+
# Date: 2025/5/22
3+
from typing import Dict
4+
5+
6+
def db_type_relation() -> Dict:
7+
return {
8+
"mysql": "MySQL",
9+
"sqlServer": "Microsoft SQL Server",
10+
"pg": "PostgreSQL",
11+
"excel": "Excel/CSV",
12+
"oracle": "Oracle"
13+
}

backend/pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ dependencies = [
3232
"pymssql (>=2.3.4,<3.0.0)",
3333
"pandas (>=2.2.3,<3.0.0)",
3434
"openpyxl (>=3.1.5,<4.0.0)",
35-
"psycopg2 (>=2.9.10,<3.0.0)"
35+
"psycopg2 (>=2.9.10,<3.0.0)",
36+
"oracledb (>=3.1.1,<4.0.0)"
3637
]
3738
[[tool.uv.index]]
3839
url = "https://pypi.tuna.tsinghua.edu.cn/simple"

frontend/src/views/ds/form.vue

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,21 @@
6464
<el-form-item label="Database">
6565
<el-input v-model="config.database" />
6666
</el-form-item>
67+
<el-form-item label="Connect Mode" v-if="form.type === 'oracle'">
68+
<el-radio-group v-model="config.mode">
69+
<el-radio value="service_name">Service Name</el-radio>
70+
<el-radio value="sid">SID</el-radio>
71+
</el-radio-group>
72+
</el-form-item>
6773
<el-form-item label="Extra JDBC String">
6874
<el-input v-model="config.extraJdbc" />
6975
</el-form-item>
7076
<el-form-item label="Schema" v-if="haveSchema.includes(form.type)">
7177
<el-input v-model="config.dbSchema" />
7278
<el-button link type="primary" :icon="Plus" v-if="false">Get Schema</el-button>
7379
</el-form-item>
80+
<span v-if="form.type === 'sqlServer'">Supported version: 2012+</span>
81+
<span v-else-if="form.type === 'oracle'">Supported version: 12+</span>
7482
</div>
7583
</el-form>
7684
</div>
@@ -143,7 +151,8 @@ const config = ref<any>({
143151
extraJdbc:'',
144152
dbSchema:'',
145153
filename:'',
146-
sheets: []
154+
sheets: [],
155+
mode:'service_name'
147156
})
148157
149158
const close = () => {
@@ -176,6 +185,7 @@ const open = (item: any, editTable: boolean = false) => {
176185
config.value.dbSchema = configuration.dbSchema
177186
config.value.filename = configuration.filename
178187
config.value.sheets = configuration.sheets
188+
config.value.mode = configuration.mode
179189
}
180190
181191
if (editTable) {
@@ -217,7 +227,8 @@ const open = (item: any, editTable: boolean = false) => {
217227
extraJdbc:'',
218228
dbSchema:'',
219229
filename:'',
220-
sheets: []
230+
sheets: [],
231+
mode:'service_name'
221232
}
222233
}
223234
dialogVisible.value = true
@@ -268,7 +279,8 @@ const buildConf = () => {
268279
extraJdbc:config.value.extraJdbc,
269280
dbSchema:config.value.dbSchema,
270281
filename:config.value.filename,
271-
sheets:config.value.sheets
282+
sheets:config.value.sheets,
283+
mode:config.value.mode
272284
}))
273285
}
274286

frontend/src/views/ds/index.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,12 @@
3030
<SQLServerDs v-else-if="ds.type === 'sqlServer'"/>
3131
<PgDs v-else-if="ds.type === 'pg'"/>
3232
<ExcelDs v-else-if="ds.type === 'excel'"/>
33+
<OracleDs v-else-if="ds.type === 'oracle'"/>
3334
</Icon>
3435
</div>
3536
<div class="connection-details">
3637
<div class="connection-name">{{ ds.name }}</div>
37-
<div class="connection-type">{{ dsRelation[ds.type] }}</div>
38+
<div class="connection-type">{{ ds.type_name }}</div>
3839
<div class="connection-host">{{ ds.description }}</div>
3940
<div class="connection-last">{{ datetimeFormat(ds.create_time) }}</div>
4041
</div>
@@ -58,14 +59,14 @@ import MysqlDs from '@/assets/svg/ds/mysql-ds.svg'
5859
import SQLServerDs from '@/assets/svg/ds/sqlServer-ds.svg'
5960
import PgDs from '@/assets/svg/ds/pg-ds.svg'
6061
import ExcelDs from '@/assets/svg/ds/Excel-ds.svg'
62+
import OracleDs from '@/assets/svg/ds/oracle-ds.svg'
6163
import { Search, List, CreditCard } from '@element-plus/icons-vue'
6264
import { ref, onMounted } from 'vue'
6365
import DsForm from './form.vue'
6466
import { datasourceApi } from '@/api/datasource'
6567
import { datetimeFormat } from '@/utils/utils'
6668
import { ElMessageBox } from 'element-plus'
6769
import { useRouter } from 'vue-router'
68-
import { dsRelation } from '@/views/ds/js/ds-type'
6970
7071
const searchValue = ref<string>('')
7172
const dsForm = ref()
Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,13 @@
11
export const dsType = [
22
{label:"MySQL", value:"mysql"},
3-
{label:"Microsoft SQL Server", value:"sqlServer"},
43
{label:"PostgreSQL", value:"pg"},
4+
{label:"Microsoft SQL Server", value:"sqlServer"},
5+
{label:"Oracle", value:"oracle"},
56
{label:"Excel/CSV", value:"excel"},
67
]
78

89
export const haveSchema = [
910
'sqlServer',
10-
'pg'
11-
]
12-
13-
export const dsRelation: any = {
14-
"mysql": "MySQL",
15-
"sqlServer": "Microsoft SQL Server",
16-
"pg": "PostgreSQL",
17-
"excel": "Excel/CSV",
18-
}
11+
'pg',
12+
'oracle'
13+
]

0 commit comments

Comments
 (0)