fix(oracle): export portable table DDL

This commit is contained in:
zipg authored and GitHub committed 2026-08-17 14:45:25 +08:00
1 parent 1cdee024c2
commit db7463eb70
13 files changed
+241 -30

No files matched your search

+91 -9
View File
@@ -4,6 +4,7 @@ import (
"bufio" "bufio"
"context" "context"
"database/sql" "database/sql"
"database/sql/driver"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@@ -42,6 +43,9 @@ FROM PRODUCT_COMPONENT_VERSION
WHERE PRODUCT LIKE 'Oracle Database%' WHERE PRODUCT LIKE 'Oracle Database%'
AND ROWNUM = 1` 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 ( 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`) 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*$`) 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") schema := stringParam(params, "schema")
table := stringParam(params, "table") table := stringParam(params, "table")
objectType := stringParam(params, "object_type") 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 return ddl, false, err
case "execute_query": case "execute_query":
var opts queryOptions 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) { 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 var err error
schema, err = s.normalizeSchema(schema) schema, err = s.normalizeSchema(schema)
if err != nil { if err != nil {
@@ -2898,9 +2906,32 @@ func (s *server) getTableDDL(schema, table, objectType string) (string, error) {
return s.buildViewDDL(schema, table) return s.buildViewDDL(schema, table)
} }
var ddl string 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 err == nil && strings.TrimSpace(ddl) != "" {
if objectType == "TABLE" { if objectType == "TABLE" {
if portable {
return s.appendTableDependentDDLWithIndexes(schema, table, ddl, indexDDLs), nil
}
return s.appendTableDependentDDL(schema, table, ddl), nil return s.appendTableDependentDDL(schema, table, ddl), nil
} }
return ddl, nil return ddl, nil
@@ -2910,12 +2941,41 @@ func (s *server) getTableDDL(schema, table, objectType string) (string, error) {
if fallbackErr != nil { if fallbackErr != nil {
return "", fallbackErr return "", fallbackErr
} }
if portable {
return s.appendTableDependentDDLWithIndexes(schema, table, fallback, indexDDLs), nil
}
return s.appendTableDependentDDL(schema, table, fallback), nil return s.appendTableDependentDDL(schema, table, fallback), nil
} }
return "", err 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 { 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 var builder strings.Builder
baseDDL := strings.TrimSpace(tableDDL) baseDDL := strings.TrimSpace(tableDDL)
builder.WriteString(baseDDL) builder.WriteString(baseDDL)
@@ -2931,10 +2991,8 @@ func (s *server) appendTableDependentDDL(schema, table, tableDDL string) string
dependentAppended = true dependentAppended = true
} }
if indexDDLs, err := s.loadTableIndexDDLs(schema, table); err == nil { for _, ddl := range indexDDLs {
for _, ddl := range indexDDLs { appendDependent(ddl)
appendDependent(ddl)
}
} }
if triggerDDLs, err := s.loadTableTriggerDDLs(schema, table); err == nil { if triggerDDLs, err := s.loadTableTriggerDDLs(schema, table); err == nil {
for _, ddl := range triggerDDLs { for _, ddl := range triggerDDLs {
@@ -2949,8 +3007,7 @@ func (s *server) appendTableDependentDDL(schema, table, tableDDL string) string
return builder.String() return builder.String()
} }
func (s *server) loadTableIndexDDLs(schema, table string) ([]string, error) { const oracleTableIndexDDLsSQL = `
rows, err := s.queryRows(`
SELECT DBMS_METADATA.GET_DDL('INDEX', i.INDEX_NAME, i.OWNER) SELECT DBMS_METADATA.GET_DDL('INDEX', i.INDEX_NAME, i.OWNER)
FROM ALL_INDEXES i FROM ALL_INDEXES i
WHERE i.TABLE_OWNER = :1 WHERE i.TABLE_OWNER = :1
@@ -2964,11 +3021,27 @@ WHERE i.TABLE_OWNER = :1
AND c.CONSTRAINT_TYPE IN ('P', 'U') AND c.CONSTRAINT_TYPE IN ('P', 'U')
AND c.INDEX_NAME IS NOT NULL 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 { if err != nil {
return nil, err return nil, err
} }
defer s.closeRows(rows) 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 var result []string
for rows.Next() { for rows.Next() {
var ddl sql.NullString var ddl sql.NullString
@@ -4745,6 +4818,15 @@ func intParam(params map[string]json.RawMessage, key string) int {
return value 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 { func errorResponse(id json.RawMessage, err error) response {
return response{JSONRPC: "2.0", ID: id, Error: &rpcError{Code: -1, Message: err.Error()}} return response{JSONRPC: "2.0", ID: id, Error: &rpcError{Code: -1, Message: err.Error()}}
} }
+88
View File
@@ -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) { func TestListIndexesSeparatesQuotedCloneTableNamesByCase(t *testing.T) {
const schema = "HR" const schema = "HR"
db, scripted := openOracleViewSourceTestDB(t, []oracleViewSourceQueryStep{ db, scripted := openOracleViewSourceTestDB(t, []oracleViewSourceQueryStep{
@@ -2461,6 +2519,7 @@ type oracleViewSourceQueryStep struct {
columns []string columns []string
rows [][]driver.Value rows [][]driver.Value
err error err error
exec bool
} }
type oracleViewSourceDriver struct { type oracleViewSourceDriver struct {
@@ -2498,6 +2557,9 @@ func (c *oracleViewSourceConn) QueryContext(
} }
step := c.driver.steps[c.driver.next] step := c.driver.steps[c.driver.next]
c.driver.next++ c.driver.next++
if step.exec {
return nil, errors.New("expected ExecContext call: " + query)
}
if !strings.Contains(query, step.queryContains) { if !strings.Contains(query, step.queryContains) {
return nil, errors.New("unexpected query: " + query) return nil, errors.New("unexpected query: " + query)
} }
@@ -2518,6 +2580,32 @@ func (c *oracleViewSourceConn) QueryContext(
return &oracleViewSourceRows{columns: columns, values: step.rows}, nil 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 { type oracleViewSourceRows struct {
columns []string columns []string
values [][]driver.Value values [][]driver.Value
@@ -1806,7 +1806,7 @@ async function confirmBatchEmptyTables() {
async function exportStructure(row: ObjectBrowserRow) { async function exportStructure(row: ObjectBrowserRow) {
try { try {
const schema = row.schema || selectedSchema.value || props.database; 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"); await saveFileContent(buildSingleDdlExportFileContent(ddl), `${row.name}.sql`, "SQL", "sql");
} catch (e: any) { } catch (e: any) {
console.error("Export structure failed:", e); console.error("Export structure failed:", e);
@@ -80,8 +80,8 @@ describe("useSidebarTreeExportRuntime", () => {
expect(connectionStore.ensureConnected).toHaveBeenNthCalledWith(1, first.connectionId); expect(connectionStore.ensureConnected).toHaveBeenNthCalledWith(1, first.connectionId);
expect(connectionStore.ensureConnected).toHaveBeenNthCalledWith(2, second.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(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); 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(structurePreviewSql.value).toBe("CREATE TABLE one (id INT);\n\nCREATE VIEW two AS SELECT 1;\n");
expect(structurePreviewTitle.value).toBe("contextMenu.exportStructurePreviewTitleMultiple"); expect(structurePreviewTitle.value).toBe("contextMenu.exportStructurePreviewTitleMultiple");
expect(showStructurePreviewDialog.value).toBe(true); expect(showStructurePreviewDialog.value).toBe(true);
@@ -75,7 +75,7 @@ export function useSidebarTreeExportRuntime(options: SidebarTreeExportRuntimeOpt
const parts: string[] = []; const parts: string[] = [];
for (const target of targets) { for (const target of targets) {
await connectionStore.ensureConnected(target.connectionId); 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()); parts.push(ddl.trim());
} }
structurePreviewSql.value = joinExportedDdls(parts); structurePreviewSql.value = joinExportedDdls(parts);
@@ -52,7 +52,7 @@ describe("ObjectBrowser DDL API boundaries", () => {
}); });
it("keeps structure exports on the portable base DDL", () => { 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("); expect(exportStructureSource).not.toContain("api.getTableDisplayDdl(");
}); });
}); });
+2 -2
View File
@@ -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 })}`); 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<string> { export async function getTableDdl(connectionId: string, database: string, schema: string, table: string, objectType?: ObjectSourceKind, catalog?: string, portable = false): Promise<string> {
return get(`/api/schema/ddl?${qs({ connection_id: connectionId, database, schema, table, object_type: objectType, catalog })}`); 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<string> { export async function getTableDisplayDdl(connectionId: string, database: string, schema: string, table: string, objectType?: ObjectSourceKind, catalog?: string): Promise<string> {
+3 -1
View File
@@ -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<string> { export async function getTableDdl(connectionId: string, database: string, schema: string, table: string, objectType?: ObjectSourceKind, catalog?: string, portable = false): Promise<string> {
return invoke("get_table_ddl", { return invoke("get_table_ddl", {
connectionId, connectionId,
database, database,
@@ -1662,6 +1662,7 @@ export async function getTableDdl(connectionId: string, database: string, schema
table, table,
objectType, objectType,
catalog, catalog,
portable,
}); });
} }
@@ -1674,6 +1675,7 @@ export async function getTableDisplayDdl(connectionId: string, database: string,
objectType, objectType,
catalog, catalog,
includePostgresAccess: true, includePostgresAccess: true,
portable: false,
}); });
} }
+2 -2
View File
@@ -2108,7 +2108,7 @@ pub async fn export_database_sql_core(
} }
let ddl = if request.include_structure { let ddl = if request.include_structure {
Some( Some(
crate::schema::get_table_ddl_core( crate::schema::get_table_export_ddl_core(
state, state,
&request.connection_id, &request.connection_id,
&request.database, &request.database,
@@ -2266,7 +2266,7 @@ pub async fn export_database_sql_core(
{ {
Some(result) => result, Some(result) => result,
None => { None => {
crate::schema::get_table_ddl_core( crate::schema::get_table_export_ddl_core(
state, state,
&request.connection_id, &request.connection_id,
&request.database, &request.database,
+14 -6
View File
@@ -2203,12 +2203,20 @@ impl AgentDriverClient {
table: &str, table: &str,
timeout_duration: Option<Duration>, timeout_duration: Option<Duration>,
) -> Result<T, String> { ) -> Result<T, String> {
self.call_method_with_timeout( self.get_table_ddl_with_options(database, schema, table, false, timeout_duration).await
AgentMethod::GetTableDdl, }
agent_schema_table_params(database, schema, table),
timeout_duration, pub async fn get_table_ddl_with_options<T: DeserializeOwned + Send + 'static>(
) &mut self,
.await database: &str,
schema: &str,
table: &str,
portable: bool,
timeout_duration: Option<Duration>,
) -> Result<T, String> {
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<T: DeserializeOwned + Send + 'static>(&mut self, params: Value) -> Result<T, String> { pub async fn execute_query<T: DeserializeOwned + Send + 'static>(&mut self, params: Value) -> Result<T, String> {
+19 -4
View File
@@ -6446,7 +6446,18 @@ pub async fn get_table_ddl_core(
table: &str, table: &str,
object_type: Option<db::ObjectSourceKind>, object_type: Option<db::ObjectSourceKind>,
) -> Result<String, String> { ) -> Result<String, String> {
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<db::ObjectSourceKind>,
) -> Result<String, String> {
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( pub async fn get_table_display_ddl_core(
@@ -6457,7 +6468,7 @@ pub async fn get_table_display_ddl_core(
table: &str, table: &str,
object_type: Option<db::ObjectSourceKind>, object_type: Option<db::ObjectSourceKind>,
) -> Result<String, String> { ) -> Result<String, String> {
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( async fn get_table_ddl_core_with_options(
@@ -6468,6 +6479,7 @@ async fn get_table_ddl_core_with_options(
table: &str, table: &str,
object_type: Option<db::ObjectSourceKind>, object_type: Option<db::ObjectSourceKind>,
include_postgres_access: bool, include_postgres_access: bool,
portable_oracle: bool,
) -> Result<String, String> { ) -> Result<String, String> {
if crate::sql_dialect::parse_sqlserver_linked_schema_ref(schema).is_some() { 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()); 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), || { 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 .await
} }
@@ -6525,6 +6537,7 @@ async fn get_table_ddl_once(
schema: &str, schema: &str,
table: &str, table: &str,
include_postgres_access: bool, include_postgres_access: bool,
portable_oracle: bool,
) -> Result<String, String> { ) -> Result<String, String> {
let pool_key = state.get_or_create_metadata_pool_for_session(connection_id, Some(database), None).await?; 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; let db_config = connection_config(state, connection_id).await;
@@ -6604,6 +6617,7 @@ async fn get_table_ddl_once(
database, database,
schema, schema,
table, table,
portable_oracle,
agent_metadata_timeout(db_config.as_ref()), agent_metadata_timeout(db_config.as_ref()),
) )
.await; .await;
@@ -7712,10 +7726,11 @@ async fn oracle_agent_table_ddl(
database: &str, database: &str,
schema: &str, schema: &str,
table: &str, table: &str,
portable: bool,
timeout_duration: Option<Duration>, timeout_duration: Option<Duration>,
) -> Result<String, String> { ) -> Result<String, String> {
let mut client = client.lock().await; let mut client = client.lock().await;
let ddl = client.get_table_ddl::<String>(database, schema, table, timeout_duration).await?; let ddl = client.get_table_ddl_with_options::<String>(database, schema, table, portable, timeout_duration).await?;
match append_oracle_table_comment_ddl(&mut client, database, schema, table, &ddl, timeout_duration).await { match append_oracle_table_comment_ddl(&mut client, database, schema, table, &ddl, timeout_duration).await {
Ok(ddl) => Ok(ddl), Ok(ddl) => Ok(ddl),
Err(error) => { Err(error) => {
+12
View File
@@ -28,6 +28,7 @@ pub struct SchemaQuery {
pub apply_visible_filter: Option<bool>, pub apply_visible_filter: Option<bool>,
pub client_session_id: Option<String>, pub client_session_id: Option<String>,
pub include_postgres_access: Option<bool>, pub include_postgres_access: Option<bool>,
pub portable: Option<bool>,
} }
#[derive(Deserialize)] #[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) dbx_core::schema::get_doris_catalog_table_ddl_core(&state.app, &q.connection_id, &catalog, database, table)
.await .await
.map_err(AppError::from)? .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) { } else if q.include_postgres_access.unwrap_or(false) {
dbx_core::schema::get_table_display_ddl_core( dbx_core::schema::get_table_display_ddl_core(
&state.app, &state.app,
+5 -1
View File
@@ -474,12 +474,16 @@ pub async fn get_table_ddl(
object_type: Option<db::ObjectSourceKind>, object_type: Option<db::ObjectSourceKind>,
catalog: Option<String>, catalog: Option<String>,
include_postgres_access: Option<bool>, include_postgres_access: Option<bool>,
portable: Option<bool>,
) -> Result<String, String> { ) -> Result<String, String> {
if let Some(catalog) = external_doris_catalog(&state, &connection_id, catalog.as_deref()).await { 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) return dbx_core::schema::get_doris_catalog_table_ddl_core(&state, &connection_id, &catalog, &database, &table)
.await; .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) dbx_core::schema::get_table_display_ddl_core(&state, &connection_id, &database, &schema, &table, object_type)
.await .await
} else { } else {