fix(oracle): export portable table DDL
This commit is contained in:
13 files changed
+241
-30
No files matched your search
@@ -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()}}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
+1
-1
@@ -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(");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -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> {
|
||||||
|
|||||||
@@ -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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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> {
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in new issue
Block a user