fix: support metadata comment search

Co-authored-by: zipg <4047349+zipg@users.noreply.github.com>
This commit is contained in:
zipgandzipg authored and GitHub committed 2026-07-06 16:49:28 +08:00
1 parent 745f7f60b0
commit f2c429afd9
3 files changed
+124 -21

No files matched your search

@@ -102,13 +102,21 @@ public final class MetadataListConstraints {
} }
public boolean nameMatches(String name) { public boolean nameMatches(String name) {
return textMatches(name);
}
public boolean nameOrCommentMatches(String name, String comment) {
return textMatches(name) || textMatches(comment);
}
private boolean textMatches(String text) {
if (filter.isEmpty()) { if (filter.isEmpty()) {
return true; return true;
} }
if (name == null) { if (text == null) {
return false; return false;
} }
String candidate = name.toLowerCase(Locale.ROOT); String candidate = text.toLowerCase(Locale.ROOT);
return candidate.contains(filter) || (filter.length() >= 2 && fuzzySubsequenceMatches(candidate, filter)); return candidate.contains(filter) || (filter.length() >= 2 && fuzzySubsequenceMatches(candidate, filter));
} }
@@ -118,7 +126,8 @@ public final class MetadataListConstraints {
int max = limit == null ? Integer.MAX_VALUE : limit; int max = limit == null ? Integer.MAX_VALUE : limit;
int start = offset == null ? 0 : offset; int start = offset == null ? 0 : offset;
for (TableInfo table : tables) { for (TableInfo table : tables) {
if (!nameMatches(table.getName()) || !tableTypeAllowed(table.getTable_type())) { if (!nameOrCommentMatches(table.getName(), table.getComment())
|| !tableTypeAllowed(table.getTable_type())) {
continue; continue;
} }
if (skipped++ < start) { if (skipped++ < start) {
@@ -138,7 +147,8 @@ public final class MetadataListConstraints {
int max = limit == null ? Integer.MAX_VALUE : limit; int max = limit == null ? Integer.MAX_VALUE : limit;
int start = offset == null ? 0 : offset; int start = offset == null ? 0 : offset;
for (ObjectInfo object : objects) { for (ObjectInfo object : objects) {
if (!nameMatches(object.getName()) || !objectTypeAllowed(object.getObject_type())) { if (!nameOrCommentMatches(object.getName(), object.getComment())
|| !objectTypeAllowed(object.getObject_type())) {
continue; continue;
} }
if (skipped++ < start) { if (skipped++ < start) {
@@ -302,6 +302,29 @@ class CommonJavaCompatibilityTest {
assertEquals("usage_view", objects.get(0).getName()); assertEquals("usage_view", objects.get(0).getName());
} }
@Test
void metadataConstraintsMatchTableAndObjectComments() {
MetadataListConstraints tableConstraints =
new MetadataListConstraints("account", null, null, Collections.singletonList("TABLE"));
List<TableInfo> tables = tableConstraints.filterTables(Arrays.asList(
new TableInfo("orders", "TABLE", "sales archive"),
new TableInfo("profile", "TABLE", "customer account data"),
new TableInfo("account_view", "VIEW", "ignored by type")
));
assertEquals(1, tables.size());
assertEquals("profile", tables.get(0).getName());
MetadataListConstraints objectConstraints =
new MetadataListConstraints("revenue", null, null, Collections.singletonList("VIEW"));
List<ObjectInfo> objects = objectConstraints.filterObjects(Arrays.asList(
new ObjectInfo("order_view", "VIEW", "public", "monthly revenue summary"),
new ObjectInfo("sync_user", "PROCEDURE", "public", "sync revenue data"),
new ObjectInfo("audit_log", "TABLE", "public", "audit records")
));
assertEquals(1, objects.size());
assertEquals("order_view", objects.get(0).getName());
}
@Test @Test
void executesTransactionsOneByOneWhenJdbcDriverDoesNotSupportTransactions() { void executesTransactionsOneByOneWhenJdbcDriverDoesNotSupportTransactions() {
List<String> calls = new ArrayList<>(); List<String> calls = new ArrayList<>();
+87 -17
View File
@@ -1529,17 +1529,32 @@ async fn list_tables_once(
if let Some(client) = extract_pool!(&connections, &pool_key, Agent) { if let Some(client) = extract_pool!(&connections, &pool_key, Agent) {
let is_oracle = db_config.as_ref().is_some_and(|config| config.db_type == DatabaseType::Oracle); let is_oracle = db_config.as_ref().is_some_and(|config| config.db_type == DatabaseType::Oracle);
let use_oracle_agent_paging = db_config.as_ref().is_some_and(is_default_oracle_agent_config); let use_oracle_agent_paging = db_config.as_ref().is_some_and(is_default_oracle_agent_config);
let filter_locally_after_oracle_comments =
is_oracle && filter.is_some_and(|filter| !filter.trim().is_empty());
let timeout_duration = agent_metadata_timeout(db_config.as_ref()); let timeout_duration = agent_metadata_timeout(db_config.as_ref());
let fallback_config = db_config.clone(); let fallback_config = db_config.clone();
drop(connections); drop(connections);
let mut client = client.lock().await; let mut client = client.lock().await;
let agent_limit = if use_oracle_agent_paging { limit } else { None }; let agent_filter = if filter_locally_after_oracle_comments { None } else { filter };
let agent_offset = if use_oracle_agent_paging { offset } else { None }; let agent_limit = if filter_locally_after_oracle_comments {
None
} else if use_oracle_agent_paging {
limit
} else {
None
};
let agent_offset = if filter_locally_after_oracle_comments {
None
} else if use_oracle_agent_paging {
offset
} else {
None
};
match client match client
.list_tables_constrained::<Vec<db::TableInfo>>( .list_tables_constrained::<Vec<db::TableInfo>>(
database, database,
schema, schema,
filter, agent_filter,
agent_limit, agent_limit,
agent_offset, agent_offset,
object_types, object_types,
@@ -1547,14 +1562,7 @@ async fn list_tables_once(
) )
.await .await
{ {
Ok(tables) if !tables.is_empty() => { Ok(mut tables) if !tables.is_empty() => {
let final_offset =
if oracle_agent_paging_likely_applied(use_oracle_agent_paging, limit, tables.len()) {
Some(0)
} else {
offset
};
let mut tables = filter_table_infos(tables, filter, limit, final_offset, object_types);
if is_oracle { if is_oracle {
load_oracle_table_comments_for_tables( load_oracle_table_comments_for_tables(
&mut client, &mut client,
@@ -1565,6 +1573,14 @@ async fn list_tables_once(
) )
.await?; .await?;
} }
let final_offset = if filter_locally_after_oracle_comments {
offset
} else if oracle_agent_paging_likely_applied(use_oracle_agent_paging, limit, tables.len()) {
Some(0)
} else {
offset
};
let tables = filter_table_infos(tables, filter, limit, final_offset, object_types);
return Ok(tables); return Ok(tables);
} }
Ok(tables) => { Ok(tables) => {
@@ -1703,7 +1719,7 @@ fn filter_table_infos(
let offset = offset.unwrap_or(0); let offset = offset.unwrap_or(0);
tables tables
.into_iter() .into_iter()
.filter(|table| crate::sql::contains_or_fuzzy_match(&table.name, filter)) .filter(|table| metadata_name_or_comment_matches(&table.name, table.comment.as_deref(), filter))
.filter(|table| table_info_matches_object_types(table, object_types)) .filter(|table| table_info_matches_object_types(table, object_types))
.skip(offset) .skip(offset)
.take(limit) .take(limit)
@@ -1722,13 +1738,21 @@ fn filter_object_infos(
let offset = offset.unwrap_or(0); let offset = offset.unwrap_or(0);
objects objects
.into_iter() .into_iter()
.filter(|object| crate::sql::contains_or_fuzzy_match(&object.name, filter)) .filter(|object| metadata_name_or_comment_matches(&object.name, object.comment.as_deref(), filter))
.filter(|object| object_info_matches_object_types(object, object_types)) .filter(|object| object_info_matches_object_types(object, object_types))
.skip(offset) .skip(offset)
.take(limit) .take(limit)
.collect() .collect()
} }
fn metadata_name_or_comment_matches(name: &str, comment: Option<&str>, filter: &str) -> bool {
if filter.trim().is_empty() {
return true;
}
crate::sql::contains_or_fuzzy_match(name, filter)
|| comment.is_some_and(|comment| crate::sql::contains_or_fuzzy_match(comment, filter))
}
fn object_info_matches_object_types(object: &db::ObjectInfo, object_types: Option<&[String]>) -> bool { fn object_info_matches_object_types(object: &db::ObjectInfo, object_types: Option<&[String]>) -> bool {
let Some(object_types) = object_types else { let Some(object_types) = object_types else {
return true; return true;
@@ -2285,6 +2309,19 @@ mod tests {
assert_eq!(user_order.into_iter().map(|table| table.name).collect::<Vec<_>>(), vec!["user_order"]); assert_eq!(user_order.into_iter().map(|table| table.name).collect::<Vec<_>>(), vec!["user_order"]);
} }
#[test]
fn filter_table_infos_matches_comments() {
let mut orders = test_table_info("orders");
orders.comment = Some("sales archive".to_string());
let mut profile = test_table_info("profile");
profile.comment = Some("customer account data".to_string());
let tables = vec![orders, profile, test_table_info("logs")];
let filtered = filter_table_infos(tables, Some("account"), None, None, None);
assert_eq!(filtered.into_iter().map(|table| table.name).collect::<Vec<_>>(), vec!["profile"]);
}
#[test] #[test]
fn filter_table_infos_skips_fuzzy_for_single_character_filters() { fn filter_table_infos_skips_fuzzy_for_single_character_filters() {
let tables = vec![test_table_info("orders"), test_table_info("user_order")]; let tables = vec![test_table_info("orders"), test_table_info("user_order")];
@@ -2347,6 +2384,20 @@ mod tests {
assert_eq!(filtered[0].name, "fetch_name"); assert_eq!(filtered[0].name, "fetch_name");
} }
#[test]
fn filter_object_infos_matches_comments() {
let mut order_view = test_object_info("order_view", "VIEW");
order_view.comment = Some("monthly revenue summary".to_string());
let mut sync_user = test_object_info("sync_user", "PROCEDURE");
sync_user.comment = Some("sync account records".to_string());
let objects = vec![order_view, sync_user, test_object_info("audit_log", "TABLE")];
let object_types = vec!["VIEW".to_string()];
let filtered = filter_object_infos(objects, Some("revenue"), None, None, Some(&object_types));
assert_eq!(filtered.into_iter().map(|object| object.name).collect::<Vec<_>>(), vec!["order_view"]);
}
#[test] #[test]
fn presto_like_information_schema_sql_uses_catalog_and_schema_without_system_jdbc() { fn presto_like_information_schema_sql_uses_catalog_and_schema_without_system_jdbc() {
let sql = presto_like_information_schema_tables_sql("hive", "sales_analytics", None, None); let sql = presto_like_information_schema_tables_sql("hive", "sales_analytics", None, None);
@@ -2925,8 +2976,12 @@ pub async fn list_objects_core(
offset: Option<usize>, offset: Option<usize>,
object_types: Option<&[String]>, object_types: Option<&[String]>,
) -> Result<Vec<db::ObjectInfo>, String> { ) -> Result<Vec<db::ObjectInfo>, String> {
let db_config = connection_config(state, connection_id).await;
let filter_locally_after_oracle_comments = db_config.as_ref().is_some_and(|config| {
config.db_type == DatabaseType::Oracle && filter.is_some_and(|filter| !filter.trim().is_empty())
});
let use_oracle_agent_paging = let use_oracle_agent_paging =
connection_config(state, connection_id).await.as_ref().is_some_and(is_default_oracle_agent_config); db_config.as_ref().is_some_and(is_default_oracle_agent_config) && !filter_locally_after_oracle_comments;
retry_metadata_connection(state, connection_id, Some(database), || async { retry_metadata_connection(state, connection_id, Some(database), || async {
let objects = list_objects_once(state, connection_id, database, schema, filter, limit, offset, object_types) let objects = list_objects_once(state, connection_id, database, schema, filter, limit, offset, object_types)
.await .await
@@ -3322,6 +3377,8 @@ async fn list_objects_once(
if let Some(client) = extract_pool!(&connections, &pool_key, Agent) { if let Some(client) = extract_pool!(&connections, &pool_key, Agent) {
let is_oracle = db_config.as_ref().is_some_and(|config| config.db_type == DatabaseType::Oracle); let is_oracle = db_config.as_ref().is_some_and(|config| config.db_type == DatabaseType::Oracle);
let use_oracle_agent_paging = db_config.as_ref().is_some_and(is_default_oracle_agent_config); let use_oracle_agent_paging = db_config.as_ref().is_some_and(is_default_oracle_agent_config);
let filter_locally_after_oracle_comments =
is_oracle && filter.is_some_and(|filter| !filter.trim().is_empty());
let timeout_duration = agent_metadata_timeout(db_config.as_ref()); let timeout_duration = agent_metadata_timeout(db_config.as_ref());
let fallback_config = db_config.clone(); let fallback_config = db_config.clone();
drop(connections); drop(connections);
@@ -3329,13 +3386,26 @@ async fn list_objects_once(
return oracle_agent_list_objects(client, database, schema, timeout_duration).await; return oracle_agent_list_objects(client, database, schema, timeout_duration).await;
} }
let mut client = client.lock().await; let mut client = client.lock().await;
let agent_limit = if use_oracle_agent_paging { limit } else { None }; let agent_filter = if filter_locally_after_oracle_comments { None } else { filter };
let agent_offset = if use_oracle_agent_paging { offset } else { None }; let agent_limit = if filter_locally_after_oracle_comments {
None
} else if use_oracle_agent_paging {
limit
} else {
None
};
let agent_offset = if filter_locally_after_oracle_comments {
None
} else if use_oracle_agent_paging {
offset
} else {
None
};
match client match client
.list_objects_constrained::<Vec<db::ObjectInfo>>( .list_objects_constrained::<Vec<db::ObjectInfo>>(
database, database,
schema, schema,
filter, agent_filter,
agent_limit, agent_limit,
agent_offset, agent_offset,
object_types, object_types,