feat(doris): show external catalogs in sidebar
This commit is contained in:
1 parent
47d9257ac2
commit
d9bb92d1e9
36 files changed
+1680
-174
No files matched your search
@@ -71,6 +71,29 @@ pub fn qualified_table_name(database_type: Option<DatabaseType>, schema: Option<
|
||||
quote_table_identifier(database_type, table_name)
|
||||
}
|
||||
|
||||
/// Like `qualified_table_name`, but prefixes a Doris/StarRocks external
|
||||
/// catalog (`<catalog>.<schema>.<table>`) when `catalog` is present, non-empty,
|
||||
/// and not the engine's `internal` catalog. The `internal` guard is defensive —
|
||||
/// built-in-catalog tables never carry a catalog in the first place (the
|
||||
/// sidebar routes them through the standard path), so this only ever prefixes
|
||||
/// genuine external catalogs. Other engines ignore `catalog` (they have no
|
||||
/// 3-part catalog naming).
|
||||
pub fn qualified_table_name_with_catalog(
|
||||
database_type: Option<DatabaseType>,
|
||||
catalog: Option<&str>,
|
||||
schema: Option<&str>,
|
||||
table_name: &str,
|
||||
) -> String {
|
||||
let table = qualified_table_name(database_type, schema, table_name);
|
||||
let catalog = catalog.map(str::trim).filter(|catalog| !catalog.is_empty() && *catalog != "internal");
|
||||
match (catalog, database_type) {
|
||||
(Some(catalog), Some(DatabaseType::Doris | DatabaseType::StarRocks)) => {
|
||||
format!("{}.{}", quote_table_identifier(database_type, catalog), table)
|
||||
}
|
||||
_ => table,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn quote_table_identifier(database_type: Option<DatabaseType>, name: &str) -> String {
|
||||
match database_type {
|
||||
Some(DatabaseType::Iotdb) => name.to_string(),
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use crate::models::connection::DatabaseType;
|
||||
|
||||
use super::capabilities::{firebird_rows_clause, table_pagination_strategy, uses_fetch_first, TablePaginationStrategy};
|
||||
use super::identifiers::{normalize_where_input, qualified_table_name, quote_table_identifier};
|
||||
use super::identifiers::{
|
||||
normalize_where_input, qualified_table_name, qualified_table_name_with_catalog, quote_table_identifier,
|
||||
};
|
||||
use super::types::{
|
||||
TableDataSelectSqlOptions, TableSelectSqlOptions, DBX_NEO4J_ELEMENT_ID_COLUMN, DBX_ROWID_COLUMN,
|
||||
DBX_TDENGINE_TBNAME_COLUMN,
|
||||
@@ -18,7 +20,13 @@ pub fn build_table_data_select_sql(options: TableDataSelectSqlOptions) -> String
|
||||
return build_neo4j_table_select_sql(&options, limit);
|
||||
}
|
||||
|
||||
let table = qualified_table_name(database_type, options.schema.as_deref(), &options.table_name);
|
||||
// Doris / StarRocks multi-catalog: prefix the catalog for external-catalog tables.
|
||||
let table = qualified_table_name_with_catalog(
|
||||
database_type,
|
||||
options.catalog.as_deref(),
|
||||
options.schema.as_deref(),
|
||||
&options.table_name,
|
||||
);
|
||||
let predicate = normalize_where_input(options.where_input.as_deref());
|
||||
let where_clause = if predicate.is_empty() { String::new() } else { format!(" WHERE ({predicate})") };
|
||||
let default_order_by = if database_type == Some(DatabaseType::InfluxDb) {
|
||||
@@ -399,3 +407,66 @@ pub(super) fn build_questdb_table_select_sql(
|
||||
let upper_bound = offset + limit;
|
||||
format!("SELECT {columns_sql} FROM {table}{where_clause}{order_by} LIMIT {offset}, {upper_bound}")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn opts(database_type: DatabaseType, catalog: Option<&str>, table: &str) -> TableDataSelectSqlOptions {
|
||||
TableDataSelectSqlOptions {
|
||||
database_type: Some(database_type),
|
||||
schema: None,
|
||||
table_name: table.to_string(),
|
||||
catalog: catalog.map(|c| c.to_string()),
|
||||
table_type: None,
|
||||
primary_keys: Vec::new(),
|
||||
columns: Vec::new(),
|
||||
fallback_order_columns: Vec::new(),
|
||||
order_by: None,
|
||||
limit: Some(10),
|
||||
offset: None,
|
||||
where_input: None,
|
||||
include_row_id: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doris_external_catalog_prefixes_from_clause() {
|
||||
let sql = build_table_data_select_sql(opts(DatabaseType::Doris, Some("iceberg_catalog"), "orders"));
|
||||
assert!(sql.contains("FROM `iceberg_catalog`.`orders`"), "sql was: {sql}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn starrocks_external_catalog_prefixes_from_clause() {
|
||||
let sql = build_table_data_select_sql(opts(DatabaseType::StarRocks, Some("hive_catalog"), "orders"));
|
||||
assert!(sql.contains("FROM `hive_catalog`.`orders`"), "sql was: {sql}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doris_internal_catalog_is_not_prefixed() {
|
||||
let sql = build_table_data_select_sql(opts(DatabaseType::Doris, Some("internal"), "orders"));
|
||||
assert!(!sql.contains("internal"), "sql was: {sql}");
|
||||
assert!(sql.contains("FROM `orders`"), "sql was: {sql}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doris_empty_catalog_is_not_prefixed() {
|
||||
let sql = build_table_data_select_sql(opts(DatabaseType::Doris, Some(" "), "orders"));
|
||||
assert!(sql.contains("FROM `orders`"), "sql was: {sql}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doris_no_catalog_is_not_prefixed() {
|
||||
let sql = build_table_data_select_sql(opts(DatabaseType::Doris, None, "orders"));
|
||||
assert!(sql.contains("FROM `orders`"), "sql was: {sql}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_catalog_is_ignored_for_non_doris_engines() {
|
||||
// Postgres does not support the 3-part catalog naming; the catalog
|
||||
// must be ignored to avoid emitting an invalid qualified name.
|
||||
let sql = build_table_data_select_sql(opts(DatabaseType::Postgres, Some("iceberg_catalog"), "orders"));
|
||||
assert!(!sql.contains("iceberg_catalog"), "sql was: {sql}");
|
||||
assert!(sql.contains("orders"), "sql was: {sql}");
|
||||
}
|
||||
}
|
||||
@@ -258,6 +258,7 @@ fn builds_table_data_where_and_schema_queries() {
|
||||
offset: None,
|
||||
where_input: Some("where status = 'active'".to_string()),
|
||||
include_row_id: false,
|
||||
..Default::default()
|
||||
}),
|
||||
"SELECT * FROM `users` WHERE (status = 'active') LIMIT 100;"
|
||||
);
|
||||
@@ -275,6 +276,7 @@ fn builds_table_data_where_and_schema_queries() {
|
||||
offset: None,
|
||||
where_input: None,
|
||||
include_row_id: false,
|
||||
..Default::default()
|
||||
}),
|
||||
"SELECT * FROM `sys_dic` LIMIT 100;"
|
||||
);
|
||||
@@ -292,6 +294,7 @@ fn builds_table_data_where_and_schema_queries() {
|
||||
offset: Some(100),
|
||||
where_input: Some("WHERE amount > 10".to_string()),
|
||||
include_row_id: false,
|
||||
..Default::default()
|
||||
}),
|
||||
"SELECT * FROM \"public\".\"orders\" WHERE (amount > 10) LIMIT 50 OFFSET 100;"
|
||||
);
|
||||
@@ -309,6 +312,7 @@ fn builds_table_data_where_and_schema_queries() {
|
||||
offset: None,
|
||||
where_input: None,
|
||||
include_row_id: false,
|
||||
..Default::default()
|
||||
}),
|
||||
"SELECT * FROM \"DBX_TEST\".\"PRODUCTS\" LIMIT 100;"
|
||||
);
|
||||
@@ -326,6 +330,7 @@ fn builds_table_data_where_and_schema_queries() {
|
||||
offset: None,
|
||||
where_input: Some("`customer_name` = 'Acme'".to_string()),
|
||||
include_row_id: false,
|
||||
..Default::default()
|
||||
}),
|
||||
"SELECT * FROM `sales_report` WHERE (`customer_name` = 'Acme') LIMIT 100;"
|
||||
);
|
||||
@@ -343,6 +348,7 @@ fn builds_table_data_where_and_schema_queries() {
|
||||
offset: None,
|
||||
where_input: Some("WHERE amount > 10".to_string()),
|
||||
include_row_id: false,
|
||||
..Default::default()
|
||||
}),
|
||||
"SELECT * FROM \"DB2INST1\".\"ORDERS\" WHERE (amount > 10) FETCH FIRST 50 ROWS ONLY"
|
||||
);
|
||||
@@ -360,6 +366,7 @@ fn builds_table_data_where_and_schema_queries() {
|
||||
offset: Some(100),
|
||||
where_input: Some("WHERE amount > 10".to_string()),
|
||||
include_row_id: false,
|
||||
..Default::default()
|
||||
}),
|
||||
"SELECT * FROM \"ORDERS\" WHERE (amount > 10) ORDER BY \"ID\" ASC ROWS 101 TO 150"
|
||||
);
|
||||
@@ -377,6 +384,7 @@ fn builds_table_data_where_and_schema_queries() {
|
||||
offset: Some(100),
|
||||
where_input: Some("WHERE amount > 10".to_string()),
|
||||
include_row_id: false,
|
||||
..Default::default()
|
||||
}),
|
||||
"SELECT \"ID\", \"AMOUNT\" FROM (SELECT dbx_inner.*, ROWNUM AS \"__dbx_row_num\" FROM (SELECT \"ID\", \"AMOUNT\" FROM \"DBXTEST\".\"ORDERS\" WHERE (amount > 10) ORDER BY \"ID\" ASC) dbx_inner WHERE ROWNUM <= 150) WHERE \"__dbx_row_num\" > 100"
|
||||
);
|
||||
@@ -394,6 +402,7 @@ fn builds_table_data_where_and_schema_queries() {
|
||||
offset: None,
|
||||
where_input: None,
|
||||
include_row_id: false,
|
||||
..Default::default()
|
||||
}),
|
||||
"SELECT * FROM (SELECT * FROM \"DBXTEST\".\"ORDERS\") WHERE ROWNUM <= 100"
|
||||
);
|
||||
@@ -411,6 +420,7 @@ fn builds_table_data_where_and_schema_queries() {
|
||||
offset: None,
|
||||
where_input: Some("WHERE amount > 10".to_string()),
|
||||
include_row_id: false,
|
||||
..Default::default()
|
||||
}),
|
||||
"SELECT * FROM (SELECT * FROM \"DBXTEST\".\"ORDERS\" WHERE (amount > 10)) WHERE ROWNUM <= 50"
|
||||
);
|
||||
@@ -428,6 +438,7 @@ fn builds_table_data_where_and_schema_queries() {
|
||||
offset: Some(100),
|
||||
where_input: Some("WHERE amount > 10".to_string()),
|
||||
include_row_id: false,
|
||||
..Default::default()
|
||||
}),
|
||||
"SELECT \"ID\", \"AMOUNT\" FROM (SELECT dbx_t.\"ID\", dbx_t.\"AMOUNT\", ROW_NUMBER() OVER () AS \"__dbx_row_num\" FROM \"DB2INST1\".\"ORDERS\" dbx_t WHERE (amount > 10)) dbx_page WHERE \"__dbx_row_num\" > 100 AND \"__dbx_row_num\" <= 150 ORDER BY \"__dbx_row_num\""
|
||||
);
|
||||
@@ -445,6 +456,7 @@ fn builds_table_data_where_and_schema_queries() {
|
||||
offset: None,
|
||||
where_input: None,
|
||||
include_row_id: false,
|
||||
..Default::default()
|
||||
}),
|
||||
"SELECT TOP 100 * FROM \"Ens\".\"AlarmResponse\""
|
||||
);
|
||||
@@ -462,6 +474,7 @@ fn builds_table_data_where_and_schema_queries() {
|
||||
offset: None,
|
||||
where_input: None,
|
||||
include_row_id: false,
|
||||
..Default::default()
|
||||
}),
|
||||
"SELECT * FROM root.test.device2 LIMIT 100;"
|
||||
);
|
||||
@@ -483,6 +496,7 @@ fn builds_informix_table_data_with_skip_first_pagination() {
|
||||
offset: Some(100),
|
||||
where_input: Some("WHERE active = 1".to_string()),
|
||||
include_row_id: false,
|
||||
..Default::default()
|
||||
}),
|
||||
"SELECT SKIP 100 FIRST 50 * FROM users WHERE (active = 1)"
|
||||
);
|
||||
@@ -516,6 +530,7 @@ fn explicit_table_data_order_is_preserved() {
|
||||
offset: None,
|
||||
where_input: None,
|
||||
include_row_id: false,
|
||||
..Default::default()
|
||||
}),
|
||||
"SELECT * FROM \"public\".\"country_gdp\" ORDER BY \"iso3\" ASC LIMIT 100;"
|
||||
);
|
||||
@@ -536,6 +551,7 @@ fn builds_iris_table_data_sql_with_literal_top_and_quoted_object() {
|
||||
offset: None,
|
||||
where_input: Some("WHERE \"Status\" = 'Open'".to_string()),
|
||||
include_row_id: false,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
@@ -569,6 +585,7 @@ fn builds_table_data_special_column_queries() {
|
||||
offset: None,
|
||||
where_input: None,
|
||||
include_row_id: false,
|
||||
..Default::default()
|
||||
}),
|
||||
"SELECT tbname, `ts` AS `ts`, `current` AS `current`, `voltage` AS `voltage`, `location` AS `location`, `groupid` AS `groupid` FROM `test_db`.`meters` LIMIT 100;"
|
||||
);
|
||||
@@ -586,6 +603,7 @@ fn builds_table_data_special_column_queries() {
|
||||
offset: None,
|
||||
where_input: None,
|
||||
include_row_id: false,
|
||||
..Default::default()
|
||||
}),
|
||||
"SELECT `ts` AS `ts`, `current` AS `current` FROM `test_db`.`d1001` LIMIT 100;"
|
||||
);
|
||||
@@ -603,6 +621,7 @@ fn builds_table_data_special_column_queries() {
|
||||
offset: None,
|
||||
where_input: None,
|
||||
include_row_id: false,
|
||||
..Default::default()
|
||||
}),
|
||||
"SELECT `ts` AS `ts`, `current` AS `current` FROM `test_db`.`d1001` LIMIT 100;"
|
||||
);
|
||||
@@ -620,6 +639,7 @@ fn builds_table_data_special_column_queries() {
|
||||
offset: None,
|
||||
where_input: None,
|
||||
include_row_id: false,
|
||||
..Default::default()
|
||||
}),
|
||||
"SELECT `id` AS `id`, `name` AS `name` FROM `departments` LIMIT 100;"
|
||||
);
|
||||
@@ -641,6 +661,7 @@ fn builds_sqlserver_table_data_pages() {
|
||||
offset: None,
|
||||
where_input: Some("where id = 1".to_string()),
|
||||
include_row_id: false,
|
||||
..Default::default()
|
||||
}),
|
||||
"SELECT TOP (25) * FROM [dbo].[accounts] WHERE (id = 1)"
|
||||
);
|
||||
@@ -658,6 +679,7 @@ fn builds_sqlserver_table_data_pages() {
|
||||
offset: Some(100),
|
||||
where_input: None,
|
||||
include_row_id: false,
|
||||
..Default::default()
|
||||
}),
|
||||
"WITH [dbx_page] AS (SELECT [order_id], [customer], ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS [__dbx_row_num] FROM [sales].[orders]) SELECT [order_id], [customer] FROM [dbx_page] WHERE [__dbx_row_num] > 100 AND [__dbx_row_num] <= 150 ORDER BY [__dbx_row_num]"
|
||||
);
|
||||
@@ -679,6 +701,7 @@ fn builds_oracle_and_neo4j_table_data_queries() {
|
||||
offset: None,
|
||||
where_input: None,
|
||||
include_row_id: true,
|
||||
..Default::default()
|
||||
}),
|
||||
"SELECT * FROM (SELECT ROWIDTOCHAR(t.ROWID) AS \"__DBX_ROWID\", t.* FROM \"DBXTEST\".\"DBX_LOAD_TABLE_006\" t) WHERE ROWNUM <= 100"
|
||||
);
|
||||
@@ -696,6 +719,7 @@ fn builds_oracle_and_neo4j_table_data_queries() {
|
||||
offset: None,
|
||||
where_input: None,
|
||||
include_row_id: true,
|
||||
..Default::default()
|
||||
}),
|
||||
"SELECT \"__DBX_ROWID\", \"ID\", \"NAME\" FROM (SELECT ROWIDTOCHAR(t.ROWID) AS \"__DBX_ROWID\", t.* FROM \"DBXTEST\".\"DBX_LOAD_TABLE_006\" t) WHERE ROWNUM <= 100"
|
||||
);
|
||||
@@ -713,6 +737,7 @@ fn builds_oracle_and_neo4j_table_data_queries() {
|
||||
offset: None,
|
||||
where_input: None,
|
||||
include_row_id: true,
|
||||
..Default::default()
|
||||
}),
|
||||
"SELECT \"ID\", \"NAME\" FROM (SELECT \"ID\", \"NAME\" FROM \"DBXTEST\".\"DBX_JOIN_VIEW\") WHERE ROWNUM <= 100"
|
||||
);
|
||||
@@ -730,6 +755,7 @@ fn builds_oracle_and_neo4j_table_data_queries() {
|
||||
offset: None,
|
||||
where_input: None,
|
||||
include_row_id: false,
|
||||
..Default::default()
|
||||
}),
|
||||
"MATCH (n:`Employee`) RETURN elementId(n) AS `__DBX_ELEMENT_ID`, n.`id` AS `id`, n.`first name` AS `first name`, n.`role` AS `role` LIMIT 100;"
|
||||
);
|
||||
|
||||
@@ -16,7 +16,7 @@ pub struct TableSelectSqlOptions<'a> {
|
||||
pub limit: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TableDataSelectSqlOptions {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
@@ -24,6 +24,11 @@ pub struct TableDataSelectSqlOptions {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub schema: Option<String>,
|
||||
pub table_name: String,
|
||||
/// Doris / StarRocks multi-catalog: when set to a non-`internal` catalog,
|
||||
/// the FROM clause is prefixed with the catalog
|
||||
/// (`<catalog>.<schema>.<table>`).
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub catalog: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub table_type: Option<String>,
|
||||
#[serde(default)]
|
||||
|
||||
Reference in new issue
Block a user