From b16393f68c6f6446785531208e4bbf5b1a04e806 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Mon, 18 May 2026 22:38:04 +0800 Subject: [PATCH] feat(database): add agent database profiles --- .../public/icons/database/databricks.webp | Bin 0 -> 706 bytes .../desktop/public/icons/database/exasol.webp | Bin 0 -> 2876 bytes .../public/icons/database/firebird.webp | Bin 0 -> 384 bytes apps/desktop/public/icons/database/gbase.webp | Bin 0 -> 740 bytes .../public/icons/database/greatsql.webp | Bin 0 -> 2552 bytes .../public/icons/database/polardb.webp | Bin 0 -> 174 bytes .../public/icons/database/saphana.webp | Bin 0 -> 240 bytes apps/desktop/public/icons/database/tdsql.webp | Bin 0 -> 526 bytes .../public/icons/database/teradata.webp | Bin 0 -> 816 bytes .../public/icons/database/vertica.webp | Bin 0 -> 20882 bytes .../connection/ConnectionDialog.vue | 41 +++++++++++- .../src/components/icons/DatabaseIcon.vue | 10 +++ .../desktop/src/lib/databaseCapabilitySets.ts | 61 +++++++++++++++++- apps/desktop/src/types/database.ts | 9 +++ crates/dbx-core/src/agent_service.rs | 9 +++ crates/dbx-core/src/connection.rs | 11 +++- crates/dbx-core/src/database_capabilities.rs | 11 ++++ crates/dbx-core/src/models/connection.rs | 51 ++++++++++++++- .../dbx-core/tests/database_capabilities.rs | 23 +++++++ .../app-tests/databaseCapabilities.test.ts | 22 +++++++ packages/app-tests/databaseIconAssets.test.ts | 34 ++++++++++ 21 files changed, 277 insertions(+), 5 deletions(-) create mode 100644 apps/desktop/public/icons/database/databricks.webp create mode 100644 apps/desktop/public/icons/database/exasol.webp create mode 100644 apps/desktop/public/icons/database/firebird.webp create mode 100644 apps/desktop/public/icons/database/gbase.webp create mode 100644 apps/desktop/public/icons/database/greatsql.webp create mode 100644 apps/desktop/public/icons/database/polardb.webp create mode 100644 apps/desktop/public/icons/database/saphana.webp create mode 100644 apps/desktop/public/icons/database/tdsql.webp create mode 100644 apps/desktop/public/icons/database/teradata.webp create mode 100644 apps/desktop/public/icons/database/vertica.webp create mode 100644 packages/app-tests/databaseIconAssets.test.ts diff --git a/apps/desktop/public/icons/database/databricks.webp b/apps/desktop/public/icons/database/databricks.webp new file mode 100644 index 0000000000000000000000000000000000000000..0a74319cffb9948cccbaa385daf91c2e34882ad4 GIT binary patch literal 706 zcmV;z0zLgwNk&Gx0ssJ4MM6+kP&iDj0ssInAHW9?U!|aJ8_B4SKeg@lL74vmQP{R^ zn~Q8`v}BVuWH0s&)Elq{0jL1V5)2@KRM6)X34kDIf|aGDR#bHrNkJeX8pxOcS5YvE zF<`^lLg)|Da|<5;qPA^koA>XZCM#!Wwr$(CZQHhOC!G(o+Rh-N{}TXX6>|J58!@8!=a|OWq{zr6}-+CQ{f$n*f&Ezf~&y zsVeVWa0Fmif43UL8OOz}-OUI*07%^XUdDZ?Vbp*8xz|17nFol1fC-s?>U1Xhh;!%j zWH5s;EvxxjD?pyOc~xnt%d+xnGeHJecZ!Yx|M_#0tpv;q&-T+5fHG_Uq|h8t<(@uQ z$pHSPr2k_jngtx{Z|0+-7H{kA05*F4y~j-gfPW5?hE9Kt`A|sQ`(9uB+$ErXW1o{j z47B;z7(kx5CA9GbOke;5;?yB-#>)WvI2mAla#3Uom=|2!R9FCjMa9i&ff-<0bSXLl z#I^flGmZls>K^t}<%*2`hdNt;i`x{J1Relr;ONJMKLv5?5f@j5E;j-iIPo#+7x}Ek z5^&M0LbnS66Erdsz<{`PYgVE;glSohmYM_VoRj-%D?lm*tZMF$vQ>b2v&IonG`{a7 z$pGuByQ54cpv*bGtFi`UDLZ1L0s!?J`kW4CKwLWCmSSSoURzxVn9!N8A#VzuT;!sr z&y9fk4}X3R`%n@!IT6@Tb&!P@I|)2R$kNoQ;^DjbQPw3OS4g0H~TohX4Qo literal 0 HcmV?d00001 diff --git a/apps/desktop/public/icons/database/exasol.webp b/apps/desktop/public/icons/database/exasol.webp new file mode 100644 index 0000000000000000000000000000000000000000..00b340da9b4d6dc68a433e2e291dfaff07dc6629 GIT binary patch literal 2876 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 aI1Ealq`eq^P$I=r1o;z>6b`aZs#pN87?TtL literal 0 HcmV?d00001 diff --git a/apps/desktop/public/icons/database/firebird.webp b/apps/desktop/public/icons/database/firebird.webp new file mode 100644 index 0000000000000000000000000000000000000000..fd84e6f76121a204d9a5e36dfb67e8295c21c0e4 GIT binary patch literal 384 zcmV-`0e}8dNk&F^0RRA3MM6+kP&iC$0RR9m55NNeSK~IeZB==KyKDA`jQ&H);ivxp zl_#eS0!bU+Kl7P`NOHqwMW7OY8ii0Ig&>*0&wrrIJpcdz85ytuMuq?|of*94v4jVA zY|CLlK!kCC9zAjkU_}MNbPn*&8>R!y9CS+LR)u7Rof0O@f() zB@G?H+3|4P|CjRBy^I@+qU=%*ixOZkgTs&NE-oo!p1m!p4n!z1ln8>vW%^R|F`=}> zf(T8d`-&qm4rEF7HKzL-GaWIa`cNz}il{yy7!Yh>f=Rvz{AGys5& zf(a&gK@jxg@P{Bsmgv7+dkBIcI6yGq@Ed?93hK%v#CzNYdlQOv4n>YG+c zvog|JBtb~2xNS_)Fx!;}8uPp7!Wyj=K(kOc9>zc!=vqD)Vzdl&E*}drQwzG+&k~W` z2^_m+qTzfV1*4?20M~YTvWQQ~RIkKG>T3j^E(T4d;6+522NB%>UIq?C^g6tV$iB?~ ztIAG$Xt7;OePp%S-a^{#H|*CftM%5;Csy0#PuI_U&GfxbII;g|(wf!#Hm(2rIC900 zo6-AsUU(h5>%h^}&oBQt)1U7BxP6Iv&EhR9H-1~bChGjFRhu{LxDm0id0Ud7*~N@^ ziKmampIW!}@YZGLmYzMn=+4Q5yJN1z?KyNTg#lz${=aw8XiC0oFdAPx_<)XQ&#xK{ z_kTYy7%zQy0F*a?7ZI5U5j`FT;9-d9b$Aic0RT-s5^$CJCW`nJPbtlOS``|~m1 zq;&A;l!<7jM!(t_3LGxgt8A2l_m}EZIzXXEss(LA%_xOC(~+wsuXC2dSBIJl#ZXdV W%ZN&v)rp>l(i}m^DQ)PTR4Ft)J7@j? literal 0 HcmV?d00001 diff --git a/apps/desktop/public/icons/database/greatsql.webp b/apps/desktop/public/icons/database/greatsql.webp new file mode 100644 index 0000000000000000000000000000000000000000..c7bd0562d2d2e97dd74c29b879e591810928931c GIT binary patch literal 2552 zcmV<|BMM6+kP&iEE2><{uKfn(VHPNlM|Ba>m@ZQ~>zt#Ug(nzD3 zxsJQudv7u#`fo)Vjb?~8`#PWq4xwF}Q2-@uEn9;Q4Ukd?4Ula}_KR)@bU+Edg-wY# z|AXtp#ajU0WgI%73zrcQQvxW<>dN0U*JyY8)%lq+0Y2wwi_#p7~g>1`$ne)>q zl!HkY#-+N07g9$sEkQ}H>TkuDfoHL?z_TRiJAp+)^+i9YX1b$DCFrXvhI!rcjOyn5 zp{Z7dQjkPKRr7U(#%DDsS-V=Yt(*xwvs%>CP>ntb#?Xgw1pK46;R&S6$`E1puJs8}Z zY<+6loIVW-xvJfe=fX^}B>(xuLF8=GhLK)iaAP z07Dnxs>ed`g|!4hVKmj%5O>-ccKg5aQlC*fr zLKI@zT#8(QrXZail2>BIejk{s^j$!Qz*l!QteT%QQ-6B<2>`5H1s2R~Y7(fp{M+S1 zh%$RpH+>g}rL7Eg&uniw@Q5PoD}C<^Yq4%Ul1AU2Vh!qRx61_p-TxnJ*N3Y{b*@*E zhRvk9#Kt%=IdvVbNJD^vS&&u-ZA;fKBaSzby2XZbFGyZwc~4_gsjd<<`DDwhCtK>; zUN-;nDO0?)`@)g{(AyiCYq08mt*15JJ3~{~rF}pW_=Kk>YulEe?vU+)zFqQ1bTwq( zi{oyp-&vx6G=RvF9YpT3#qKDoMKfpS~9aN^tm0KFY7?<76I&^6YU$t>zEZsDEvQj9 zL5DYZlX`%)!-@EGk-AiQG>!YnOw(Ruu#c?vk&l_9b7o2nzXp)L8T1{Y5*1~wW;n&M8)#IQ za7e;z#~8pUSTr}fu>b(#aG;x+Dp{)z!EyM7;R;ETimxnw&m*}CFuAnV+vr(blf+m-+bj#;IwHRySf{J5Bp(!z1{8~CH*KXL**_7G-g7io zEmUIlW1(qSvZi7` zJ0SYGySJqPd}O?>Y^x`Um2LA$*#nuKmP^~A8hw9?nOh|qebvBQp1wmF{TwXnizIbu zJ~7dOyL30!ijlssEe;*JFxJ!34WKwDdunP~Ur!Xz)O2r^1Ow=6-#AIHIq*PWW)2p( zwzwTy2GFDLz>e#I7$0G<3g)i{cpjyh?tBS*`7gCdx-RL(GXmg+##J{0xthj`tu65H z4vFlIL&r=i#Fe_RxSma$W$Oa@9h4UoTRBkp({}-4bq%f%84A3r**&_H*5fnlGt10B zYq9AP(QGO+bK?t##N7h`T|XnW(^#<|OIWa0L>}o`SkmvKj)>e90TbV>#a}%W14c6Q zDJ8zn%=iO9Zyor;;wvTmpCPlKq{9W&5H)p6=Rg z^e7mo@ivl$cDT8;KlcrxsGgb8wD`Uq z!`z;_C|R!1Uxs}du{ZSM8NFEkgArdU)ObURB6n@Q+=5DTT{h;;b#FB^_M>6a+*JL4 zbzEr(Z5sl!U`1!y&FMx^$re~Qajp!Tjsr|g@4mxOZ5IdZki3mhY5Y?QGofzx1(kAB zIdd%^Q)}N=N&-C%dPT5SsNtb?D*|`F5>y)Kku%%!X@KO6OylY*^Y333sF+BgP6?>= ze^BZ5&!z%&u71aGtm8XP>OwTb1iz+kyy>5y(nyV*+eS|bptt|S;tm$Oj<#gJBI<=` zg)pwwz`vEQxGF;aQ zX7fSN=bv%nXs|deHE|v46YC=|IT9|I<`d0+Rcx&Re?>rLFOU5`Fm`zyGEkM`08JdS O&#+wMGE`+a*$Dtv8~+vn literal 0 HcmV?d00001 diff --git a/apps/desktop/public/icons/database/polardb.webp b/apps/desktop/public/icons/database/polardb.webp new file mode 100644 index 0000000000000000000000000000000000000000..7363a491961e6ebfc269622bbdfb6f56f3433421 GIT binary patch literal 174 zcmV;f08#%^Nk&Gd00012MM6+kP&iDP0000lAHW9?Z!l`}9|YO9p&Psi3pNly00`i4 zQ)}Csyy?jn4>1tx%#4Rjlnn)40wVChgapP8kD2r)Afo?@+_r7#YevT})c;%nEGY(< z>jbcuq!)ny0PcR~^aqk3Y;EN-yTT2UvI!4>pv*aAlnFB`O~GQlUazNRo&cLBhNIDF c6s4+5st4702ddwx`W>s^x%wZf|Ec#809OP|2LJ#7 literal 0 HcmV?d00001 diff --git a/apps/desktop/public/icons/database/saphana.webp b/apps/desktop/public/icons/database/saphana.webp new file mode 100644 index 0000000000000000000000000000000000000000..ca36c340744068601e7cb0a30cd20e58e1a7767f GIT binary patch literal 240 zcmWIYbaQ*bz`zjh>J$(bVBvEc$ks1EAS+P6U_q|5nquq><&w;nTibFae*V9Be%W!K z+h!LP`t18_;~aK|dtTbo_wcc8#s5>*g&*p93X*P%&6Q!S4ENBHVV^jmi;L64Lg+$K zjSXiwqtT4m<()77v%I+c`?|TodB)PCo$Oac)#U=h=lp1>I@-yLI>P))RNW4t1W7ZpO{~c_R$Eh!jH+#G;~xBp+U*uh zH7DD)jW~HHpzf1`l}ho+gCry=PZIbqxC=>Ue zu!%7cs>(6kaqqiXM>+uxp)L|JXa zlOX8c9cahhDW`gZ0B6ZNvabeY7u*Ez-7Z>-5aTXE431hvG;zP(4!;Kp?h}MytwrDy z{JN!XSGV(w*95hNFIfDyzcRwH3-QRyG38GJn2qnn}3{~aS?y|d&>&f8@16&f9P}S+1t94Vy Qb@L(TzhV;gK-Ya30BUmdCjbBd literal 0 HcmV?d00001 diff --git a/apps/desktop/public/icons/database/teradata.webp b/apps/desktop/public/icons/database/teradata.webp new file mode 100644 index 0000000000000000000000000000000000000000..cc357589ec6b0cbf9377c0096d7efb0c43b3eb83 GIT binary patch literal 816 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 = { mariadb: "mariadb", tidb: "tidb", oceanbase: "oceanbase", + "oceanbase-oracle": "oceanbase", goldendb: "goldendb", + tdsql: "tdsql", + polardb: "polardb", + greatsql: "greatsql", + databricks: "databricks", + saphana: "saphana", + teradata: "teradata", + vertica: "vertica", + firebird: "firebird", + exasol: "exasol", + gbase: "gbase", opengauss: "opengauss", gaussdb: "gaussdb", kingbase: "kingbase", @@ -421,11 +449,22 @@ const dbOptions = [ { value: "gaussdb", label: "GaussDB" }, { value: "tidb", label: "TiDB" }, { value: "oceanbase", label: "OceanBase" }, + { value: "oceanbase-oracle", label: "OceanBase Oracle Mode" }, { value: "goldendb", label: "GoldenDB" }, + { value: "tdsql", label: "TDSQL" }, + { value: "polardb", label: "PolarDB" }, + { value: "greatsql", label: "GreatSQL" }, { value: "doris", label: "Doris" }, { value: "selectdb", label: "SelectDB" }, { value: "starrocks", label: "StarRocks" }, { value: "tdengine", label: "TDengine" }, + { value: "databricks", label: "Databricks SQL" }, + { value: "saphana", label: "SAP HANA" }, + { value: "teradata", label: "Teradata" }, + { value: "vertica", label: "Vertica" }, + { value: "firebird", label: "Firebird" }, + { value: "exasol", label: "Exasol" }, + { value: "gbase", label: "GBase" }, { value: "opengauss", label: "openGauss" }, { value: "kingbase", label: "KingBase" }, { value: "highgo", label: "瀚高 HighGo" }, diff --git a/apps/desktop/src/components/icons/DatabaseIcon.vue b/apps/desktop/src/components/icons/DatabaseIcon.vue index 91ebc7bc9f..906a6c141d 100644 --- a/apps/desktop/src/components/icons/DatabaseIcon.vue +++ b/apps/desktop/src/components/icons/DatabaseIcon.vue @@ -52,6 +52,16 @@ const assetIcons: Record = { redshift: "redshift", neo4j: "neo4j", informix: "informix", + databricks: "databricks.webp", + saphana: "saphana.webp", + teradata: "teradata.webp", + vertica: "vertica.webp", + firebird: "firebird.webp", + exasol: "exasol.webp", + gbase: "gbase.webp", + tdsql: "tdsql.webp", + polardb: "polardb.webp", + greatsql: "greatsql.webp", }; const letterIcons: Record = {}; diff --git a/apps/desktop/src/lib/databaseCapabilitySets.ts b/apps/desktop/src/lib/databaseCapabilitySets.ts index 3ff57e3e24..fd8bd3daae 100644 --- a/apps/desktop/src/lib/databaseCapabilitySets.ts +++ b/apps/desktop/src/lib/databaseCapabilitySets.ts @@ -11,6 +11,14 @@ export const SCHEMA_AWARE_TYPES = new Set([ "highgo", "vastbase", "yashandb", + "databricks", + "saphana", + "teradata", + "vertica", + "exasol", + "opengauss", + "oceanbase-oracle", + "gbase", "jdbc", "h2", "snowflake", @@ -35,6 +43,15 @@ export const DIAGRAM_SUPPORTED_TYPES = new Set([ "vastbase", "goldendb", "yashandb", + "databricks", + "saphana", + "teradata", + "vertica", + "firebird", + "exasol", + "opengauss", + "oceanbase-oracle", + "gbase", "access", "h2", "db2", @@ -56,6 +73,15 @@ export const DATABASE_SEARCH_SUPPORTED_TYPES = new Set([ "vastbase", "goldendb", "yashandb", + "databricks", + "saphana", + "teradata", + "vertica", + "firebird", + "exasol", + "opengauss", + "oceanbase-oracle", + "gbase", "access", "h2", "snowflake", @@ -110,6 +136,8 @@ export const CREATE_DATABASE_SUPPORTED_TYPES = new Set([ "oracle", "dameng", "gaussdb", + "opengauss", + "oceanbase-oracle", "doris", "starrocks", "redshift", @@ -124,9 +152,17 @@ export const FIELD_LINEAGE_SUPPORTED_TYPES = new Set([ "redshift", "dameng", "gaussdb", + "opengauss", + "oceanbase-oracle", ]); -export const SINGLE_DATABASE_TYPES = new Set(["oracle", "dameng", "access"]); +export const SINGLE_DATABASE_TYPES = new Set([ + "oracle", + "dameng", + "firebird", + "oceanbase-oracle", + "access", +]); export const FETCH_FIRST_TYPES = new Set(["oracle", "dameng"]); @@ -139,13 +175,21 @@ export const TREE_SCHEMA_TYPES = new Set([ "highgo", "vastbase", "yashandb", + "databricks", + "saphana", + "teradata", + "vertica", + "exasol", + "opengauss", + "oceanbase-oracle", + "gbase", "jdbc", "trino", "h2", "tdengine", ]); -export const PG_LIKE_STRUCTURE_TYPES = new Set(["postgres", "redshift", "gaussdb"]); +export const PG_LIKE_STRUCTURE_TYPES = new Set(["postgres", "redshift", "gaussdb", "opengauss"]); export const AGENT_DRIVER_TYPES = new Set([ "dameng", @@ -154,6 +198,15 @@ export const AGENT_DRIVER_TYPES = new Set([ "vastbase", "yashandb", "goldendb", + "databricks", + "saphana", + "teradata", + "vertica", + "firebird", + "exasol", + "opengauss", + "oceanbase-oracle", + "gbase", "access", "oracle", "h2", @@ -181,6 +234,8 @@ export const TRANSFER_SQL_TYPES = new Set([ "duckdb", "dameng", "gaussdb", + "opengauss", + "oceanbase-oracle", ]); export const DIAGRAM_SQL_TYPES = new Set([ @@ -192,4 +247,6 @@ export const DIAGRAM_SQL_TYPES = new Set([ "redshift", "dameng", "gaussdb", + "opengauss", + "oceanbase-oracle", ]); diff --git a/apps/desktop/src/types/database.ts b/apps/desktop/src/types/database.ts index 516f28ff9b..71d2ad9692 100644 --- a/apps/desktop/src/types/database.ts +++ b/apps/desktop/src/types/database.ts @@ -19,6 +19,15 @@ export type DatabaseType = | "vastbase" | "goldendb" | "yashandb" + | "databricks" + | "saphana" + | "teradata" + | "vertica" + | "firebird" + | "exasol" + | "opengauss" + | "oceanbase-oracle" + | "gbase" | "access" | "h2" | "snowflake" diff --git a/crates/dbx-core/src/agent_service.rs b/crates/dbx-core/src/agent_service.rs index 9e6dd3062c..d82a4e81f9 100644 --- a/crates/dbx-core/src/agent_service.rs +++ b/crates/dbx-core/src/agent_service.rs @@ -14,6 +14,15 @@ pub const AGENT_TYPES: &[(&str, &str)] = &[ ("highgo", "瀚高 HighGo"), ("vastbase", "Vastbase"), ("goldendb", "GoldenDB"), + ("databricks", "Databricks SQL"), + ("saphana", "SAP HANA"), + ("teradata", "Teradata"), + ("vertica", "Vertica"), + ("firebird", "Firebird"), + ("exasol", "Exasol"), + ("opengauss", "openGauss"), + ("oceanbase-oracle", "OceanBase Oracle Mode"), + ("gbase", "GBase"), ("access", "Microsoft Access"), ("oracle", "Oracle"), ("oracle-10g", "Oracle 10g"), diff --git a/crates/dbx-core/src/connection.rs b/crates/dbx-core/src/connection.rs index 3ba5ce439e..6dcfd1ea6f 100644 --- a/crates/dbx-core/src/connection.rs +++ b/crates/dbx-core/src/connection.rs @@ -71,7 +71,7 @@ pub fn metadata_connection_config(config: &ConnectionConfig) -> ConnectionConfig pub fn database_connection_config(config: &ConnectionConfig, database: Option<&str>) -> ConnectionConfig { let mut db_config = if database.is_some() { config.clone() } else { metadata_connection_config(config) }; if let Some(db) = database { - if !matches!(db_config.db_type, DatabaseType::Oracle | DatabaseType::Dameng) { + if !matches!(db_config.db_type, DatabaseType::Oracle | DatabaseType::Dameng | DatabaseType::OceanbaseOracle) { db_config.database = Some(db.to_string()); } } @@ -233,6 +233,15 @@ impl AppState { | DatabaseType::Vastbase | DatabaseType::Goldendb | DatabaseType::Yashandb + | DatabaseType::Databricks + | DatabaseType::SapHana + | DatabaseType::Teradata + | DatabaseType::Vertica + | DatabaseType::Firebird + | DatabaseType::Exasol + | DatabaseType::OpenGauss + | DatabaseType::OceanbaseOracle + | DatabaseType::Gbase | DatabaseType::Oracle | DatabaseType::H2 | DatabaseType::Snowflake diff --git a/crates/dbx-core/src/database_capabilities.rs b/crates/dbx-core/src/database_capabilities.rs index f0a2eef3a4..f23864e810 100644 --- a/crates/dbx-core/src/database_capabilities.rs +++ b/crates/dbx-core/src/database_capabilities.rs @@ -8,6 +8,15 @@ pub fn agent_key(db_type: &DatabaseType, driver_profile: Option<&str>) -> Option DatabaseType::Vastbase => Some("vastbase"), DatabaseType::Goldendb => Some("goldendb"), DatabaseType::Yashandb => Some("yashandb"), + DatabaseType::Databricks => Some("databricks"), + DatabaseType::SapHana => Some("saphana"), + DatabaseType::Teradata => Some("teradata"), + DatabaseType::Vertica => Some("vertica"), + DatabaseType::Firebird => Some("firebird"), + DatabaseType::Exasol => Some("exasol"), + DatabaseType::OpenGauss => Some("opengauss"), + DatabaseType::OceanbaseOracle => Some("oceanbase-oracle"), + DatabaseType::Gbase => Some("gbase"), DatabaseType::Oracle => match driver_profile { Some("oracle-10g") => Some("oracle-10g"), _ => Some("oracle"), @@ -47,6 +56,8 @@ pub fn is_single_connection_pool(db_type: &DatabaseType) -> bool { | DatabaseType::Vastbase | DatabaseType::Goldendb | DatabaseType::Yashandb + | DatabaseType::Firebird + | DatabaseType::OceanbaseOracle | DatabaseType::Access | DatabaseType::Jdbc ) diff --git a/crates/dbx-core/src/models/connection.rs b/crates/dbx-core/src/models/connection.rs index 6a76bfe715..a803646f74 100644 --- a/crates/dbx-core/src/models/connection.rs +++ b/crates/dbx-core/src/models/connection.rs @@ -131,6 +131,18 @@ pub enum DatabaseType { Goldendb, Gaussdb, Yashandb, + Databricks, + #[serde(rename = "saphana")] + SapHana, + Teradata, + Vertica, + Firebird, + Exasol, + #[serde(rename = "opengauss")] + OpenGauss, + #[serde(rename = "oceanbase-oracle")] + OceanbaseOracle, + Gbase, Access, #[serde(rename = "h2")] H2, @@ -175,10 +187,11 @@ impl ConnectionConfig { _ => Some("postgres"), }, DatabaseType::Redshift => Some("dev"), - DatabaseType::Gaussdb => Some("postgres"), + DatabaseType::Gaussdb | DatabaseType::OpenGauss => Some("postgres"), DatabaseType::Kingbase | DatabaseType::Vastbase => Some("postgres"), DatabaseType::Highgo => Some("highgo"), DatabaseType::Yashandb => Some("yasdb"), + DatabaseType::Firebird => Some("employee"), _ => None, } } @@ -272,6 +285,15 @@ impl ConnectionConfig { DatabaseType::Goldendb => format!("goldendb://{host}:{port}{db_part}"), DatabaseType::Gaussdb => format!("gaussdb://{host}:{port}{db_part}"), DatabaseType::Yashandb => format!("yashandb://{host}:{port}{db_part}"), + DatabaseType::Databricks => format!("databricks://{host}:{port}{db_part}"), + DatabaseType::SapHana => format!("saphana://{host}:{port}{db_part}"), + DatabaseType::Teradata => format!("teradata://{host}:{port}{db_part}"), + DatabaseType::Vertica => format!("vertica://{host}:{port}{db_part}"), + DatabaseType::Firebird => format!("firebird://{host}:{port}{db_part}"), + DatabaseType::Exasol => format!("exasol://{host}:{port}{db_part}"), + DatabaseType::OpenGauss => format!("opengauss://{host}:{port}{db_part}"), + DatabaseType::OceanbaseOracle => format!("oceanbase-oracle://{host}:{port}{db_part}"), + DatabaseType::Gbase => format!("gbase://{host}:{port}{db_part}"), DatabaseType::H2 => format!("h2://{host}:{port}{db_part}"), DatabaseType::Snowflake => format!("snowflake://{host}/{db_part}"), DatabaseType::Trino => format!("trino://{host}:{port}{db_part}"), @@ -374,6 +396,33 @@ impl ConnectionConfig { DatabaseType::Yashandb => { format!("yashandb://{}:{}@{host}:{port}{db_part}", username, password) } + DatabaseType::Databricks => { + format!("databricks://{}:{}@{host}:{port}{db_part}", username, password) + } + DatabaseType::SapHana => { + format!("saphana://{}:{}@{host}:{port}{db_part}", username, password) + } + DatabaseType::Teradata => { + format!("teradata://{}:{}@{host}:{port}{db_part}", username, password) + } + DatabaseType::Vertica => { + format!("vertica://{}:{}@{host}:{port}{db_part}", username, password) + } + DatabaseType::Firebird => { + format!("firebird://{}:{}@{host}:{port}{db_part}", username, password) + } + DatabaseType::Exasol => { + format!("exasol://{}:{}@{host}:{port}{db_part}", username, password) + } + DatabaseType::OpenGauss => { + format!("opengauss://{}:{}@{host}:{port}{db_part}", username, password) + } + DatabaseType::OceanbaseOracle => { + format!("oceanbase-oracle://{}:{}@{host}:{port}{db_part}", username, password) + } + DatabaseType::Gbase => { + format!("gbase://{}:{}@{host}:{port}{db_part}", username, password) + } DatabaseType::H2 => { format!("h2://{}:{}@{host}:{port}{db_part}", username, password) } diff --git a/crates/dbx-core/tests/database_capabilities.rs b/crates/dbx-core/tests/database_capabilities.rs index 1668ce956d..660d72b611 100644 --- a/crates/dbx-core/tests/database_capabilities.rs +++ b/crates/dbx-core/tests/database_capabilities.rs @@ -10,6 +10,15 @@ fn maps_agent_database_types_to_driver_keys() { assert_eq!(agent_key(&DatabaseType::Gaussdb, None), Some("gaussdb")); assert_eq!(agent_key(&DatabaseType::Tdengine, None), Some("tdengine")); assert_eq!(agent_key(&DatabaseType::Yashandb, None), Some("yashandb")); + assert_eq!(agent_key(&DatabaseType::Databricks, None), Some("databricks")); + assert_eq!(agent_key(&DatabaseType::SapHana, None), Some("saphana")); + assert_eq!(agent_key(&DatabaseType::Teradata, None), Some("teradata")); + assert_eq!(agent_key(&DatabaseType::Vertica, None), Some("vertica")); + assert_eq!(agent_key(&DatabaseType::Firebird, None), Some("firebird")); + assert_eq!(agent_key(&DatabaseType::Exasol, None), Some("exasol")); + assert_eq!(agent_key(&DatabaseType::OpenGauss, None), Some("opengauss")); + assert_eq!(agent_key(&DatabaseType::OceanbaseOracle, None), Some("oceanbase-oracle")); + assert_eq!(agent_key(&DatabaseType::Gbase, None), Some("gbase")); assert_eq!(agent_key(&DatabaseType::Access, None), Some("access")); assert_eq!(agent_key(&DatabaseType::Oracle, None), Some("oracle")); assert_eq!(agent_key(&DatabaseType::Oracle, Some("oracle-10g")), Some("oracle-10g")); @@ -23,6 +32,15 @@ fn classifies_agent_database_types() { assert!(is_agent_type(&DatabaseType::Hive)); assert!(is_agent_type(&DatabaseType::Tdengine)); assert!(is_agent_type(&DatabaseType::Yashandb)); + assert!(is_agent_type(&DatabaseType::Databricks)); + assert!(is_agent_type(&DatabaseType::SapHana)); + assert!(is_agent_type(&DatabaseType::Teradata)); + assert!(is_agent_type(&DatabaseType::Vertica)); + assert!(is_agent_type(&DatabaseType::Firebird)); + assert!(is_agent_type(&DatabaseType::Exasol)); + assert!(is_agent_type(&DatabaseType::OpenGauss)); + assert!(is_agent_type(&DatabaseType::OceanbaseOracle)); + assert!(is_agent_type(&DatabaseType::Gbase)); assert!(is_agent_type(&DatabaseType::Access)); assert!(!is_agent_type(&DatabaseType::Mysql)); assert!(!is_agent_type(&DatabaseType::Jdbc)); @@ -36,6 +54,8 @@ fn identifies_single_connection_pool_types() { assert!(is_single_connection_pool(&DatabaseType::Dameng)); assert!(is_single_connection_pool(&DatabaseType::Access)); assert!(is_single_connection_pool(&DatabaseType::Yashandb)); + assert!(is_single_connection_pool(&DatabaseType::Firebird)); + assert!(is_single_connection_pool(&DatabaseType::OceanbaseOracle)); assert!(is_single_connection_pool(&DatabaseType::Jdbc)); assert!(!is_single_connection_pool(&DatabaseType::Trino)); assert!(!is_single_connection_pool(&DatabaseType::Postgres)); @@ -60,6 +80,9 @@ fn skips_tcp_probe_for_local_file_plugin_and_agent_types() { assert!(skips_tcp_probe(&DatabaseType::Oracle)); assert!(skips_tcp_probe(&DatabaseType::Tdengine)); assert!(skips_tcp_probe(&DatabaseType::Yashandb)); + assert!(skips_tcp_probe(&DatabaseType::Databricks)); + assert!(skips_tcp_probe(&DatabaseType::OceanbaseOracle)); + assert!(skips_tcp_probe(&DatabaseType::Gbase)); assert!(!skips_tcp_probe(&DatabaseType::Postgres)); assert!(!skips_tcp_probe(&DatabaseType::Mysql)); } diff --git a/packages/app-tests/databaseCapabilities.test.ts b/packages/app-tests/databaseCapabilities.test.ts index f5573a7f4f..67fd25f73f 100644 --- a/packages/app-tests/databaseCapabilities.test.ts +++ b/packages/app-tests/databaseCapabilities.test.ts @@ -37,6 +37,28 @@ test("treats Access as a local single-database agent driver", () => { assert.equal(supportsTableImport("access"), true); }); +test("exposes the extended JDBC agent ecosystem through driver management", () => { + for (const dbType of [ + "databricks", + "saphana", + "teradata", + "vertica", + "firebird", + "exasol", + "opengauss", + "oceanbase-oracle", + "gbase", + ] as const) { + assert.equal(supportsDriverManagement(dbType), true, `${dbType} should be agent-managed`); + assert.equal(supportsDatabaseSearch(dbType), true, `${dbType} should support database search`); + } + + assert.equal(SCHEMA_AWARE_TYPES.has("databricks"), true); + assert.equal(SCHEMA_AWARE_TYPES.has("opengauss"), true); + assert.equal(SCHEMA_AWARE_TYPES.has("oceanbase-oracle"), true); + assert.equal(SCHEMA_AWARE_TYPES.has("firebird"), false); +}); + test("describes schema tree mode through the capability helper", () => { assert.equal(usesTreeSchemaMode("trino"), true); assert.equal(usesTreeSchemaMode("h2"), true); diff --git a/packages/app-tests/databaseIconAssets.test.ts b/packages/app-tests/databaseIconAssets.test.ts new file mode 100644 index 0000000000..da7db53d4f --- /dev/null +++ b/packages/app-tests/databaseIconAssets.test.ts @@ -0,0 +1,34 @@ +import { existsSync, readFileSync } from "node:fs"; +import { join } from "node:path"; +import assert from "node:assert/strict"; +import test from "node:test"; + +const connectionDialogSource = readFileSync("apps/desktop/src/components/connection/ConnectionDialog.vue", "utf8"); +const databaseIconSource = readFileSync("apps/desktop/src/components/icons/DatabaseIcon.vue", "utf8"); + +const expectedIconNames = [ + "databricks", + "saphana", + "teradata", + "vertica", + "firebird", + "exasol", + "gbase", + "tdsql", + "polardb", + "greatsql", +] as const; + +test("new database profiles use dedicated icon identities", () => { + for (const iconName of expectedIconNames) { + assert.match(connectionDialogSource, new RegExp(`${iconName}: \\{[^}]*icon: "${iconName}"`, "s")); + assert.match(databaseIconSource, new RegExp(`${iconName}: "${iconName}\\.webp"`)); + } +}); + +test("new database favicon assets exist", () => { + for (const iconName of expectedIconNames) { + const iconPath = join("apps/desktop/public/icons/database", `${iconName}.webp`); + assert.equal(existsSync(iconPath), true, `${iconPath} should exist`); + } +});