diff --git a/apps/desktop/src/lib/tableSelectSql.ts b/apps/desktop/src/lib/tableSelectSql.ts index 14f1f3028..84eb09589 100644 --- a/apps/desktop/src/lib/tableSelectSql.ts +++ b/apps/desktop/src/lib/tableSelectSql.ts @@ -17,6 +17,7 @@ export interface BuildTableSelectSqlOptions { } export function quoteTableIdentifier(databaseType: DatabaseType | undefined, name: string): string { + if (databaseType === "jdbc") return quoteJdbcIdentifier(name); if (databaseType === "mysql" || databaseType === "hive" || databaseType === "tdengine" || databaseType === "access") return `\`${name.replace(/`/g, "``")}\``; if (databaseType === "informix" && /^[A-Za-z_][A-Za-z0-9_$]*$/.test(name)) return name; @@ -29,6 +30,11 @@ function quoteCypherIdentifier(name: string): string { return `\`${name.replace(/`/g, "``")}\``; } +function quoteJdbcIdentifier(name: string): string { + if (/^[A-Za-z_][A-Za-z0-9_$]*$/.test(name)) return name; + return `\`${name.replace(/`/g, "``")}\``; +} + export function qualifiedTableName( options: Pick, ): string { diff --git a/crates/dbx-core/src/sql_dialect.rs b/crates/dbx-core/src/sql_dialect.rs index 6c9165bf9..c8cbf1e23 100644 --- a/crates/dbx-core/src/sql_dialect.rs +++ b/crates/dbx-core/src/sql_dialect.rs @@ -174,6 +174,8 @@ pub fn qualified_table_name(database_type: Option, schema: Option< pub fn quote_table_identifier(database_type: Option, name: &str) -> String { match database_type { + Some(DatabaseType::Jdbc) if is_simple_jdbc_identifier(name) => name.to_string(), + Some(DatabaseType::Jdbc) => format!("`{}`", name.replace('`', "``")), Some( DatabaseType::Mysql | DatabaseType::Hive @@ -368,6 +370,15 @@ fn is_simple_informix_identifier(name: &str) -> bool { && chars.all(|ch| ch.is_ascii_alphanumeric() || ch == '_' || ch == '$') } +fn is_simple_jdbc_identifier(name: &str) -> bool { + let mut chars = name.chars(); + let Some(first) = chars.next() else { + return false; + }; + (first.is_ascii_alphabetic() || first == '_') + && chars.all(|ch| ch.is_ascii_alphanumeric() || ch == '_' || ch == '$') +} + #[cfg(test)] mod tests { use super::*; @@ -378,12 +389,18 @@ mod tests { assert_eq!(quote_table_identifier(Some(DatabaseType::SqlServer), "user]name"), "[user]]name]"); assert_eq!(quote_table_identifier(Some(DatabaseType::Postgres), "user\"name"), "\"user\"\"name\""); assert_eq!(quote_table_identifier(Some(DatabaseType::Informix), "users_1"), "users_1"); + assert_eq!(quote_table_identifier(Some(DatabaseType::Jdbc), "users_1"), "users_1"); + assert_eq!(quote_table_identifier(Some(DatabaseType::Jdbc), "user name"), "`user name`"); } #[test] fn qualifies_schema_only_for_schema_aware_databases() { assert_eq!(qualified_table_name(Some(DatabaseType::Postgres), Some("public"), "users"), "\"public\".\"users\""); assert_eq!(qualified_table_name(Some(DatabaseType::Mysql), Some("public"), "users"), "`users`"); + assert_eq!( + qualified_table_name(Some(DatabaseType::Jdbc), Some("cbsdw_dwd"), "dwd_test_df"), + "cbsdw_dwd.dwd_test_df" + ); } #[test] @@ -413,6 +430,17 @@ mod tests { }), "SELECT TOP (100) [id], [name] FROM [dbo].[users] ORDER BY [id] ASC" ); + assert_eq!( + build_table_select_sql(TableSelectSqlOptions { + database_type: Some(DatabaseType::Jdbc), + schema: Some("cbsdw_dwd"), + table_name: "dwd_test_df", + columns: &[], + order_columns: &[], + limit: 100, + }), + "SELECT * FROM cbsdw_dwd.dwd_test_df LIMIT 100;" + ); } #[test] diff --git a/packages/app-tests/tableSelectSql.test.ts b/packages/app-tests/tableSelectSql.test.ts new file mode 100644 index 000000000..a8d9f49fc --- /dev/null +++ b/packages/app-tests/tableSelectSql.test.ts @@ -0,0 +1,17 @@ +import { strict as assert } from "node:assert"; +import test from "node:test"; +import { qualifiedTableName, quoteTableIdentifier } from "../../apps/desktop/src/lib/tableSelectSql.ts"; + +test("JDBC table identifiers avoid double quotes for Kyuubi-compatible names", () => { + assert.equal(quoteTableIdentifier("jdbc", "cbsdw_dwd"), "cbsdw_dwd"); + assert.equal(quoteTableIdentifier("jdbc", "dwd_test_df"), "dwd_test_df"); + assert.equal( + qualifiedTableName({ databaseType: "jdbc", schema: "cbsdw_dwd", tableName: "dwd_test_df" }), + "cbsdw_dwd.dwd_test_df", + ); +}); + +test("JDBC table identifiers use backticks only when quoting is needed", () => { + assert.equal(quoteTableIdentifier("jdbc", "daily orders"), "`daily orders`"); + assert.equal(quoteTableIdentifier("jdbc", "daily`orders"), "`daily``orders`"); +});