feat(xugu): expose programmable object metadata

This commit is contained in:
Elias authored and GitHub committed 2026-07-17 17:53:44 +08:00
1 parent d3887c9936
commit 08cd8903f4
36 files changed
+431 -62

No files matched your search

@@ -40,6 +40,7 @@ pub async fn list_objects(pool: &MySqlPool, database: &str) -> Result<Vec<Object
name: table.name,
object_type: "TABLE".to_string(),
schema: Some(database.to_string()),
valid: None,
signature: None,
comment: table.comment,
created_at: None,
@@ -100,6 +101,7 @@ fn plugin_object(
name: name.to_string(),
object_type: "FUNCTION".to_string(),
schema: Some(database.to_string()),
valid: None,
signature: None,
comment: if comment_parts.is_empty() { None } else { Some(comment_parts.join(", ")) },
created_at: None,
+2
View File
@@ -2148,6 +2148,7 @@ fn row_to_object(row: &mysql_async::Row, database: &str) -> ObjectInfo {
name: get_str_by_name(row, "object_name"),
object_type: get_str_by_name(row, "object_type"),
schema: Some(database.to_string()),
valid: None,
signature: None,
comment: get_opt_str(row, "object_comment")
.map(|s| fix_potential_double_encoding(&s))
@@ -2283,6 +2284,7 @@ pub async fn list_table_objects_show(pool: &MySqlPool, database: &str) -> Result
name: table.name,
object_type: if table.table_type.eq_ignore_ascii_case("VIEW") { "VIEW" } else { "TABLE" }.to_string(),
schema: Some(database.to_string()),
valid: None,
signature: None,
comment: table.comment,
created_at: meta.and_then(|meta| meta.created_at.clone()),
+1
View File
@@ -104,6 +104,7 @@ pub async fn list_objects(pool: &mysql_async::Pool, schema: &str) -> Result<Vec<
name: get_str(row, 0),
object_type: get_str(row, 1),
schema: Some(schema.to_string()),
valid: None,
signature: None,
comment: None,
created_at: None,
+1
View File
@@ -2125,6 +2125,7 @@ pub async fn list_objects(pool: &Pool, schema: &str) -> Result<Vec<ObjectInfo>,
name: pg_row_try_string(row, 0),
object_type: pg_row_try_string(row, 1),
schema: Some(schema.to_string()),
valid: None,
comment: row.try_get::<_, Option<String>>(2).ok().flatten().filter(|s| !s.is_empty()),
created_at: row.try_get::<_, Option<String>>(3).ok().flatten().filter(|s| !s.is_empty()),
updated_at: row.try_get::<_, Option<String>>(4).ok().flatten().filter(|s| !s.is_empty()),
+1
View File
@@ -12,6 +12,7 @@ pub async fn list_objects(pool: &Pool, schema: &str) -> Result<Vec<ObjectInfo>,
name: t.name.clone(),
object_type: t.table_type.clone(),
schema: None,
valid: None,
signature: None,
comment: t.comment.clone(),
created_at: None,
+1
View File
@@ -1330,6 +1330,7 @@ pub async fn list_objects(client: &mut SqlServerClient, schema: &str) -> Result<
name: row.get::<&str, _>(0).unwrap_or("").to_string(),
object_type: row.get::<&str, _>(1).unwrap_or("TABLE").to_string(),
schema: Some(schema.to_string()),
valid: None,
signature: None,
comment: row.get::<&str, _>(4).filter(|s: &&str| !s.is_empty()).map(|s: &str| s.to_string()),
created_at: row.get::<chrono::NaiveDateTime, _>(2).map(|value| value.to_string()),
+17
View File
@@ -320,9 +320,12 @@ fn object_type_keyword(object_type: &ObjectSourceKind) -> &'static str {
ObjectSourceKind::MaterializedView => "MATERIALIZED_VIEW",
ObjectSourceKind::Procedure => "PROCEDURE",
ObjectSourceKind::Function => "FUNCTION",
ObjectSourceKind::Trigger => "TRIGGER",
ObjectSourceKind::Sequence => "SEQUENCE",
ObjectSourceKind::Package => "PACKAGE",
ObjectSourceKind::PackageBody => "PACKAGE BODY",
ObjectSourceKind::Type => "TYPE",
ObjectSourceKind::TypeBody => "TYPE BODY",
}
}
@@ -819,12 +822,18 @@ fn parse_object_source_kind(value: &str) -> Option<ObjectSourceKind> {
Some(ObjectSourceKind::Procedure)
} else if value.eq_ignore_ascii_case("FUNCTION") {
Some(ObjectSourceKind::Function)
} else if value.eq_ignore_ascii_case("TRIGGER") {
Some(ObjectSourceKind::Trigger)
} else if value.eq_ignore_ascii_case("SEQUENCE") {
Some(ObjectSourceKind::Sequence)
} else if value.eq_ignore_ascii_case("PACKAGE") {
Some(ObjectSourceKind::Package)
} else if value.eq_ignore_ascii_case("PACKAGE BODY") || value.eq_ignore_ascii_case("PACKAGE_BODY") {
Some(ObjectSourceKind::PackageBody)
} else if value.eq_ignore_ascii_case("TYPE") {
Some(ObjectSourceKind::Type)
} else if value.eq_ignore_ascii_case("TYPE BODY") || value.eq_ignore_ascii_case("TYPE_BODY") {
Some(ObjectSourceKind::TypeBody)
} else {
None
}
@@ -1251,6 +1260,14 @@ mod tests {
assert_eq!(sql, "CREATE OR REPLACE PACKAGE BODY PAYROLL AS\nEND PAYROLL;");
}
#[test]
fn parses_programmable_metadata_object_kinds() {
assert_eq!(parse_object_source_kind("TRIGGER"), Some(ObjectSourceKind::Trigger));
assert_eq!(parse_object_source_kind("TYPE"), Some(ObjectSourceKind::Type));
assert_eq!(parse_object_source_kind("TYPE_BODY"), Some(ObjectSourceKind::TypeBody));
assert_eq!(parse_object_source_kind("PACKAGE BODY"), Some(ObjectSourceKind::PackageBody));
}
#[test]
fn postgres_procedure_rename_adds_drop_cleanup() {
let statements = build_executable_object_source_statements(input(
+27 -3
View File
@@ -2219,6 +2219,7 @@ async fn external_driver_presto_like_objects(
name: table.name,
object_type: table.table_type,
schema: Some(schema.to_string()),
valid: None,
signature: None,
comment: table.comment,
created_at: None,
@@ -2601,6 +2602,7 @@ mod tests {
name: name.to_string(),
object_type: object_type.to_string(),
schema: Some("app".to_string()),
valid: None,
signature: None,
comment: None,
created_at: None,
@@ -3318,6 +3320,7 @@ mod tests {
name: "ORDERS".to_string(),
object_type: "TABLE".to_string(),
schema: Some("DBX_TEST".to_string()),
valid: None,
signature: None,
comment: None,
created_at: None,
@@ -3329,6 +3332,7 @@ mod tests {
name: "ORDERS_VIEW".to_string(),
object_type: "VIEW".to_string(),
schema: Some("DBX_TEST".to_string()),
valid: None,
signature: None,
comment: None,
created_at: None,
@@ -3340,6 +3344,7 @@ mod tests {
name: "REFRESH_ORDERS".to_string(),
object_type: "PROCEDURE".to_string(),
schema: Some("DBX_TEST".to_string()),
valid: None,
signature: None,
comment: None,
created_at: None,
@@ -3763,6 +3768,7 @@ async fn list_objects_once(
name: table.name,
object_type: table.table_type,
schema: None,
valid: None,
signature: None,
comment: table.comment,
created_at: None,
@@ -3940,6 +3946,7 @@ async fn list_objects_once(
name: table.name,
object_type: table.table_type,
schema: if schema.is_empty() { None } else { Some(schema.to_string()) },
valid: None,
signature: None,
comment: table.comment,
created_at: None,
@@ -4920,9 +4927,12 @@ fn sqlite_object_type(kind: &db::ObjectSourceKind) -> &'static str {
db::ObjectSourceKind::View | db::ObjectSourceKind::MaterializedView => "view",
db::ObjectSourceKind::Procedure
| db::ObjectSourceKind::Function
| db::ObjectSourceKind::Trigger
| db::ObjectSourceKind::Sequence
| db::ObjectSourceKind::Package
| db::ObjectSourceKind::PackageBody => "routine",
| db::ObjectSourceKind::PackageBody
| db::ObjectSourceKind::Type
| db::ObjectSourceKind::TypeBody => "routine",
}
}
@@ -4931,9 +4941,12 @@ fn sqlserver_object_type_filter(kind: &db::ObjectSourceKind) -> &'static str {
db::ObjectSourceKind::View => "'V'",
db::ObjectSourceKind::Procedure => "'P'",
db::ObjectSourceKind::Function => "'FN','IF','TF','FS','FT'",
db::ObjectSourceKind::Trigger => "'TR'",
db::ObjectSourceKind::Sequence
| db::ObjectSourceKind::Package
| db::ObjectSourceKind::PackageBody
| db::ObjectSourceKind::Type
| db::ObjectSourceKind::TypeBody
| db::ObjectSourceKind::MaterializedView => "''",
}
}
@@ -5055,7 +5068,11 @@ fn postgres_object_source_sql_inner(
sql_string(name)
)
}
db::ObjectSourceKind::Package | db::ObjectSourceKind::PackageBody => "SELECT NULL WHERE FALSE".to_string(),
db::ObjectSourceKind::Trigger
| db::ObjectSourceKind::Package
| db::ObjectSourceKind::PackageBody
| db::ObjectSourceKind::Type
| db::ObjectSourceKind::TypeBody => "SELECT NULL WHERE FALSE".to_string(),
}
}
@@ -5065,9 +5082,12 @@ pub fn oracle_object_source_sql(schema: &str, name: &str, kind: &db::ObjectSourc
db::ObjectSourceKind::MaterializedView => "MATERIALIZED_VIEW",
db::ObjectSourceKind::Procedure => "PROCEDURE",
db::ObjectSourceKind::Function => "FUNCTION",
db::ObjectSourceKind::Trigger => "TRIGGER",
db::ObjectSourceKind::Sequence => "SEQUENCE",
db::ObjectSourceKind::Package => "PACKAGE",
db::ObjectSourceKind::PackageBody => "PACKAGE_BODY",
db::ObjectSourceKind::Type => "TYPE",
db::ObjectSourceKind::TypeBody => "TYPE_BODY",
};
if schema.trim().is_empty() {
format!("SELECT DBMS_METADATA.GET_DDL({}, {}) FROM DUAL", sql_string(object_type), sql_string(name))
@@ -5095,9 +5115,12 @@ pub fn mysql_object_source_sql(database: &str, name: &str, kind: &db::ObjectSour
db::ObjectSourceKind::View => format!("SHOW CREATE VIEW {qualified_name}"),
db::ObjectSourceKind::Procedure => format!("SHOW CREATE PROCEDURE {qualified_name}"),
db::ObjectSourceKind::Function => format!("SHOW CREATE FUNCTION {qualified_name}"),
db::ObjectSourceKind::Sequence
db::ObjectSourceKind::Trigger
| db::ObjectSourceKind::Sequence
| db::ObjectSourceKind::Package
| db::ObjectSourceKind::PackageBody
| db::ObjectSourceKind::Type
| db::ObjectSourceKind::TypeBody
| db::ObjectSourceKind::MaterializedView => String::new(),
}
}
@@ -5313,6 +5336,7 @@ async fn oracle_agent_list_objects(
name,
object_type,
schema,
valid: None,
signature: None,
comment: None,
created_at: None,
@@ -235,6 +235,7 @@ mod tests {
name: "orders".to_string(),
object_type: "BASE TABLE".to_string(),
schema: None,
valid: None,
signature: None,
comment: None,
created_at: None,
@@ -246,6 +247,7 @@ mod tests {
name: "active_orders".to_string(),
object_type: "MATERIALIZED_VIEW".to_string(),
schema: None,
valid: None,
signature: None,
comment: None,
created_at: None,
@@ -257,6 +259,7 @@ mod tests {
name: "payroll".to_string(),
object_type: "PACKAGE BODY".to_string(),
schema: None,
valid: None,
signature: None,
comment: None,
created_at: None,
@@ -308,6 +311,7 @@ mod tests {
name: "ORDERS".to_string(),
object_type: "TABLE".to_string(),
schema: Some("HR".to_string()),
valid: None,
signature: None,
comment: None,
created_at: None,
@@ -319,6 +323,7 @@ mod tests {
name: "bin$deleted".to_string(),
object_type: "TABLE".to_string(),
schema: Some("HR".to_string()),
valid: None,
signature: None,
comment: None,
created_at: None,
+3
View File
@@ -4594,6 +4594,9 @@ where
db::ObjectSourceKind::Sequence | db::ObjectSourceKind::Package | db::ObjectSourceKind::PackageBody => {
object.source.clone()
}
db::ObjectSourceKind::Trigger | db::ObjectSourceKind::Type | db::ObjectSourceKind::TypeBody => {
object.source.clone()
}
};
let statements = build_executable_object_source_statements(EditableObjectSourceSqlInput {
database_type: DatabaseType::Postgres,
+20
View File
@@ -62,6 +62,8 @@ pub struct ObjectInfo {
pub object_type: String,
pub schema: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub valid: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub signature: Option<String>,
pub comment: Option<String>,
pub created_at: Option<String>,
@@ -93,9 +95,12 @@ pub enum ObjectSourceKind {
MaterializedView,
Procedure,
Function,
Trigger,
Sequence,
Package,
PackageBody,
Type,
TypeBody,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -310,3 +315,18 @@ pub struct OwnerInfo {
pub object_type: String,
pub owner: String,
}
#[cfg(test)]
mod tests {
use super::ObjectInfo;
#[test]
fn list_objects_payload_preserves_optional_validity() {
let objects: Vec<ObjectInfo> =
serde_json::from_str(r#"[{"name":"TRG_AUDIT","object_type":"TRIGGER","schema":"APP","valid":false}]"#)
.unwrap();
assert_eq!(objects[0].valid, Some(false));
assert_eq!(objects[0].object_type, "TRIGGER");
}
}