diff --git a/apps/desktop/src/components/objects/ObjectBrowser.vue b/apps/desktop/src/components/objects/ObjectBrowser.vue index 38ea6c577c..554977eb0d 100644 --- a/apps/desktop/src/components/objects/ObjectBrowser.vue +++ b/apps/desktop/src/components/objects/ObjectBrowser.vue @@ -354,8 +354,15 @@ async function openSource(row: ObjectBrowserRow) { sourceLoading.value = true; try { const result = await api.getObjectSource(props.connection.id, props.database, row.schema || selectedSchema.value || props.database, row.name, row.type as ObjectSourceKind); - sourceContent.value = result.source; - sourceDraft.value = result.source; + const editable = await api.buildEditableObjectSource({ + databaseType: effectiveDatabaseType.value, + objectType: row.type as ObjectSourceKind, + schema: row.schema || selectedSchema.value || props.database, + name: row.name, + source: result.source, + }); + sourceContent.value = editable; + sourceDraft.value = editable; sourceEditing.value = row.type !== "SEQUENCE"; } catch (e: any) { sourceError.value = e?.message || String(e); diff --git a/apps/desktop/src/lib/api.ts b/apps/desktop/src/lib/api.ts index 52f077cf25..1c70207e3a 100644 --- a/apps/desktop/src/lib/api.ts +++ b/apps/desktop/src/lib/api.ts @@ -156,6 +156,7 @@ export const buildDropSchemaSql = forward("buildDropSchemaSql"); export const buildDuplicateTableStructureSql = forward("buildDuplicateTableStructureSql"); export const buildExecutableObjectSourceStatements = forward("buildExecutableObjectSourceStatements"); export const buildExecutableObjectSourceSql = forward("buildExecutableObjectSourceSql"); +export const buildEditableObjectSource = forward("buildEditableObjectSource"); export const buildRoutineRenameObjectSourceStatements = forward("buildRoutineRenameObjectSourceStatements"); export const buildViewDdlSql = forward("buildViewDdlSql"); export const buildTableStructureChangeSql = forward("buildTableStructureChangeSql"); diff --git a/apps/desktop/src/lib/http.ts b/apps/desktop/src/lib/http.ts index a6447450ea..dc34e1400f 100644 --- a/apps/desktop/src/lib/http.ts +++ b/apps/desktop/src/lib/http.ts @@ -655,6 +655,10 @@ export async function buildExecutableObjectSourceSql(input: BuildEditableObjectS return post("/api/query/build-executable-object-source-sql", { input }); } +export async function buildEditableObjectSource(input: BuildEditableObjectSourceSqlInput): Promise { + return post("/api/query/build-editable-object-source", { input }); +} + export async function buildRoutineRenameObjectSourceStatements(input: BuildRoutineRenameObjectSourceInput): Promise { return post("/api/query/build-routine-rename-object-source-statements", { input }); } diff --git a/apps/desktop/src/lib/tauri.ts b/apps/desktop/src/lib/tauri.ts index f4ba30d457..a1d82cd158 100644 --- a/apps/desktop/src/lib/tauri.ts +++ b/apps/desktop/src/lib/tauri.ts @@ -681,6 +681,10 @@ export async function buildExecutableObjectSourceSql(input: BuildEditableObjectS return invoke("build_executable_object_source_sql", { input }); } +export async function buildEditableObjectSource(input: BuildEditableObjectSourceSqlInput): Promise { + return invoke("build_editable_object_source", { input }); +} + export async function buildRoutineRenameObjectSourceStatements(input: BuildRoutineRenameObjectSourceInput): Promise { return invoke("build_routine_rename_object_source_statements", { input }); } diff --git a/crates/dbx-core/src/object_source_sql.rs b/crates/dbx-core/src/object_source_sql.rs index 48de130600..782124ce99 100644 --- a/crates/dbx-core/src/object_source_sql.rs +++ b/crates/dbx-core/src/object_source_sql.rs @@ -150,6 +150,23 @@ pub fn build_executable_object_source_sql(input: EditableObjectSourceSqlInput) - Ok(build_executable_object_source_statements(input)?.join("\n")) } +/// Convert a raw database object source into a form suitable for the source editor. +/// +/// This is the *editable* presentation shown to the user when they open a view, +/// procedure, or function for editing. For SQL Server the raw `CREATE VIEW` / +/// `CREATE PROCEDURE` is rewritten to `CREATE OR ALTER` so the user doesn't see +/// a mismatched CREATE statement for an already-existing object. Callers that +/// only need the first statement should use this instead of calling +/// `build_executable_object_source_statements` and discarding rename-cleanup +/// statements. +pub fn build_editable_object_source(input: EditableObjectSourceSqlInput) -> String { + let source = input.source.clone(); + match build_executable_object_source_statements(input) { + Ok(statements) => statements.into_iter().next().unwrap_or_default(), + Err(_) => ensure_semicolon(source.trim()), + } +} + pub fn build_view_ddl_sql(input: BuildViewDdlInput) -> String { let source = input.source.trim(); if Regex::new(r"(?i)^(?:CREATE|ALTER)\s+").unwrap().is_match(source) { @@ -568,6 +585,30 @@ mod tests { ); } + #[test] + fn sqlserver_view_source_opened_for_editing_shows_create_or_alter() { + let sql = build_editable_object_source(EditableObjectSourceSqlInput { + database_type: DatabaseType::SqlServer, + object_type: ObjectSourceKind::View, + schema: Some("dbo".to_string()), + name: "v_active_users".to_string(), + source: "CREATE VIEW dbo.v_active_users AS SELECT id, name FROM users WHERE active = 1;".to_string(), + }); + assert_eq!(sql, "CREATE OR ALTER VIEW dbo.v_active_users AS SELECT id, name FROM users WHERE active = 1;"); + } + + #[test] + fn sqlserver_procedure_source_opened_for_editing_shows_create_or_alter() { + let sql = build_editable_object_source(EditableObjectSourceSqlInput { + database_type: DatabaseType::SqlServer, + object_type: ObjectSourceKind::Procedure, + schema: Some("dbo".to_string()), + name: "usp_demo".to_string(), + source: "CREATE PROCEDURE dbo.usp_demo AS SELECT 1;".to_string(), + }); + assert_eq!(sql, "CREATE OR ALTER PROCEDURE dbo.usp_demo AS SELECT 1;"); + } + #[test] fn oracle_family_routine_rename_rewrites_source_and_drops_original() { let statements = build_routine_rename_object_source_statements(RoutineRenameObjectSourceInput { diff --git a/crates/dbx-web/src/main.rs b/crates/dbx-web/src/main.rs index a9a0b62386..9eb8adfa4e 100644 --- a/crates/dbx-web/src/main.rs +++ b/crates/dbx-web/src/main.rs @@ -254,6 +254,7 @@ async fn main() { post(routes::query::build_executable_object_source_statements), ) .route("/query/build-executable-object-source-sql", post(routes::query::build_executable_object_source_sql)) + .route("/query/build-editable-object-source", post(routes::query::build_editable_object_source)) .route( "/query/build-routine-rename-object-source-statements", post(routes::query::build_routine_rename_object_source_statements), diff --git a/crates/dbx-web/src/routes/query.rs b/crates/dbx-web/src/routes/query.rs index 0053da3f60..100a2f01b6 100644 --- a/crates/dbx-web/src/routes/query.rs +++ b/crates/dbx-web/src/routes/query.rs @@ -615,6 +615,10 @@ pub async fn build_executable_object_source_sql( dbx_core::object_source_sql::build_executable_object_source_sql(req.input).map(Json).map_err(AppError) } +pub async fn build_editable_object_source(Json(req): Json) -> Json { + Json(dbx_core::object_source_sql::build_editable_object_source(req.input)) +} + pub async fn build_routine_rename_object_source_statements( Json(req): Json, ) -> Result>, AppError> { diff --git a/src-tauri/src/commands/query.rs b/src-tauri/src/commands/query.rs index 0a2c4b7caa..62b90eedf7 100644 --- a/src-tauri/src/commands/query.rs +++ b/src-tauri/src/commands/query.rs @@ -347,6 +347,13 @@ pub fn build_executable_object_source_sql( dbx_core::object_source_sql::build_executable_object_source_sql(input) } +#[tauri::command] +pub fn build_editable_object_source( + input: dbx_core::object_source_sql::EditableObjectSourceSqlInput, +) -> Result { + Ok(dbx_core::object_source_sql::build_editable_object_source(input)) +} + #[tauri::command] pub fn build_routine_rename_object_source_statements( input: dbx_core::object_source_sql::RoutineRenameObjectSourceInput, diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index f21a09009b..003cdc55fe 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -430,6 +430,7 @@ pub fn run() { commands::query::build_duplicate_table_structure_sql, commands::query::build_executable_object_source_statements, commands::query::build_executable_object_source_sql, + commands::query::build_editable_object_source, commands::query::build_routine_rename_object_source_statements, commands::query::build_view_ddl_sql, commands::query::build_table_structure_change_sql,