From db7463eb702bcbea6baa1ba5e2222ec040ce84e1 Mon Sep 17 00:00:00 2001 From: zipg Date: Mon, 17 Aug 2026 14:45:25 +0800 Subject: [PATCH] fix(oracle): export portable table DDL --- agents/drivers/oracle-go/main.go | 100 ++++++++++++++++-- agents/drivers/oracle-go/main_test.go | 88 +++++++++++++++ .../src/components/objects/ObjectBrowser.vue | 2 +- .../useSidebarTreeExportRuntime.spec.ts | 4 +- .../useSidebarTreeExportRuntime.ts | 2 +- .../contentAreaObjectBrowserCatalog.spec.ts | 2 +- apps/desktop/src/lib/backend/http.ts | 4 +- apps/desktop/src/lib/backend/tauri.ts | 4 +- crates/dbx-core/src/database_export.rs | 4 +- crates/dbx-core/src/db/agent_driver.rs | 20 ++-- crates/dbx-core/src/schema.rs | 23 +++- crates/dbx-web/src/routes/schema.rs | 12 +++ src-tauri/src/commands/schema.rs | 6 +- 13 files changed, 241 insertions(+), 30 deletions(-) diff --git a/agents/drivers/oracle-go/main.go b/agents/drivers/oracle-go/main.go index 2e02f2d249..bfd3db69a3 100644 --- a/agents/drivers/oracle-go/main.go +++ b/agents/drivers/oracle-go/main.go @@ -4,6 +4,7 @@ import ( "bufio" "context" "database/sql" + "database/sql/driver" "encoding/json" "errors" "fmt" @@ -42,6 +43,9 @@ FROM PRODUCT_COMPONENT_VERSION WHERE PRODUCT LIKE 'Oracle Database%' AND ROWNUM = 1` +const oracleDisableSegmentAttributesSQL = `BEGIN DBMS_METADATA.SET_TRANSFORM_PARAM(DBMS_METADATA.SESSION_TRANSFORM, 'SEGMENT_ATTRIBUTES', FALSE); END;` +const oracleEnableSegmentAttributesSQL = `BEGIN DBMS_METADATA.SET_TRANSFORM_PARAM(DBMS_METADATA.SESSION_TRANSFORM, 'SEGMENT_ATTRIBUTES', TRUE); END;` + var ( oraclePlSQLBlockStartRegexp = regexp.MustCompile(`(?is)^\s*(?:DECLARE|BEGIN|CREATE\s+(?:OR\s+REPLACE\s+)?(?:(?:EDITIONABLE|NONEDITIONABLE)\s+)?(?:FUNCTION|PROCEDURE|TRIGGER|PACKAGE(?:\s+BODY)?|TYPE(?:\s+BODY)?))\b`) oraclePlSQLBlockEndRegexp = regexp.MustCompile(`(?is)\bEND\s*;\s*$`) @@ -843,7 +847,7 @@ func (s *server) dispatch(method string, params map[string]json.RawMessage) (any schema := stringParam(params, "schema") table := stringParam(params, "table") objectType := stringParam(params, "object_type") - ddl, err := s.getTableDDL(schema, table, objectType) + ddl, err := s.getTableDDLWithOptions(schema, table, objectType, boolParam(params, "portable")) return ddl, false, err case "execute_query": var opts queryOptions @@ -2881,6 +2885,10 @@ func (s *server) normalizeSchemaForIdentity(schema string) (string, error) { } func (s *server) getTableDDL(schema, table, objectType string) (string, error) { + return s.getTableDDLWithOptions(schema, table, objectType, false) +} + +func (s *server) getTableDDLWithOptions(schema, table, objectType string, portable bool) (string, error) { var err error schema, err = s.normalizeSchema(schema) if err != nil { @@ -2898,9 +2906,32 @@ func (s *server) getTableDDL(schema, table, objectType string) (string, error) { return s.buildViewDDL(schema, table) } var ddl string - err = db.QueryRow("SELECT DBMS_METADATA.GET_DDL(:1, :2, :3) FROM DUAL", objectType, table, schema).Scan(&ddl) + var indexDDLs []string + if portable && objectType == "TABLE" { + var metadataErr error + err = withOraclePortableMetadataSession(db, func(conn *sql.Conn) error { + metadataErr = conn.QueryRowContext( + context.Background(), + "SELECT DBMS_METADATA.GET_DDL(:1, :2, :3) FROM DUAL", + objectType, + table, + schema, + ).Scan(&ddl) + indexDDLs, _ = loadTableIndexDDLsFromConn(conn, schema, table) + return nil + }) + if err != nil { + return "", err + } + err = metadataErr + } else { + err = db.QueryRow("SELECT DBMS_METADATA.GET_DDL(:1, :2, :3) FROM DUAL", objectType, table, schema).Scan(&ddl) + } if err == nil && strings.TrimSpace(ddl) != "" { if objectType == "TABLE" { + if portable { + return s.appendTableDependentDDLWithIndexes(schema, table, ddl, indexDDLs), nil + } return s.appendTableDependentDDL(schema, table, ddl), nil } return ddl, nil @@ -2910,12 +2941,41 @@ func (s *server) getTableDDL(schema, table, objectType string) (string, error) { if fallbackErr != nil { return "", fallbackErr } + if portable { + return s.appendTableDependentDDLWithIndexes(schema, table, fallback, indexDDLs), nil + } return s.appendTableDependentDDL(schema, table, fallback), nil } return "", err } +func withOraclePortableMetadataSession(db *sql.DB, operation func(*sql.Conn) error) (err error) { + conn, err := db.Conn(context.Background()) + if err != nil { + return err + } + if _, err = conn.ExecContext(context.Background(), oracleDisableSegmentAttributesSQL); err != nil { + _ = conn.Close() + return fmt.Errorf("failed to disable Oracle segment attributes: %w", err) + } + defer func() { + if _, resetErr := conn.ExecContext(context.Background(), oracleEnableSegmentAttributesSQL); resetErr != nil { + _ = conn.Raw(func(any) error { return driver.ErrBadConn }) + if err == nil { + err = fmt.Errorf("failed to restore Oracle segment attributes: %w", resetErr) + } + } + _ = conn.Close() + }() + return operation(conn) +} + func (s *server) appendTableDependentDDL(schema, table, tableDDL string) string { + indexDDLs, _ := s.loadTableIndexDDLs(schema, table) + return s.appendTableDependentDDLWithIndexes(schema, table, tableDDL, indexDDLs) +} + +func (s *server) appendTableDependentDDLWithIndexes(schema, table, tableDDL string, indexDDLs []string) string { var builder strings.Builder baseDDL := strings.TrimSpace(tableDDL) builder.WriteString(baseDDL) @@ -2931,10 +2991,8 @@ func (s *server) appendTableDependentDDL(schema, table, tableDDL string) string dependentAppended = true } - if indexDDLs, err := s.loadTableIndexDDLs(schema, table); err == nil { - for _, ddl := range indexDDLs { - appendDependent(ddl) - } + for _, ddl := range indexDDLs { + appendDependent(ddl) } if triggerDDLs, err := s.loadTableTriggerDDLs(schema, table); err == nil { for _, ddl := range triggerDDLs { @@ -2949,8 +3007,7 @@ func (s *server) appendTableDependentDDL(schema, table, tableDDL string) string return builder.String() } -func (s *server) loadTableIndexDDLs(schema, table string) ([]string, error) { - rows, err := s.queryRows(` +const oracleTableIndexDDLsSQL = ` SELECT DBMS_METADATA.GET_DDL('INDEX', i.INDEX_NAME, i.OWNER) FROM ALL_INDEXES i WHERE i.TABLE_OWNER = :1 @@ -2964,11 +3021,27 @@ WHERE i.TABLE_OWNER = :1 AND c.CONSTRAINT_TYPE IN ('P', 'U') AND c.INDEX_NAME IS NOT NULL ) -ORDER BY i.INDEX_NAME`, []any{schema, table, schema, table}) +ORDER BY i.INDEX_NAME` + +func (s *server) loadTableIndexDDLs(schema, table string) ([]string, error) { + rows, err := s.queryRows(oracleTableIndexDDLsSQL, []any{schema, table, schema, table}) if err != nil { return nil, err } defer s.closeRows(rows) + return scanOracleDDLs(rows) +} + +func loadTableIndexDDLsFromConn(conn *sql.Conn, schema, table string) ([]string, error) { + rows, err := conn.QueryContext(context.Background(), oracleTableIndexDDLsSQL, schema, table, schema, table) + if err != nil { + return nil, err + } + defer rows.Close() + return scanOracleDDLs(rows) +} + +func scanOracleDDLs(rows *sql.Rows) ([]string, error) { var result []string for rows.Next() { var ddl sql.NullString @@ -4745,6 +4818,15 @@ func intParam(params map[string]json.RawMessage, key string) int { return value } +func boolParam(params map[string]json.RawMessage, key string) bool { + if params == nil || len(params[key]) == 0 { + return false + } + var value bool + _ = json.Unmarshal(params[key], &value) + return value +} + func errorResponse(id json.RawMessage, err error) response { return response{JSONRPC: "2.0", ID: id, Error: &rpcError{Code: -1, Message: err.Error()}} } diff --git a/agents/drivers/oracle-go/main_test.go b/agents/drivers/oracle-go/main_test.go index 6e7e5de935..2325194cb8 100644 --- a/agents/drivers/oracle-go/main_test.go +++ b/agents/drivers/oracle-go/main_test.go @@ -627,6 +627,64 @@ func TestGetTableDDLAppendsIndexesTriggersAndComments(t *testing.T) { } } +func TestGetPortableTableDDLDisablesAndRestoresSegmentAttributes(t *testing.T) { + const schema = "HR" + const table = "ORDERS" + const tableDDL = `CREATE TABLE "HR"."ORDERS" ("ID" NUMBER)` + const indexDDL = `CREATE INDEX "HR"."IDX_ORDERS" ON "HR"."ORDERS" ("ID")` + db, scripted := openOracleViewSourceTestDB(t, []oracleViewSourceQueryStep{ + { + queryContains: "'SEGMENT_ATTRIBUTES', FALSE", + exec: true, + }, + { + queryContains: "DBMS_METADATA.GET_DDL(:1, :2, :3)", + args: []driver.Value{"TABLE", table, schema}, + rows: [][]driver.Value{{tableDDL}}, + }, + { + queryContains: "FROM ALL_INDEXES", + args: []driver.Value{schema, table, schema, table}, + rows: [][]driver.Value{{indexDDL}}, + }, + { + queryContains: "'SEGMENT_ATTRIBUTES', TRUE", + exec: true, + }, + { + queryContains: "FROM ALL_TRIGGERS", + args: []driver.Value{schema, table}, + rows: nil, + }, + { + queryContains: "FROM ALL_TAB_COMMENTS", + args: []driver.Value{schema, table}, + rows: nil, + }, + { + queryContains: "FROM ALL_COL_COMMENTS", + args: []driver.Value{schema, table}, + rows: nil, + }, + }) + s := newServer() + s.db = db + + got, err := s.getTableDDLWithOptions(schema, table, "TABLE", true) + if err != nil { + t.Fatal(err) + } + if !strings.Contains(got, tableDDL) || !strings.Contains(got, indexDDL) { + t.Fatalf("portable DDL should include table and index definitions:\n%s", got) + } + if strings.Contains(got, "TABLESPACE") || strings.Contains(got, "STORAGE") { + t.Fatalf("portable DDL should omit physical storage attributes:\n%s", got) + } + if scripted.next != len(scripted.steps) { + t.Fatalf("expected %d queries, got %d", len(scripted.steps), scripted.next) + } +} + func TestListIndexesSeparatesQuotedCloneTableNamesByCase(t *testing.T) { const schema = "HR" db, scripted := openOracleViewSourceTestDB(t, []oracleViewSourceQueryStep{ @@ -2461,6 +2519,7 @@ type oracleViewSourceQueryStep struct { columns []string rows [][]driver.Value err error + exec bool } type oracleViewSourceDriver struct { @@ -2498,6 +2557,9 @@ func (c *oracleViewSourceConn) QueryContext( } step := c.driver.steps[c.driver.next] c.driver.next++ + if step.exec { + return nil, errors.New("expected ExecContext call: " + query) + } if !strings.Contains(query, step.queryContains) { return nil, errors.New("unexpected query: " + query) } @@ -2518,6 +2580,32 @@ func (c *oracleViewSourceConn) QueryContext( return &oracleViewSourceRows{columns: columns, values: step.rows}, nil } +func (c *oracleViewSourceConn) ExecContext( + _ context.Context, + query string, + args []driver.NamedValue, +) (driver.Result, error) { + if c.driver.next >= len(c.driver.steps) { + return nil, errors.New("unexpected extra exec: " + query) + } + step := c.driver.steps[c.driver.next] + c.driver.next++ + if !step.exec || !strings.Contains(query, step.queryContains) { + return nil, errors.New("unexpected exec: " + query) + } + values := make([]driver.Value, len(args)) + for index, arg := range args { + values[index] = arg.Value + } + if (len(values) > 0 || len(step.args) > 0) && !reflect.DeepEqual(values, step.args) { + return nil, errors.New("unexpected exec arguments") + } + if step.err != nil { + return nil, step.err + } + return driver.RowsAffected(0), nil +} + type oracleViewSourceRows struct { columns []string values [][]driver.Value diff --git a/apps/desktop/src/components/objects/ObjectBrowser.vue b/apps/desktop/src/components/objects/ObjectBrowser.vue index d4b9327af4..59e3c9c506 100644 --- a/apps/desktop/src/components/objects/ObjectBrowser.vue +++ b/apps/desktop/src/components/objects/ObjectBrowser.vue @@ -1806,7 +1806,7 @@ async function confirmBatchEmptyTables() { async function exportStructure(row: ObjectBrowserRow) { try { const schema = row.schema || selectedSchema.value || props.database; - const ddl = await api.getTableDdl(props.connection.id, props.database, schema, row.name, tableDdlObjectType(row.type), props.catalog); + const ddl = await api.getTableDdl(props.connection.id, props.database, schema, row.name, tableDdlObjectType(row.type), props.catalog, true); await saveFileContent(buildSingleDdlExportFileContent(ddl), `${row.name}.sql`, "SQL", "sql"); } catch (e: any) { console.error("Export structure failed:", e); diff --git a/apps/desktop/src/composables/__tests__/useSidebarTreeExportRuntime.spec.ts b/apps/desktop/src/composables/__tests__/useSidebarTreeExportRuntime.spec.ts index f74a314362..3669c6d7b6 100644 --- a/apps/desktop/src/composables/__tests__/useSidebarTreeExportRuntime.spec.ts +++ b/apps/desktop/src/composables/__tests__/useSidebarTreeExportRuntime.spec.ts @@ -80,8 +80,8 @@ describe("useSidebarTreeExportRuntime", () => { expect(connectionStore.ensureConnected).toHaveBeenNthCalledWith(1, first.connectionId); expect(connectionStore.ensureConnected).toHaveBeenNthCalledWith(2, second.connectionId); - expect(apiMock.getTableDdl).toHaveBeenNthCalledWith(1, first.connectionId, first.database, first.schema, first.label, undefined, undefined); - expect(apiMock.getTableDdl).toHaveBeenNthCalledWith(2, second.connectionId, second.database, second.schema, second.label, "VIEW", undefined); + expect(apiMock.getTableDdl).toHaveBeenNthCalledWith(1, first.connectionId, first.database, first.schema, first.label, undefined, undefined, true); + expect(apiMock.getTableDdl).toHaveBeenNthCalledWith(2, second.connectionId, second.database, second.schema, second.label, "VIEW", undefined, true); expect(structurePreviewSql.value).toBe("CREATE TABLE one (id INT);\n\nCREATE VIEW two AS SELECT 1;\n"); expect(structurePreviewTitle.value).toBe("contextMenu.exportStructurePreviewTitleMultiple"); expect(showStructurePreviewDialog.value).toBe(true); diff --git a/apps/desktop/src/composables/useSidebarTreeExportRuntime.ts b/apps/desktop/src/composables/useSidebarTreeExportRuntime.ts index 94f532ec50..0196691f46 100644 --- a/apps/desktop/src/composables/useSidebarTreeExportRuntime.ts +++ b/apps/desktop/src/composables/useSidebarTreeExportRuntime.ts @@ -75,7 +75,7 @@ export function useSidebarTreeExportRuntime(options: SidebarTreeExportRuntimeOpt const parts: string[] = []; for (const target of targets) { await connectionStore.ensureConnected(target.connectionId); - const ddl = await api.getTableDdl(target.connectionId, target.database, target.schema || target.database, target.label, tableDdlObjectTypeForNode(target.type), target.catalog); + const ddl = await api.getTableDdl(target.connectionId, target.database, target.schema || target.database, target.label, tableDdlObjectTypeForNode(target.type), target.catalog, true); parts.push(ddl.trim()); } structurePreviewSql.value = joinExportedDdls(parts); diff --git a/apps/desktop/src/lib/__tests__/table/contentAreaObjectBrowserCatalog.spec.ts b/apps/desktop/src/lib/__tests__/table/contentAreaObjectBrowserCatalog.spec.ts index 22fb36480c..459c711440 100644 --- a/apps/desktop/src/lib/__tests__/table/contentAreaObjectBrowserCatalog.spec.ts +++ b/apps/desktop/src/lib/__tests__/table/contentAreaObjectBrowserCatalog.spec.ts @@ -52,7 +52,7 @@ describe("ObjectBrowser DDL API boundaries", () => { }); it("keeps structure exports on the portable base DDL", () => { - expect(exportStructureSource).toMatch(/api\.getTableDdl\([\s\S]*?props\.catalog\);/); + expect(exportStructureSource).toMatch(/api\.getTableDdl\([\s\S]*?props\.catalog, true\);/); expect(exportStructureSource).not.toContain("api.getTableDisplayDdl("); }); }); diff --git a/apps/desktop/src/lib/backend/http.ts b/apps/desktop/src/lib/backend/http.ts index ea00b2df06..144c912dc4 100644 --- a/apps/desktop/src/lib/backend/http.ts +++ b/apps/desktop/src/lib/backend/http.ts @@ -873,8 +873,8 @@ export async function listSubpartitions(connectionId: string, database: string, return get(`/api/schema/subpartitions?${qs({ connection_id: connectionId, database, schema, table, catalog })}`); } -export async function getTableDdl(connectionId: string, database: string, schema: string, table: string, objectType?: ObjectSourceKind, catalog?: string): Promise { - return get(`/api/schema/ddl?${qs({ connection_id: connectionId, database, schema, table, object_type: objectType, catalog })}`); +export async function getTableDdl(connectionId: string, database: string, schema: string, table: string, objectType?: ObjectSourceKind, catalog?: string, portable = false): Promise { + return get(`/api/schema/ddl?${qs({ connection_id: connectionId, database, schema, table, object_type: objectType, catalog, portable })}`); } export async function getTableDisplayDdl(connectionId: string, database: string, schema: string, table: string, objectType?: ObjectSourceKind, catalog?: string): Promise { diff --git a/apps/desktop/src/lib/backend/tauri.ts b/apps/desktop/src/lib/backend/tauri.ts index 6f566d6d81..719bfd2c51 100644 --- a/apps/desktop/src/lib/backend/tauri.ts +++ b/apps/desktop/src/lib/backend/tauri.ts @@ -1654,7 +1654,7 @@ export async function listSubpartitions(connectionId: string, database: string, }); } -export async function getTableDdl(connectionId: string, database: string, schema: string, table: string, objectType?: ObjectSourceKind, catalog?: string): Promise { +export async function getTableDdl(connectionId: string, database: string, schema: string, table: string, objectType?: ObjectSourceKind, catalog?: string, portable = false): Promise { return invoke("get_table_ddl", { connectionId, database, @@ -1662,6 +1662,7 @@ export async function getTableDdl(connectionId: string, database: string, schema table, objectType, catalog, + portable, }); } @@ -1674,6 +1675,7 @@ export async function getTableDisplayDdl(connectionId: string, database: string, objectType, catalog, includePostgresAccess: true, + portable: false, }); } diff --git a/crates/dbx-core/src/database_export.rs b/crates/dbx-core/src/database_export.rs index f7fcba5830..ff1e802106 100644 --- a/crates/dbx-core/src/database_export.rs +++ b/crates/dbx-core/src/database_export.rs @@ -2108,7 +2108,7 @@ pub async fn export_database_sql_core( } let ddl = if request.include_structure { Some( - crate::schema::get_table_ddl_core( + crate::schema::get_table_export_ddl_core( state, &request.connection_id, &request.database, @@ -2266,7 +2266,7 @@ pub async fn export_database_sql_core( { Some(result) => result, None => { - crate::schema::get_table_ddl_core( + crate::schema::get_table_export_ddl_core( state, &request.connection_id, &request.database, diff --git a/crates/dbx-core/src/db/agent_driver.rs b/crates/dbx-core/src/db/agent_driver.rs index e5a743c7b7..61b1c2b509 100644 --- a/crates/dbx-core/src/db/agent_driver.rs +++ b/crates/dbx-core/src/db/agent_driver.rs @@ -2203,12 +2203,20 @@ impl AgentDriverClient { table: &str, timeout_duration: Option, ) -> Result { - self.call_method_with_timeout( - AgentMethod::GetTableDdl, - agent_schema_table_params(database, schema, table), - timeout_duration, - ) - .await + self.get_table_ddl_with_options(database, schema, table, false, timeout_duration).await + } + + pub async fn get_table_ddl_with_options( + &mut self, + database: &str, + schema: &str, + table: &str, + portable: bool, + timeout_duration: Option, + ) -> Result { + let mut params = agent_schema_table_params(database, schema, table); + params["portable"] = serde_json::json!(portable); + self.call_method_with_timeout(AgentMethod::GetTableDdl, params, timeout_duration).await } pub async fn execute_query(&mut self, params: Value) -> Result { diff --git a/crates/dbx-core/src/schema.rs b/crates/dbx-core/src/schema.rs index 7c98298bd1..39f868d44a 100644 --- a/crates/dbx-core/src/schema.rs +++ b/crates/dbx-core/src/schema.rs @@ -6446,7 +6446,18 @@ pub async fn get_table_ddl_core( table: &str, object_type: Option, ) -> Result { - get_table_ddl_core_with_options(state, connection_id, database, schema, table, object_type, false).await + get_table_ddl_core_with_options(state, connection_id, database, schema, table, object_type, false, false).await +} + +pub async fn get_table_export_ddl_core( + state: &AppState, + connection_id: &str, + database: &str, + schema: &str, + table: &str, + object_type: Option, +) -> Result { + get_table_ddl_core_with_options(state, connection_id, database, schema, table, object_type, false, true).await } pub async fn get_table_display_ddl_core( @@ -6457,7 +6468,7 @@ pub async fn get_table_display_ddl_core( table: &str, object_type: Option, ) -> Result { - get_table_ddl_core_with_options(state, connection_id, database, schema, table, object_type, true).await + get_table_ddl_core_with_options(state, connection_id, database, schema, table, object_type, true, false).await } async fn get_table_ddl_core_with_options( @@ -6468,6 +6479,7 @@ async fn get_table_ddl_core_with_options( table: &str, object_type: Option, include_postgres_access: bool, + portable_oracle: bool, ) -> Result { if crate::sql_dialect::parse_sqlserver_linked_schema_ref(schema).is_some() { return Err("DDL is not supported for SQL Server linked server tables".to_string()); @@ -6513,7 +6525,7 @@ async fn get_table_ddl_core_with_options( } retry_metadata_connection(state, connection_id, Some(database), || { - get_table_ddl_once(state, connection_id, database, schema, table, include_postgres_access) + get_table_ddl_once(state, connection_id, database, schema, table, include_postgres_access, portable_oracle) }) .await } @@ -6525,6 +6537,7 @@ async fn get_table_ddl_once( schema: &str, table: &str, include_postgres_access: bool, + portable_oracle: bool, ) -> Result { let pool_key = state.get_or_create_metadata_pool_for_session(connection_id, Some(database), None).await?; let db_config = connection_config(state, connection_id).await; @@ -6604,6 +6617,7 @@ async fn get_table_ddl_once( database, schema, table, + portable_oracle, agent_metadata_timeout(db_config.as_ref()), ) .await; @@ -7712,10 +7726,11 @@ async fn oracle_agent_table_ddl( database: &str, schema: &str, table: &str, + portable: bool, timeout_duration: Option, ) -> Result { let mut client = client.lock().await; - let ddl = client.get_table_ddl::(database, schema, table, timeout_duration).await?; + let ddl = client.get_table_ddl_with_options::(database, schema, table, portable, timeout_duration).await?; match append_oracle_table_comment_ddl(&mut client, database, schema, table, &ddl, timeout_duration).await { Ok(ddl) => Ok(ddl), Err(error) => { diff --git a/crates/dbx-web/src/routes/schema.rs b/crates/dbx-web/src/routes/schema.rs index 3e72e92b8c..1b8be53fe5 100644 --- a/crates/dbx-web/src/routes/schema.rs +++ b/crates/dbx-web/src/routes/schema.rs @@ -28,6 +28,7 @@ pub struct SchemaQuery { pub apply_visible_filter: Option, pub client_session_id: Option, pub include_postgres_access: Option, + pub portable: Option, } #[derive(Deserialize)] @@ -646,6 +647,17 @@ pub async fn get_ddl( dbx_core::schema::get_doris_catalog_table_ddl_core(&state.app, &q.connection_id, &catalog, database, table) .await .map_err(AppError::from)? + } else if q.portable.unwrap_or(false) { + dbx_core::schema::get_table_export_ddl_core( + &state.app, + &q.connection_id, + database, + schema, + table, + q.object_type, + ) + .await + .map_err(AppError::from)? } else if q.include_postgres_access.unwrap_or(false) { dbx_core::schema::get_table_display_ddl_core( &state.app, diff --git a/src-tauri/src/commands/schema.rs b/src-tauri/src/commands/schema.rs index f62caaf762..ad3712ad12 100644 --- a/src-tauri/src/commands/schema.rs +++ b/src-tauri/src/commands/schema.rs @@ -474,12 +474,16 @@ pub async fn get_table_ddl( object_type: Option, catalog: Option, include_postgres_access: Option, + portable: Option, ) -> Result { if let Some(catalog) = external_doris_catalog(&state, &connection_id, catalog.as_deref()).await { return dbx_core::schema::get_doris_catalog_table_ddl_core(&state, &connection_id, &catalog, &database, &table) .await; } - if include_postgres_access.unwrap_or(false) { + if portable.unwrap_or(false) { + dbx_core::schema::get_table_export_ddl_core(&state, &connection_id, &database, &schema, &table, object_type) + .await + } else if include_postgres_access.unwrap_or(false) { dbx_core::schema::get_table_display_ddl_core(&state, &connection_id, &database, &schema, &table, object_type) .await } else {