feat(xugu): expose programmable object metadata
This commit is contained in:
1 parent
d3887c9936
commit
08cd8903f4
36 files changed
+431
-62
No files matched your search
@@ -201,6 +201,7 @@ type objectInfo struct {
|
|||||||
ObjectType string `json:"object_type"`
|
ObjectType string `json:"object_type"`
|
||||||
Schema string `json:"schema"`
|
Schema string `json:"schema"`
|
||||||
Comment *string `json:"comment"`
|
Comment *string `json:"comment"`
|
||||||
|
Valid *bool `json:"valid,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type metadataListConstraints struct {
|
type metadataListConstraints struct {
|
||||||
@@ -1258,10 +1259,15 @@ func (s *server) listObjects(schema string, constraints metadataListConstraints)
|
|||||||
var result []objectInfo
|
var result []objectInfo
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var item objectInfo
|
var item objectInfo
|
||||||
|
var valid any
|
||||||
item.Schema = schema
|
item.Schema = schema
|
||||||
if err := rows.Scan(&item.Name, &item.ObjectType, &item.Comment); err != nil {
|
if err := rows.Scan(&item.Name, &item.ObjectType, &item.Comment, &valid); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if valid != nil {
|
||||||
|
value := truthy(valid)
|
||||||
|
item.Valid = &value
|
||||||
|
}
|
||||||
result = append(result, item)
|
result = append(result, item)
|
||||||
}
|
}
|
||||||
return emptyIfNil(result), rows.Err()
|
return emptyIfNil(result), rows.Err()
|
||||||
@@ -1311,19 +1317,58 @@ WHERE UPPER(s.SCHEMA_NAME) = UPPER(?)`,
|
|||||||
func xuguListObjectsQuery(schema string, constraints metadataListConstraints) xuguMetadataListQuery {
|
func xuguListObjectsQuery(schema string, constraints metadataListConstraints) xuguMetadataListQuery {
|
||||||
return xuguConstrainedMetadataListQuery(
|
return xuguConstrainedMetadataListQuery(
|
||||||
`
|
`
|
||||||
SELECT t.TABLE_NAME AS OBJECT_NAME, 'TABLE' AS OBJECT_TYPE, t.COMMENTS
|
SELECT t.TABLE_NAME AS OBJECT_NAME, 'TABLE' AS OBJECT_TYPE, t.COMMENTS, NULL AS VALID
|
||||||
FROM ALL_TABLES t
|
FROM ALL_TABLES t
|
||||||
JOIN ALL_SCHEMAS s ON s.DB_ID = t.DB_ID AND s.SCHEMA_ID = t.SCHEMA_ID
|
JOIN ALL_SCHEMAS s ON s.DB_ID = t.DB_ID AND s.SCHEMA_ID = t.SCHEMA_ID
|
||||||
WHERE UPPER(s.SCHEMA_NAME) = UPPER(?)
|
WHERE UPPER(s.SCHEMA_NAME) = UPPER(?)
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT v.VIEW_NAME AS OBJECT_NAME, 'VIEW' AS OBJECT_TYPE, v.COMMENTS
|
SELECT v.VIEW_NAME AS OBJECT_NAME, 'VIEW' AS OBJECT_TYPE, v.COMMENTS, NULL AS VALID
|
||||||
FROM ALL_VIEWS v
|
FROM ALL_VIEWS v
|
||||||
JOIN ALL_SCHEMAS s ON s.DB_ID = v.DB_ID AND s.SCHEMA_ID = v.SCHEMA_ID
|
JOIN ALL_SCHEMAS s ON s.DB_ID = v.DB_ID AND s.SCHEMA_ID = v.SCHEMA_ID
|
||||||
WHERE UPPER(s.SCHEMA_NAME) = UPPER(?)`,
|
WHERE UPPER(s.SCHEMA_NAME) = UPPER(?)
|
||||||
"OBJECT_NAME, OBJECT_TYPE, COMMENTS",
|
UNION ALL
|
||||||
|
SELECT p.PROC_NAME AS OBJECT_NAME,
|
||||||
|
CASE WHEN p.RET_TYPE IS NULL THEN 'PROCEDURE' ELSE 'FUNCTION' END AS OBJECT_TYPE,
|
||||||
|
p.COMMENTS, p.VALID
|
||||||
|
FROM ALL_PROCEDURES p
|
||||||
|
JOIN ALL_SCHEMAS s ON s.DB_ID = p.DB_ID AND s.SCHEMA_ID = p.SCHEMA_ID
|
||||||
|
WHERE UPPER(s.SCHEMA_NAME) = UPPER(?)
|
||||||
|
UNION ALL
|
||||||
|
SELECT p.PACK_NAME AS OBJECT_NAME, 'PACKAGE' AS OBJECT_TYPE, p.COMMENTS, p.VALID
|
||||||
|
FROM ALL_PACKAGES p
|
||||||
|
JOIN ALL_SCHEMAS s ON s.DB_ID = p.DB_ID AND s.SCHEMA_ID = p.SCHEMA_ID
|
||||||
|
WHERE UPPER(s.SCHEMA_NAME) = UPPER(?)
|
||||||
|
UNION ALL
|
||||||
|
SELECT p.PACK_NAME AS OBJECT_NAME, 'PACKAGE_BODY' AS OBJECT_TYPE, p.COMMENTS, p.ALL_OK
|
||||||
|
FROM ALL_PACKAGES p
|
||||||
|
JOIN ALL_SCHEMAS s ON s.DB_ID = p.DB_ID AND s.SCHEMA_ID = p.SCHEMA_ID
|
||||||
|
WHERE UPPER(s.SCHEMA_NAME) = UPPER(?)
|
||||||
|
AND p.BODY IS NOT NULL
|
||||||
|
UNION ALL
|
||||||
|
SELECT tr.TRIG_NAME AS OBJECT_NAME, 'TRIGGER' AS OBJECT_TYPE, tr.COMMENTS, tr.VALID
|
||||||
|
FROM ALL_TRIGGERS tr
|
||||||
|
JOIN ALL_SCHEMAS s ON s.DB_ID = tr.DB_ID AND s.SCHEMA_ID = tr.SCHEMA_ID
|
||||||
|
WHERE UPPER(s.SCHEMA_NAME) = UPPER(?)
|
||||||
|
UNION ALL
|
||||||
|
SELECT q.SEQ_NAME AS OBJECT_NAME, 'SEQUENCE' AS OBJECT_TYPE, NULL AS COMMENTS, NULL AS VALID
|
||||||
|
FROM ALL_SEQUENCES q
|
||||||
|
JOIN ALL_SCHEMAS s ON s.DB_ID = q.DB_ID AND s.SCHEMA_ID = q.SCHEMA_ID
|
||||||
|
WHERE UPPER(s.SCHEMA_NAME) = UPPER(?)
|
||||||
|
UNION ALL
|
||||||
|
SELECT u.TYPE_NAME AS OBJECT_NAME, 'TYPE' AS OBJECT_TYPE, u.COMMENTS, u.VALID
|
||||||
|
FROM ALL_TYPES u
|
||||||
|
JOIN ALL_SCHEMAS s ON s.DB_ID = u.DB_ID AND s.SCHEMA_ID = u.SCHEMA_ID
|
||||||
|
WHERE UPPER(s.SCHEMA_NAME) = UPPER(?)
|
||||||
|
UNION ALL
|
||||||
|
SELECT u.TYPE_NAME AS OBJECT_NAME, 'TYPE_BODY' AS OBJECT_TYPE, u.COMMENTS, u.VALID
|
||||||
|
FROM ALL_TYPES u
|
||||||
|
JOIN ALL_SCHEMAS s ON s.DB_ID = u.DB_ID AND s.SCHEMA_ID = u.SCHEMA_ID
|
||||||
|
WHERE UPPER(s.SCHEMA_NAME) = UPPER(?)
|
||||||
|
AND u.BODY IS NOT NULL`,
|
||||||
|
"OBJECT_NAME, OBJECT_TYPE, COMMENTS, VALID",
|
||||||
"OBJECT_NAME",
|
"OBJECT_NAME",
|
||||||
"OBJECT_TYPE",
|
"OBJECT_TYPE",
|
||||||
[]any{schema, schema},
|
[]any{schema, schema, schema, schema, schema, schema, schema, schema, schema},
|
||||||
constraints,
|
constraints,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1388,6 +1433,8 @@ func normalizedXuguObjectTypes(values []string) []string {
|
|||||||
normalized = "TABLE"
|
normalized = "TABLE"
|
||||||
case "VIEW":
|
case "VIEW":
|
||||||
normalized = "VIEW"
|
normalized = "VIEW"
|
||||||
|
case "PROCEDURE", "FUNCTION", "TRIGGER", "SEQUENCE", "PACKAGE", "PACKAGE_BODY", "TYPE", "TYPE_BODY":
|
||||||
|
// Already normalized.
|
||||||
default:
|
default:
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -1653,7 +1700,13 @@ func (s *server) getObjectSource(schema, name, objectType string) (map[string]an
|
|||||||
}
|
}
|
||||||
builder.WriteString(line)
|
builder.WriteString(line)
|
||||||
}
|
}
|
||||||
return map[string]any{"name": name, "object_type": objectType, "schema": schema, "source": builder.String()}, rows.Err()
|
result := map[string]any{"name": name, "object_type": objectType, "schema": schema, "source": builder.String()}
|
||||||
|
normalizedType := strings.ToUpper(strings.ReplaceAll(strings.TrimSpace(objectType), "_", " "))
|
||||||
|
if normalizedType == "TYPE" || normalizedType == "TYPE BODY" {
|
||||||
|
// Type source is exposed as catalog SPEC/BODY text, but cannot be safely edited as DDL.
|
||||||
|
result["editable"] = false
|
||||||
|
}
|
||||||
|
return result, rows.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) getTableDDL(schema, table string) (string, error) {
|
func (s *server) getTableDDL(schema, table string) (string, error) {
|
||||||
@@ -2121,12 +2174,31 @@ SELECT TO_CHAR(p.DEFINE)
|
|||||||
FROM SYS_PROCEDURES p
|
FROM SYS_PROCEDURES p
|
||||||
JOIN SYS_SCHEMAS s ON s.DB_ID = p.DB_ID AND s.SCHEMA_ID = p.SCHEMA_ID
|
JOIN SYS_SCHEMAS s ON s.DB_ID = p.DB_ID AND s.SCHEMA_ID = p.SCHEMA_ID
|
||||||
WHERE UPPER(s.SCHEMA_NAME) = UPPER(?) AND UPPER(p.PROC_NAME) = UPPER(?)`, []any{schema, name}, nil
|
WHERE UPPER(s.SCHEMA_NAME) = UPPER(?) AND UPPER(p.PROC_NAME) = UPPER(?)`, []any{schema, name}, nil
|
||||||
case "PACKAGE", "PACKAGE BODY":
|
case "PACKAGE":
|
||||||
return `
|
return `
|
||||||
SELECT COALESCE(TO_CHAR(k.SPEC), '') || COALESCE(TO_CHAR(k.BODY), '')
|
SELECT COALESCE(TO_CHAR(k.SPEC), '')
|
||||||
FROM SYS_PACKAGES k
|
FROM SYS_PACKAGES k
|
||||||
JOIN SYS_SCHEMAS s ON s.DB_ID = k.DB_ID AND s.SCHEMA_ID = k.SCHEMA_ID
|
JOIN SYS_SCHEMAS s ON s.DB_ID = k.DB_ID AND s.SCHEMA_ID = k.SCHEMA_ID
|
||||||
WHERE UPPER(s.SCHEMA_NAME) = UPPER(?) AND UPPER(k.PACK_NAME) = UPPER(?)`, []any{schema, name}, nil
|
WHERE UPPER(s.SCHEMA_NAME) = UPPER(?) AND UPPER(k.PACK_NAME) = UPPER(?)`, []any{schema, name}, nil
|
||||||
|
case "PACKAGE BODY", "PACKAGE_BODY":
|
||||||
|
return `
|
||||||
|
SELECT COALESCE(TO_CHAR(k.BODY), '')
|
||||||
|
FROM SYS_PACKAGES k
|
||||||
|
JOIN SYS_SCHEMAS s ON s.DB_ID = k.DB_ID AND s.SCHEMA_ID = k.SCHEMA_ID
|
||||||
|
WHERE UPPER(s.SCHEMA_NAME) = UPPER(?) AND UPPER(k.PACK_NAME) = UPPER(?)`, []any{schema, name}, nil
|
||||||
|
case "TYPE":
|
||||||
|
return `
|
||||||
|
SELECT COALESCE(TO_CHAR(u.SPEC), '')
|
||||||
|
FROM ALL_TYPES u
|
||||||
|
JOIN ALL_SCHEMAS s ON s.DB_ID = u.DB_ID AND s.SCHEMA_ID = u.SCHEMA_ID
|
||||||
|
WHERE UPPER(s.SCHEMA_NAME) = UPPER(?) AND UPPER(u.TYPE_NAME) = UPPER(?)`, []any{schema, name}, nil
|
||||||
|
case "TYPE BODY", "TYPE_BODY":
|
||||||
|
return `
|
||||||
|
SELECT COALESCE(TO_CHAR(u.BODY), '')
|
||||||
|
FROM ALL_TYPES u
|
||||||
|
JOIN ALL_SCHEMAS s ON s.DB_ID = u.DB_ID AND s.SCHEMA_ID = u.SCHEMA_ID
|
||||||
|
WHERE UPPER(s.SCHEMA_NAME) = UPPER(?) AND UPPER(u.TYPE_NAME) = UPPER(?)
|
||||||
|
AND u.BODY IS NOT NULL`, []any{schema, name}, nil
|
||||||
default:
|
default:
|
||||||
return "", nil, fmt.Errorf("object source is not supported for %s", objectType)
|
return "", nil, fmt.Errorf("object source is not supported for %s", objectType)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -537,10 +537,61 @@ func TestXuguListObjectsQueryRejectsUnsupportedObjectTypes(t *testing.T) {
|
|||||||
t.Fatalf("unsupported object type should produce empty-result predicate:\n%s", query.SQL)
|
t.Fatalf("unsupported object type should produce empty-result predicate:\n%s", query.SQL)
|
||||||
}
|
}
|
||||||
|
|
||||||
wantArgs := []any{"APP", "APP", 10, 0}
|
wantArgs := []any{"APP", "APP", "APP", "APP", "APP", "APP", "APP", "APP", "APP", 10, 0}
|
||||||
assertArgs(t, query.Args, wantArgs)
|
assertArgs(t, query.Args, wantArgs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestXuguListObjectsQueryIncludesProgrammableObjects(t *testing.T) {
|
||||||
|
query := xuguListObjectsQuery("APP", metadataListConstraints{
|
||||||
|
ObjectTypes: []string{"procedure", "function", "package", "package-body", "trigger", "sequence", "type", "type-body"},
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, want := range []string{"ALL_PROCEDURES", "p.VALID", "ALL_PACKAGES", "p.BODY IS NOT NULL", "ALL_TRIGGERS", "ALL_SEQUENCES", "ALL_TYPES", "u.BODY IS NOT NULL", "OBJECT_NAME, OBJECT_TYPE, COMMENTS, VALID", "OBJECT_TYPE IN (?,?,?,?,?,?,?,?)"} {
|
||||||
|
if !strings.Contains(query.SQL, want) {
|
||||||
|
t.Fatalf("expected SQL to contain %q:\n%s", want, query.SQL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wantArgs := []any{"APP", "APP", "APP", "APP", "APP", "APP", "APP", "APP", "APP", "FUNCTION", "PACKAGE", "PACKAGE_BODY", "PROCEDURE", "SEQUENCE", "TRIGGER", "TYPE", "TYPE_BODY"}
|
||||||
|
assertArgs(t, query.Args, wantArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestXuguObjectSourceQuerySupportsSharedObjectKinds(t *testing.T) {
|
||||||
|
for _, objectType := range []string{"TRIGGER", "PACKAGE_BODY", "TYPE", "TYPE_BODY"} {
|
||||||
|
query, _, err := objectSourceQuery("APP", "demo", objectType)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s should support object source lookup: %v", objectType, err)
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(query) == "" {
|
||||||
|
t.Fatalf("%s should produce source SQL", objectType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
packageBodyQuery, _, err := objectSourceQuery("APP", "demo", "PACKAGE_BODY")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(packageBodyQuery, "TO_CHAR(k.BODY)") || strings.Contains(packageBodyQuery, "k.SPEC") {
|
||||||
|
t.Fatalf("package body query must request only the body: %s", packageBodyQuery)
|
||||||
|
}
|
||||||
|
|
||||||
|
typeSpecQuery, _, err := objectSourceQuery("APP", "demo", "TYPE")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(typeSpecQuery, "ALL_TYPES") || !strings.Contains(typeSpecQuery, "TO_CHAR(u.SPEC)") {
|
||||||
|
t.Fatalf("type query must return catalog SPEC content: %s", typeSpecQuery)
|
||||||
|
}
|
||||||
|
|
||||||
|
typeBodyQuery, _, err := objectSourceQuery("APP", "demo", "TYPE_BODY")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(typeBodyQuery, "ALL_TYPES") || !strings.Contains(typeBodyQuery, "TO_CHAR(u.BODY)") || !strings.Contains(typeBodyQuery, "u.BODY IS NOT NULL") {
|
||||||
|
t.Fatalf("type body query must return catalog BODY content: %s", typeBodyQuery)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMetadataListConstraintsFromParams(t *testing.T) {
|
func TestMetadataListConstraintsFromParams(t *testing.T) {
|
||||||
params := map[string]json.RawMessage{
|
params := map[string]json.RawMessage{
|
||||||
"filter": json.RawMessage(`"tab"`),
|
"filter": json.RawMessage(`"tab"`),
|
||||||
@@ -648,7 +699,6 @@ func contains(values []string, target string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// -- fake drivers for timeout tests --
|
// -- fake drivers for timeout tests --
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -675,13 +725,13 @@ type xuguBlockingConn struct{}
|
|||||||
func (c *xuguBlockingConn) Prepare(query string) (driver.Stmt, error) {
|
func (c *xuguBlockingConn) Prepare(query string) (driver.Stmt, error) {
|
||||||
return &xuguBlockingStmt{}, nil
|
return &xuguBlockingStmt{}, nil
|
||||||
}
|
}
|
||||||
func (c *xuguBlockingConn) Close() error { return nil }
|
func (c *xuguBlockingConn) Close() error { return nil }
|
||||||
func (c *xuguBlockingConn) Begin() (driver.Tx, error) { return nil, errors.New("not supported") }
|
func (c *xuguBlockingConn) Begin() (driver.Tx, error) { return nil, errors.New("not supported") }
|
||||||
|
|
||||||
type xuguBlockingStmt struct{}
|
type xuguBlockingStmt struct{}
|
||||||
|
|
||||||
func (s *xuguBlockingStmt) Close() error { return nil }
|
func (s *xuguBlockingStmt) Close() error { return nil }
|
||||||
func (s *xuguBlockingStmt) NumInput() int { return -1 }
|
func (s *xuguBlockingStmt) NumInput() int { return -1 }
|
||||||
func (s *xuguBlockingStmt) Exec(args []driver.Value) (driver.Result, error) {
|
func (s *xuguBlockingStmt) Exec(args []driver.Value) (driver.Result, error) {
|
||||||
<-xuguBlockingUnblock
|
<-xuguBlockingUnblock
|
||||||
return nil, errors.New("killed")
|
return nil, errors.New("killed")
|
||||||
@@ -702,13 +752,13 @@ type xuguFastConn struct{}
|
|||||||
func (c *xuguFastConn) Prepare(query string) (driver.Stmt, error) {
|
func (c *xuguFastConn) Prepare(query string) (driver.Stmt, error) {
|
||||||
return &xuguFastStmt{}, nil
|
return &xuguFastStmt{}, nil
|
||||||
}
|
}
|
||||||
func (c *xuguFastConn) Close() error { return nil }
|
func (c *xuguFastConn) Close() error { return nil }
|
||||||
func (c *xuguFastConn) Begin() (driver.Tx, error) { return nil, errors.New("not supported") }
|
func (c *xuguFastConn) Begin() (driver.Tx, error) { return nil, errors.New("not supported") }
|
||||||
|
|
||||||
type xuguFastStmt struct{}
|
type xuguFastStmt struct{}
|
||||||
|
|
||||||
func (s *xuguFastStmt) Close() error { return nil }
|
func (s *xuguFastStmt) Close() error { return nil }
|
||||||
func (s *xuguFastStmt) NumInput() int { return -1 }
|
func (s *xuguFastStmt) NumInput() int { return -1 }
|
||||||
func (s *xuguFastStmt) Exec(args []driver.Value) (driver.Result, error) {
|
func (s *xuguFastStmt) Exec(args []driver.Value) (driver.Result, error) {
|
||||||
return driver.ResultNoRows, nil
|
return driver.ResultNoRows, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1609,14 +1609,17 @@ async function handleQuickOpenSelect(item: any) {
|
|||||||
tableName: item.objectName || item.tableName,
|
tableName: item.objectName || item.tableName,
|
||||||
tableType: item.type === "view" ? "VIEW" : item.type === "materialized_view" ? "MATERIALIZED_VIEW" : "TABLE",
|
tableType: item.type === "view" ? "VIEW" : item.type === "materialized_view" ? "MATERIALIZED_VIEW" : "TABLE",
|
||||||
});
|
});
|
||||||
} else if (item.type === "procedure" || item.type === "function" || item.type === "sequence" || item.type === "package" || item.type === "package-body") {
|
} else if (item.type === "procedure" || item.type === "function" || item.type === "trigger" || item.type === "sequence" || item.type === "package" || item.type === "package-body" || item.type === "type" || item.type === "type-body") {
|
||||||
// Open the object source in a source tab
|
// Open the object source in a source tab
|
||||||
const objectTypeMap: Record<string, ObjectSourceKind> = {
|
const objectTypeMap: Record<string, ObjectSourceKind> = {
|
||||||
procedure: "PROCEDURE",
|
procedure: "PROCEDURE",
|
||||||
function: "FUNCTION",
|
function: "FUNCTION",
|
||||||
|
trigger: "TRIGGER",
|
||||||
sequence: "SEQUENCE",
|
sequence: "SEQUENCE",
|
||||||
package: "PACKAGE",
|
package: "PACKAGE",
|
||||||
"package-body": "PACKAGE_BODY",
|
"package-body": "PACKAGE_BODY",
|
||||||
|
type: "TYPE",
|
||||||
|
"type-body": "TYPE_BODY",
|
||||||
};
|
};
|
||||||
|
|
||||||
const objectType = objectTypeMap[item.type];
|
const objectType = objectTypeMap[item.type];
|
||||||
@@ -1627,7 +1630,7 @@ async function handleQuickOpenSelect(item: any) {
|
|||||||
const result = await api.getObjectSource(item.connectionId, item.database, schema, item.objectName || item.tableName, objectType);
|
const result = await api.getObjectSource(item.connectionId, item.database, schema, item.objectName || item.tableName, objectType);
|
||||||
const tabId = queryStore.createTab(item.connectionId, item.database, `Source - ${item.objectName || item.tableName}`);
|
const tabId = queryStore.createTab(item.connectionId, item.database, `Source - ${item.objectName || item.tableName}`);
|
||||||
queryStore.updateSql(tabId, result.source);
|
queryStore.updateSql(tabId, result.source);
|
||||||
if (item.type !== "sequence") {
|
if (item.type !== "sequence" && item.type !== "trigger" && item.type !== "type" && item.type !== "type-body") {
|
||||||
queryStore.setObjectSource(tabId, {
|
queryStore.setObjectSource(tabId, {
|
||||||
schema,
|
schema,
|
||||||
name: item.objectName || item.tableName,
|
name: item.objectName || item.tableName,
|
||||||
|
|||||||
@@ -93,13 +93,13 @@ import {
|
|||||||
type ObjectBrowserSortDirection,
|
type ObjectBrowserSortDirection,
|
||||||
type ObjectBrowserSortKey,
|
type ObjectBrowserSortKey,
|
||||||
} from "@/lib/table/objectBrowserRows";
|
} from "@/lib/table/objectBrowserRows";
|
||||||
import { resolveRowClickAction, shouldDeferSingleClick, type ObjectBrowserRowAction } from "@/lib/table/objectBrowserRowAction";
|
import { isSourceOnlyObjectBrowserRow, resolveRowClickAction, shouldDeferSingleClick, type ObjectBrowserRowAction } from "@/lib/table/objectBrowserRowAction";
|
||||||
import { filterObjectBrowserTableColumns } from "@/lib/table/objectBrowserTableInfo";
|
import { filterObjectBrowserTableColumns } from "@/lib/table/objectBrowserTableInfo";
|
||||||
import { createSidePanelRequestGuard } from "@/lib/table/sidePanelRequestGuard";
|
import { createSidePanelRequestGuard } from "@/lib/table/sidePanelRequestGuard";
|
||||||
import { runBatchTableTruncate } from "@/lib/table/batchTableTruncate";
|
import { runBatchTableTruncate } from "@/lib/table/batchTableTruncate";
|
||||||
import { tableColumnDefaultDisplayValue } from "@/lib/table/tableColumnDefaultPresentation";
|
import { tableColumnDefaultDisplayValue } from "@/lib/table/tableColumnDefaultPresentation";
|
||||||
|
|
||||||
type ObjectFilter = "all" | "tables" | "views" | "materializedViews" | "procedures" | "functions" | "sequences" | "packages";
|
type ObjectFilter = "all" | "tables" | "views" | "materializedViews" | "procedures" | "functions" | "triggers" | "sequences" | "packages" | "types";
|
||||||
type ObjectBrowserColumnKey = "select" | "name" | "type" | "estimatedRows" | "totalBytes" | "created_at" | "updated_at" | "comment";
|
type ObjectBrowserColumnKey = "select" | "name" | "type" | "estimatedRows" | "totalBytes" | "created_at" | "updated_at" | "comment";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -236,8 +236,10 @@ const viewCount = computed(() => rows.value.filter((row) => row.type === "VIEW")
|
|||||||
const materializedViewCount = computed(() => rows.value.filter((row) => row.type === "MATERIALIZED_VIEW").length);
|
const materializedViewCount = computed(() => rows.value.filter((row) => row.type === "MATERIALIZED_VIEW").length);
|
||||||
const procedureCount = computed(() => rows.value.filter((row) => row.type === "PROCEDURE").length);
|
const procedureCount = computed(() => rows.value.filter((row) => row.type === "PROCEDURE").length);
|
||||||
const functionCount = computed(() => rows.value.filter((row) => row.type === "FUNCTION").length);
|
const functionCount = computed(() => rows.value.filter((row) => row.type === "FUNCTION").length);
|
||||||
|
const triggerCount = computed(() => rows.value.filter((row) => row.type === "TRIGGER").length);
|
||||||
const sequenceCount = computed(() => rows.value.filter((row) => row.type === "SEQUENCE").length);
|
const sequenceCount = computed(() => rows.value.filter((row) => row.type === "SEQUENCE").length);
|
||||||
const packageCount = computed(() => rows.value.filter((row) => row.type === "PACKAGE" || row.type === "PACKAGE_BODY").length);
|
const packageCount = computed(() => rows.value.filter((row) => row.type === "PACKAGE" || row.type === "PACKAGE_BODY").length);
|
||||||
|
const typeCount = computed(() => rows.value.filter((row) => row.type === "TYPE" || row.type === "TYPE_BODY").length);
|
||||||
const canOpenStructureEditor = computed(() => supportsTableStructureEditing(tableStructureDatabaseType.value));
|
const canOpenStructureEditor = computed(() => supportsTableStructureEditing(tableStructureDatabaseType.value));
|
||||||
const canOpenDiagram = computed(() => !!props.database && supportsSchemaDiagram(effectiveDatabaseType.value));
|
const canOpenDiagram = computed(() => !!props.database && supportsSchemaDiagram(effectiveDatabaseType.value));
|
||||||
const canOpenTableImport = computed(() => !!props.database && supportsTableImport(effectiveDatabaseType.value));
|
const canOpenTableImport = computed(() => !!props.database && supportsTableImport(effectiveDatabaseType.value));
|
||||||
@@ -253,8 +255,10 @@ const objectFilters = computed<ObjectFilter[]>(() =>
|
|||||||
["materializedViews", materializedViewCount.value],
|
["materializedViews", materializedViewCount.value],
|
||||||
["procedures", procedureCount.value],
|
["procedures", procedureCount.value],
|
||||||
["functions", functionCount.value],
|
["functions", functionCount.value],
|
||||||
|
["triggers", triggerCount.value],
|
||||||
["sequences", sequenceCount.value],
|
["sequences", sequenceCount.value],
|
||||||
["packages", packageCount.value],
|
["packages", packageCount.value],
|
||||||
|
["types", typeCount.value],
|
||||||
] as Array<[ObjectFilter, number]>
|
] as Array<[ObjectFilter, number]>
|
||||||
)
|
)
|
||||||
.filter(([filter, count]) => filter === "all" || count > 0)
|
.filter(([filter, count]) => filter === "all" || count > 0)
|
||||||
@@ -526,8 +530,10 @@ function iconFor(row: ObjectBrowserRow) {
|
|||||||
if (row.type === "VIEW" || row.type === "MATERIALIZED_VIEW") return Eye;
|
if (row.type === "VIEW" || row.type === "MATERIALIZED_VIEW") return Eye;
|
||||||
if (row.type === "PROCEDURE") return ScrollText;
|
if (row.type === "PROCEDURE") return ScrollText;
|
||||||
if (row.type === "FUNCTION") return Braces;
|
if (row.type === "FUNCTION") return Braces;
|
||||||
|
if (row.type === "TRIGGER") return RotateCcw;
|
||||||
if (row.type === "SEQUENCE") return ListTree;
|
if (row.type === "SEQUENCE") return ListTree;
|
||||||
if (row.type === "PACKAGE" || row.type === "PACKAGE_BODY") return Package;
|
if (row.type === "PACKAGE" || row.type === "PACKAGE_BODY") return Package;
|
||||||
|
if (row.type === "TYPE" || row.type === "TYPE_BODY") return Braces;
|
||||||
return Table2;
|
return Table2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -536,9 +542,12 @@ function typeLabel(type: ObjectBrowserRow["type"]) {
|
|||||||
if (type === "VIEW") return t("objects.view");
|
if (type === "VIEW") return t("objects.view");
|
||||||
if (type === "PROCEDURE") return t("objects.procedure");
|
if (type === "PROCEDURE") return t("objects.procedure");
|
||||||
if (type === "FUNCTION") return t("objects.function");
|
if (type === "FUNCTION") return t("objects.function");
|
||||||
|
if (type === "TRIGGER") return t("objects.trigger");
|
||||||
if (type === "SEQUENCE") return t("objects.sequence");
|
if (type === "SEQUENCE") return t("objects.sequence");
|
||||||
if (type === "PACKAGE") return t("objects.package");
|
if (type === "PACKAGE") return t("objects.package");
|
||||||
if (type === "PACKAGE_BODY") return t("objects.packageBody");
|
if (type === "PACKAGE_BODY") return t("objects.packageBody");
|
||||||
|
if (type === "TYPE") return t("objects.typeDefinition");
|
||||||
|
if (type === "TYPE_BODY") return t("objects.typeBody");
|
||||||
return t("objects.table");
|
return t("objects.table");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -635,8 +644,10 @@ function rowMatchesObjectFilter(row: ObjectBrowserRow) {
|
|||||||
if (objectFilter.value === "materializedViews") return row.type === "MATERIALIZED_VIEW";
|
if (objectFilter.value === "materializedViews") return row.type === "MATERIALIZED_VIEW";
|
||||||
if (objectFilter.value === "procedures") return row.type === "PROCEDURE";
|
if (objectFilter.value === "procedures") return row.type === "PROCEDURE";
|
||||||
if (objectFilter.value === "functions") return row.type === "FUNCTION";
|
if (objectFilter.value === "functions") return row.type === "FUNCTION";
|
||||||
|
if (objectFilter.value === "triggers") return row.type === "TRIGGER";
|
||||||
if (objectFilter.value === "sequences") return row.type === "SEQUENCE";
|
if (objectFilter.value === "sequences") return row.type === "SEQUENCE";
|
||||||
if (objectFilter.value === "packages") return row.type === "PACKAGE" || row.type === "PACKAGE_BODY";
|
if (objectFilter.value === "packages") return row.type === "PACKAGE" || row.type === "PACKAGE_BODY";
|
||||||
|
if (objectFilter.value === "types") return row.type === "TYPE" || row.type === "TYPE_BODY";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -673,8 +684,10 @@ function iconClass(type: ObjectBrowserRow["type"]) {
|
|||||||
if (type === "VIEW" || type === "MATERIALIZED_VIEW") return "text-purple-500";
|
if (type === "VIEW" || type === "MATERIALIZED_VIEW") return "text-purple-500";
|
||||||
if (type === "PROCEDURE") return "text-blue-500";
|
if (type === "PROCEDURE") return "text-blue-500";
|
||||||
if (type === "FUNCTION") return "text-amber-500";
|
if (type === "FUNCTION") return "text-amber-500";
|
||||||
|
if (type === "TRIGGER") return "text-rose-500";
|
||||||
if (type === "SEQUENCE") return "text-emerald-500";
|
if (type === "SEQUENCE") return "text-emerald-500";
|
||||||
if (type === "PACKAGE" || type === "PACKAGE_BODY") return "text-cyan-500";
|
if (type === "PACKAGE" || type === "PACKAGE_BODY") return "text-cyan-500";
|
||||||
|
if (type === "TYPE" || type === "TYPE_BODY") return "text-violet-500";
|
||||||
return "text-green-500";
|
return "text-green-500";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -682,8 +695,10 @@ function iconBgClass(type: ObjectBrowserRow["type"]) {
|
|||||||
if (type === "VIEW" || type === "MATERIALIZED_VIEW") return "object-browser-icon-bg object-browser-icon-bg-view";
|
if (type === "VIEW" || type === "MATERIALIZED_VIEW") return "object-browser-icon-bg object-browser-icon-bg-view";
|
||||||
if (type === "PROCEDURE") return "object-browser-icon-bg object-browser-icon-bg-procedure";
|
if (type === "PROCEDURE") return "object-browser-icon-bg object-browser-icon-bg-procedure";
|
||||||
if (type === "FUNCTION") return "object-browser-icon-bg object-browser-icon-bg-function";
|
if (type === "FUNCTION") return "object-browser-icon-bg object-browser-icon-bg-function";
|
||||||
|
if (type === "TRIGGER") return "object-browser-icon-bg object-browser-icon-bg-procedure";
|
||||||
if (type === "SEQUENCE") return "object-browser-icon-bg object-browser-icon-bg-sequence";
|
if (type === "SEQUENCE") return "object-browser-icon-bg object-browser-icon-bg-sequence";
|
||||||
if (type === "PACKAGE" || type === "PACKAGE_BODY") return "object-browser-icon-bg object-browser-icon-bg-package";
|
if (type === "PACKAGE" || type === "PACKAGE_BODY") return "object-browser-icon-bg object-browser-icon-bg-package";
|
||||||
|
if (type === "TYPE" || type === "TYPE_BODY") return "object-browser-icon-bg object-browser-icon-bg-function";
|
||||||
return "object-browser-icon-bg object-browser-icon-bg-table";
|
return "object-browser-icon-bg object-browser-icon-bg-table";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1013,14 +1028,16 @@ async function openSource(row: ObjectBrowserRow) {
|
|||||||
try {
|
try {
|
||||||
const result = await api.getObjectSource(connectionId, database, schema, row.name, row.type as ObjectSourceKind);
|
const result = await api.getObjectSource(connectionId, database, schema, row.name, row.type as ObjectSourceKind);
|
||||||
if (sidePanelGuard.isStale(epoch)) return;
|
if (sidePanelGuard.isStale(epoch)) return;
|
||||||
sourceCanEdit.value = result.editable !== false && row.type !== "SEQUENCE";
|
sourceCanEdit.value = result.editable !== false && !["SEQUENCE", "TRIGGER", "TYPE", "TYPE_BODY"].includes(row.type);
|
||||||
const editable = await api.buildEditableObjectSource({
|
const editable = sourceCanEdit.value
|
||||||
databaseType: effectiveDatabaseType.value,
|
? await api.buildEditableObjectSource({
|
||||||
objectType: row.type as ObjectSourceKind,
|
databaseType: effectiveDatabaseType.value,
|
||||||
schema,
|
objectType: row.type as ObjectSourceKind,
|
||||||
name: row.name,
|
schema,
|
||||||
source: result.source,
|
name: row.name,
|
||||||
});
|
source: result.source,
|
||||||
|
})
|
||||||
|
: result.source;
|
||||||
if (sidePanelGuard.isStale(epoch)) return;
|
if (sidePanelGuard.isStale(epoch)) return;
|
||||||
// Viewing database source must preserve its original whitespace and comments;
|
// Viewing database source must preserve its original whitespace and comments;
|
||||||
// formatting remains an explicit editor action instead of altering it on open.
|
// formatting remains an explicit editor action instead of altering it on open.
|
||||||
@@ -2129,8 +2146,10 @@ function filterCount(filter: ObjectFilter) {
|
|||||||
if (filter === "materializedViews") return materializedViewCount.value;
|
if (filter === "materializedViews") return materializedViewCount.value;
|
||||||
if (filter === "procedures") return procedureCount.value;
|
if (filter === "procedures") return procedureCount.value;
|
||||||
if (filter === "functions") return functionCount.value;
|
if (filter === "functions") return functionCount.value;
|
||||||
|
if (filter === "triggers") return triggerCount.value;
|
||||||
if (filter === "sequences") return sequenceCount.value;
|
if (filter === "sequences") return sequenceCount.value;
|
||||||
if (filter === "packages") return packageCount.value;
|
if (filter === "packages") return packageCount.value;
|
||||||
|
if (filter === "types") return typeCount.value;
|
||||||
return rows.value.length;
|
return rows.value.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2146,11 +2165,15 @@ function filterLabel(filter: ObjectFilter) {
|
|||||||
? "objects.procedures"
|
? "objects.procedures"
|
||||||
: filter === "functions"
|
: filter === "functions"
|
||||||
? "objects.functions"
|
? "objects.functions"
|
||||||
: filter === "sequences"
|
: filter === "triggers"
|
||||||
? "objects.sequences"
|
? "tree.triggers"
|
||||||
: filter === "packages"
|
: filter === "sequences"
|
||||||
? "objects.packages"
|
? "objects.sequences"
|
||||||
: "objects.all";
|
: filter === "packages"
|
||||||
|
? "objects.packages"
|
||||||
|
: filter === "types"
|
||||||
|
? "tree.types"
|
||||||
|
: "objects.all";
|
||||||
return `${t(key)} ${filterCount(filter)}`;
|
return `${t(key)} ${filterCount(filter)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2334,8 +2357,7 @@ function getPackageMenuItems(item: ObjectBrowserRow): ContextMenuItem[] {
|
|||||||
function getObjectBrowserMenuItems(item: ObjectBrowserRow): ContextMenuItem[] {
|
function getObjectBrowserMenuItems(item: ObjectBrowserRow): ContextMenuItem[] {
|
||||||
if (item.type === "TABLE") return getTableMenuItems(item);
|
if (item.type === "TABLE") return getTableMenuItems(item);
|
||||||
if (item.type === "VIEW" || item.type === "MATERIALIZED_VIEW") return getViewMenuItems(item);
|
if (item.type === "VIEW" || item.type === "MATERIALIZED_VIEW") return getViewMenuItems(item);
|
||||||
if (item.type === "SEQUENCE") return getPackageMenuItems(item);
|
if (isSourceOnlyObjectBrowserRow(item)) return getPackageMenuItems(item);
|
||||||
if (item.type === "PACKAGE" || item.type === "PACKAGE_BODY") return getPackageMenuItems(item);
|
|
||||||
return getProcFuncMenuItems(item);
|
return getProcFuncMenuItems(item);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -154,9 +154,9 @@ watch(deferredSearchQuery, (newQuery, oldQuery) => {
|
|||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
});
|
});
|
||||||
|
|
||||||
const searchableObjectGroupTypes = new Set<TreeNodeType>(["group-tables", "group-views", "group-materialized-views", "group-procedures", "group-functions", "group-sequences", "group-packages"]);
|
const searchableObjectGroupTypes = new Set<TreeNodeType>(["group-tables", "group-views", "group-materialized-views", "group-procedures", "group-functions", "group-triggers", "group-sequences", "group-packages", "group-types"]);
|
||||||
const simpleObjectParentTypes = new Set<TreeNodeType>(["database", "schema", "linked-server-schema"]);
|
const simpleObjectParentTypes = new Set<TreeNodeType>(["database", "schema", "linked-server-schema"]);
|
||||||
const simpleObjectChildTypes = new Set<TreeNodeType>(["table", "view", "materialized_view", "procedure", "function", "sequence", "package", "package-body", "load-more"]);
|
const simpleObjectChildTypes = new Set<TreeNodeType>(["table", "view", "materialized_view", "procedure", "function", "trigger", "sequence", "package", "package-body", "type", "type-body", "load-more"]);
|
||||||
|
|
||||||
function isSimpleObjectSearchParent(node: TreeNode): boolean {
|
function isSimpleObjectSearchParent(node: TreeNode): boolean {
|
||||||
return settingsStore.editorSettings.sidebarObjectDisplay === "simple" && simpleObjectParentTypes.has(node.type) && node.isExpanded === true && (!!node.children?.some((child) => simpleObjectChildTypes.has(child.type)) || !!store.sidebarTableSearchQueries[node.id]?.trim());
|
return settingsStore.editorSettings.sidebarObjectDisplay === "simple" && simpleObjectParentTypes.has(node.type) && node.isExpanded === true && (!!node.children?.some((child) => simpleObjectChildTypes.has(child.type)) || !!store.sidebarTableSearchQueries[node.id]?.trim());
|
||||||
|
|||||||
@@ -445,7 +445,22 @@ function hasNodeDatabaseContext(node: TreeNode): node is TreeNode & { connection
|
|||||||
return !!node.connectionId && hasTreeNodeDatabaseContext(node);
|
return !!node.connectionId && hasTreeNodeDatabaseContext(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
const groupTypes: Set<TreeNodeType> = new Set(["group-columns", "group-indexes", "group-fkeys", "group-triggers", "group-tables", "group-views", "group-materialized-views", "group-procedures", "group-functions", "group-sequences", "group-packages", "group-partitions", "group-extensions"]);
|
const groupTypes: Set<TreeNodeType> = new Set([
|
||||||
|
"group-columns",
|
||||||
|
"group-indexes",
|
||||||
|
"group-fkeys",
|
||||||
|
"group-triggers",
|
||||||
|
"group-tables",
|
||||||
|
"group-views",
|
||||||
|
"group-materialized-views",
|
||||||
|
"group-procedures",
|
||||||
|
"group-functions",
|
||||||
|
"group-sequences",
|
||||||
|
"group-packages",
|
||||||
|
"group-types",
|
||||||
|
"group-partitions",
|
||||||
|
"group-extensions",
|
||||||
|
]);
|
||||||
|
|
||||||
function isGroupLabel(node: TreeNode): boolean {
|
function isGroupLabel(node: TreeNode): boolean {
|
||||||
return groupTypes.has(node.type);
|
return groupTypes.has(node.type);
|
||||||
@@ -642,7 +657,7 @@ function runRowClickAction(clickDetail: number) {
|
|||||||
scheduleOpenData(node);
|
scheduleOpenData(node);
|
||||||
} else if (isDocumentBrowserTreeNode(node.type)) {
|
} else if (isDocumentBrowserTreeNode(node.type)) {
|
||||||
openMongoTreeData(node);
|
openMongoTreeData(node);
|
||||||
} else if (node.type === "procedure" || node.type === "function" || node.type === "sequence" || node.type === "package" || node.type === "package-body") {
|
} else if (node.type === "procedure" || node.type === "function" || node.type === "trigger" || node.type === "sequence" || node.type === "package" || node.type === "package-body" || node.type === "type" || node.type === "type-body") {
|
||||||
openObjectSourceDialog(false);
|
openObjectSourceDialog(false);
|
||||||
} else if (action === "toggle") {
|
} else if (action === "toggle") {
|
||||||
toggle();
|
toggle();
|
||||||
@@ -2833,7 +2848,7 @@ const canOpenFieldLineage = computed(() => {
|
|||||||
|
|
||||||
const hasTypeMenu = computed(() => {
|
const hasTypeMenu = computed(() => {
|
||||||
const t = activeNode.value.type;
|
const t = activeNode.value.type;
|
||||||
return t === "connection" || t === "database" || t === "schema" || t === "table" || t === "view" || t === "column" || t === "procedure" || t === "function" || t === "package" || t === "package-body" || isGroupLabel(activeNode.value);
|
return t === "connection" || t === "database" || t === "schema" || t === "table" || t === "view" || t === "column" || t === "procedure" || t === "function" || t === "trigger" || t === "package" || t === "package-body" || t === "type" || t === "type-body" || isGroupLabel(activeNode.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
const isSelected = computed(() => connectionStore.selectedTreeNodeId === activeNode.value.id);
|
const isSelected = computed(() => connectionStore.selectedTreeNodeId === activeNode.value.id);
|
||||||
@@ -3871,7 +3886,7 @@ function buildObjectSidebarMenu(context: SidebarMenuFactoryContext): boolean {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.type === "index" || node.type === "fkey" || node.type === "trigger") {
|
if (node.type === "index" || node.type === "fkey" || (node.type === "trigger" && !!node.tableName)) {
|
||||||
items.push({ label: t("contextMenu.copyName"), action: copyName, icon: Copy, shortcut: shortcutCopyName.value });
|
items.push({ label: t("contextMenu.copyName"), action: copyName, icon: Copy, shortcut: shortcutCopyName.value });
|
||||||
if (node.type === "index" && canOpenStructureEditor.value) {
|
if (node.type === "index" && canOpenStructureEditor.value) {
|
||||||
items.push({ label: "", separator: true });
|
items.push({ label: "", separator: true });
|
||||||
@@ -3931,7 +3946,7 @@ function buildObjectSidebarMenu(context: SidebarMenuFactoryContext): boolean {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.type === "package" || node.type === "package-body") {
|
if (node.type === "trigger" || node.type === "package" || node.type === "package-body" || node.type === "type" || node.type === "type-body") {
|
||||||
items.push({ label: t("contextMenu.viewSource"), action: () => openObjectSourceDialog(false), icon: Code2 });
|
items.push({ label: t("contextMenu.viewSource"), action: () => openObjectSourceDialog(false), icon: Code2 });
|
||||||
items.push({ label: "", separator: true });
|
items.push({ label: "", separator: true });
|
||||||
items.push({ label: t("contextMenu.copyName"), action: copyName, icon: Copy, shortcut: shortcutCopyName.value });
|
items.push({ label: t("contextMenu.copyName"), action: copyName, icon: Copy, shortcut: shortcutCopyName.value });
|
||||||
|
|||||||
@@ -261,6 +261,10 @@ function getIconInfo(node: TreeNode): { icon: any; colorClass: string } | null {
|
|||||||
return { icon: Package, colorClass: "text-cyan-500" };
|
return { icon: Package, colorClass: "text-cyan-500" };
|
||||||
case "package-body":
|
case "package-body":
|
||||||
return { icon: FileCode, colorClass: "text-cyan-400" };
|
return { icon: FileCode, colorClass: "text-cyan-400" };
|
||||||
|
case "type":
|
||||||
|
return { icon: Braces, colorClass: "text-violet-500" };
|
||||||
|
case "type-body":
|
||||||
|
return { icon: FileCode, colorClass: "text-violet-400" };
|
||||||
case "group-tables":
|
case "group-tables":
|
||||||
return { icon: Table, colorClass: "text-green-500" };
|
return { icon: Table, colorClass: "text-green-500" };
|
||||||
case "group-views":
|
case "group-views":
|
||||||
@@ -275,6 +279,8 @@ function getIconInfo(node: TreeNode): { icon: any; colorClass: string } | null {
|
|||||||
return { icon: ListTree, colorClass: "text-emerald-500" };
|
return { icon: ListTree, colorClass: "text-emerald-500" };
|
||||||
case "group-packages":
|
case "group-packages":
|
||||||
return { icon: Package, colorClass: "text-cyan-500" };
|
return { icon: Package, colorClass: "text-cyan-500" };
|
||||||
|
case "group-types":
|
||||||
|
return { icon: Braces, colorClass: "text-violet-500" };
|
||||||
case "group-partitions":
|
case "group-partitions":
|
||||||
return { icon: node.isExpanded ? FolderOpen : FolderClosed, colorClass: "text-green-400" };
|
return { icon: node.isExpanded ? FolderOpen : FolderClosed, colorClass: "text-green-400" };
|
||||||
case "group-extensions":
|
case "group-extensions":
|
||||||
@@ -288,7 +294,22 @@ function getIconInfo(node: TreeNode): { icon: any; colorClass: string } | null {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const groupTypes: Set<TreeNodeType> = new Set(["group-columns", "group-indexes", "group-fkeys", "group-triggers", "group-tables", "group-views", "group-materialized-views", "group-procedures", "group-functions", "group-sequences", "group-packages", "group-partitions", "group-extensions"]);
|
const groupTypes: Set<TreeNodeType> = new Set([
|
||||||
|
"group-columns",
|
||||||
|
"group-indexes",
|
||||||
|
"group-fkeys",
|
||||||
|
"group-triggers",
|
||||||
|
"group-tables",
|
||||||
|
"group-views",
|
||||||
|
"group-materialized-views",
|
||||||
|
"group-procedures",
|
||||||
|
"group-functions",
|
||||||
|
"group-sequences",
|
||||||
|
"group-packages",
|
||||||
|
"group-types",
|
||||||
|
"group-partitions",
|
||||||
|
"group-extensions",
|
||||||
|
]);
|
||||||
|
|
||||||
function isGroupLabel(node: TreeNode): boolean {
|
function isGroupLabel(node: TreeNode): boolean {
|
||||||
return groupTypes.has(node.type);
|
return groupTypes.has(node.type);
|
||||||
@@ -304,10 +325,11 @@ function displayLabel(node: TreeNode): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function visibleLabel(node: TreeNode): string {
|
function visibleLabel(node: TreeNode): string {
|
||||||
|
const withValidity = (label: string) => (node.valid === false ? `${label} · INVALID` : label);
|
||||||
if (node.type === "table" || node.type === "view" || node.type === "materialized_view" || node.type === "mongo-collection" || node.type === "vector-collection" || node.type === "elasticsearch-index") {
|
if (node.type === "table" || node.type === "view" || node.type === "materialized_view" || node.type === "mongo-collection" || node.type === "vector-collection" || node.type === "elasticsearch-index") {
|
||||||
return sidebarDisplayTableName(node.label, settingsStore.editorSettings.sidebarHiddenTablePrefixes);
|
return withValidity(sidebarDisplayTableName(node.label, settingsStore.editorSettings.sidebarHiddenTablePrefixes));
|
||||||
}
|
}
|
||||||
return displayLabel(node);
|
return withValidity(displayLabel(node));
|
||||||
}
|
}
|
||||||
|
|
||||||
type DetailTooltipRow = {
|
type DetailTooltipRow = {
|
||||||
|
|||||||
@@ -1855,6 +1855,7 @@ export default {
|
|||||||
functions: "Functions",
|
functions: "Functions",
|
||||||
sequences: "Sequences",
|
sequences: "Sequences",
|
||||||
packages: "Packages",
|
packages: "Packages",
|
||||||
|
types: "Types",
|
||||||
gridfs: "GridFS",
|
gridfs: "GridFS",
|
||||||
buckets: "Buckets",
|
buckets: "Buckets",
|
||||||
partitions: "Partitions",
|
partitions: "Partitions",
|
||||||
@@ -2092,9 +2093,12 @@ export default {
|
|||||||
view: "View",
|
view: "View",
|
||||||
procedure: "Procedure",
|
procedure: "Procedure",
|
||||||
function: "Function",
|
function: "Function",
|
||||||
|
trigger: "Trigger",
|
||||||
sequence: "Sequence",
|
sequence: "Sequence",
|
||||||
package: "Package",
|
package: "Package",
|
||||||
packageBody: "Package Body",
|
packageBody: "Package Body",
|
||||||
|
typeDefinition: "Type",
|
||||||
|
typeBody: "Type Body",
|
||||||
name: "Name",
|
name: "Name",
|
||||||
type: "Type",
|
type: "Type",
|
||||||
rows: "Rows",
|
rows: "Rows",
|
||||||
|
|||||||
@@ -1802,6 +1802,7 @@ export default withEnglishFallback({
|
|||||||
loadMore: "Cargar más...",
|
loadMore: "Cargar más...",
|
||||||
objectBrowser: "Explorar en el navegador de objetos ({count})",
|
objectBrowser: "Explorar en el navegador de objetos ({count})",
|
||||||
extensions: "Extensiones",
|
extensions: "Extensiones",
|
||||||
|
types: "tipo",
|
||||||
},
|
},
|
||||||
userAdmin: {
|
userAdmin: {
|
||||||
title: "Usuarios y Privilegios",
|
title: "Usuarios y Privilegios",
|
||||||
@@ -1988,6 +1989,9 @@ export default withEnglishFallback({
|
|||||||
sortAsc: "ascendente",
|
sortAsc: "ascendente",
|
||||||
sortDesc: "descendente",
|
sortDesc: "descendente",
|
||||||
sortBy: "ordenar por",
|
sortBy: "ordenar por",
|
||||||
|
trigger: "disparador",
|
||||||
|
typeDefinition: "definición de tipo",
|
||||||
|
typeBody: "cuerpo de tipo",
|
||||||
},
|
},
|
||||||
structureEditor: {
|
structureEditor: {
|
||||||
mysqlDataTypeHelp: {
|
mysqlDataTypeHelp: {
|
||||||
|
|||||||
@@ -1800,6 +1800,7 @@ export default withEnglishFallback({
|
|||||||
loadMore: "Carica altro...",
|
loadMore: "Carica altro...",
|
||||||
objectBrowser: "Sfoglia in Esplora Oggetti ({count})",
|
objectBrowser: "Sfoglia in Esplora Oggetti ({count})",
|
||||||
extensions: "Estensioni",
|
extensions: "Estensioni",
|
||||||
|
types: "Tipi",
|
||||||
},
|
},
|
||||||
userAdmin: {
|
userAdmin: {
|
||||||
title: "Utenti e Privilegi",
|
title: "Utenti e Privilegi",
|
||||||
@@ -1986,6 +1987,9 @@ export default withEnglishFallback({
|
|||||||
sortAsc: "Crescente",
|
sortAsc: "Crescente",
|
||||||
sortDesc: "Decrescente",
|
sortDesc: "Decrescente",
|
||||||
sortBy: "Ordina per",
|
sortBy: "Ordina per",
|
||||||
|
trigger: "Trigger",
|
||||||
|
typeDefinition: "Definizione del tipo",
|
||||||
|
typeBody: "Corpo del tipo",
|
||||||
},
|
},
|
||||||
structureEditor: {
|
structureEditor: {
|
||||||
mysqlDataTypeHelp: {
|
mysqlDataTypeHelp: {
|
||||||
|
|||||||
@@ -1801,6 +1801,7 @@ export default withEnglishFallback({
|
|||||||
linkedServers: "リンクサーバー",
|
linkedServers: "リンクサーバー",
|
||||||
materializedViews: "マテリアライズドビュー",
|
materializedViews: "マテリアライズドビュー",
|
||||||
extensions: "拡張機能",
|
extensions: "拡張機能",
|
||||||
|
types: "タイプ",
|
||||||
},
|
},
|
||||||
zookeeper: {
|
zookeeper: {
|
||||||
prefixPlaceholder: "パスプレフィックス(例: /app/)",
|
prefixPlaceholder: "パスプレフィックス(例: /app/)",
|
||||||
@@ -2021,6 +2022,9 @@ export default withEnglishFallback({
|
|||||||
sortAsc: "昇順",
|
sortAsc: "昇順",
|
||||||
sortDesc: "降順",
|
sortDesc: "降順",
|
||||||
sortBy: "並べ替え",
|
sortBy: "並べ替え",
|
||||||
|
trigger: "トリガー",
|
||||||
|
typeDefinition: "タイプ",
|
||||||
|
typeBody: "タイプ本体",
|
||||||
},
|
},
|
||||||
structureEditor: {
|
structureEditor: {
|
||||||
mysqlDataTypeHelp: {
|
mysqlDataTypeHelp: {
|
||||||
|
|||||||
@@ -1802,6 +1802,7 @@ export default withEnglishFallback({
|
|||||||
loadMore: "Carregar mais...",
|
loadMore: "Carregar mais...",
|
||||||
objectBrowser: "Navegar no Navegador de Objetos ({count})",
|
objectBrowser: "Navegar no Navegador de Objetos ({count})",
|
||||||
extensions: "Extensões",
|
extensions: "Extensões",
|
||||||
|
types: "Tipos",
|
||||||
},
|
},
|
||||||
userAdmin: {
|
userAdmin: {
|
||||||
title: "Usuários e Privilégios",
|
title: "Usuários e Privilégios",
|
||||||
@@ -1988,6 +1989,9 @@ export default withEnglishFallback({
|
|||||||
sortAsc: "Crescente",
|
sortAsc: "Crescente",
|
||||||
sortDesc: "Decrescente",
|
sortDesc: "Decrescente",
|
||||||
sortBy: "Ordenar por",
|
sortBy: "Ordenar por",
|
||||||
|
trigger: "Gatilho",
|
||||||
|
typeDefinition: "Definição de tipo",
|
||||||
|
typeBody: "Corpo do tipo",
|
||||||
},
|
},
|
||||||
structureEditor: {
|
structureEditor: {
|
||||||
mysqlDataTypeHelp: {
|
mysqlDataTypeHelp: {
|
||||||
|
|||||||
@@ -1855,6 +1855,7 @@ export default withEnglishFallback({
|
|||||||
functions: "函数",
|
functions: "函数",
|
||||||
sequences: "序列",
|
sequences: "序列",
|
||||||
packages: "包",
|
packages: "包",
|
||||||
|
types: "类型",
|
||||||
partitions: "分区",
|
partitions: "分区",
|
||||||
childTables: "子表",
|
childTables: "子表",
|
||||||
loadMore: "加载更多...",
|
loadMore: "加载更多...",
|
||||||
@@ -2062,9 +2063,12 @@ export default withEnglishFallback({
|
|||||||
view: "视图",
|
view: "视图",
|
||||||
procedure: "存储过程",
|
procedure: "存储过程",
|
||||||
function: "函数",
|
function: "函数",
|
||||||
|
trigger: "触发器",
|
||||||
sequence: "序列",
|
sequence: "序列",
|
||||||
package: "包",
|
package: "包",
|
||||||
packageBody: "包体",
|
packageBody: "包体",
|
||||||
|
typeDefinition: "类型",
|
||||||
|
typeBody: "类型体",
|
||||||
name: "名称",
|
name: "名称",
|
||||||
type: "类型",
|
type: "类型",
|
||||||
rows: "行数",
|
rows: "行数",
|
||||||
|
|||||||
@@ -1802,6 +1802,7 @@ export default withEnglishFallback({
|
|||||||
loadMore: "載入更多...",
|
loadMore: "載入更多...",
|
||||||
objectBrowser: "在物件瀏覽器中檢視 ({count})",
|
objectBrowser: "在物件瀏覽器中檢視 ({count})",
|
||||||
extensions: "擴展",
|
extensions: "擴展",
|
||||||
|
types: "類型",
|
||||||
},
|
},
|
||||||
objects: {
|
objects: {
|
||||||
all: "全部",
|
all: "全部",
|
||||||
@@ -1858,6 +1859,9 @@ export default withEnglishFallback({
|
|||||||
sortAsc: "升序",
|
sortAsc: "升序",
|
||||||
sortDesc: "降序",
|
sortDesc: "降序",
|
||||||
sortBy: "排序方式",
|
sortBy: "排序方式",
|
||||||
|
trigger: "觸發器",
|
||||||
|
typeDefinition: "類型",
|
||||||
|
typeBody: "類型體",
|
||||||
},
|
},
|
||||||
structureEditor: {
|
structureEditor: {
|
||||||
mysqlDataTypeHelp: {
|
mysqlDataTypeHelp: {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { doubleClickRowAction, resolveRowClickAction, shouldDeferSingleClick, singleClickRowAction } from "@/lib/table/objectBrowserRowAction";
|
import { doubleClickRowAction, isSourceOnlyObjectBrowserRow, resolveRowClickAction, shouldDeferSingleClick, singleClickRowAction } from "@/lib/table/objectBrowserRowAction";
|
||||||
import type { ObjectBrowserRow } from "@/lib/table/objectBrowserRows";
|
import type { ObjectBrowserRow } from "@/lib/table/objectBrowserRows";
|
||||||
|
|
||||||
function row(type: ObjectBrowserRow["type"], name = "test"): ObjectBrowserRow {
|
function row(type: ObjectBrowserRow["type"], name = "test"): ObjectBrowserRow {
|
||||||
@@ -39,6 +39,10 @@ describe("singleClickRowAction", () => {
|
|||||||
expect(singleClickRowAction(row("PACKAGE_BODY", "pkg_body_test"))).toBe("open-source");
|
expect(singleClickRowAction(row("PACKAGE_BODY", "pkg_body_test"))).toBe("open-source");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.each(["TRIGGER", "TYPE", "TYPE_BODY"] as const)("returns open-source for %s", (type) => {
|
||||||
|
expect(singleClickRowAction(row(type, "programmable_test"))).toBe("open-source");
|
||||||
|
});
|
||||||
|
|
||||||
it("returns none for null/undefined", () => {
|
it("returns none for null/undefined", () => {
|
||||||
expect(singleClickRowAction(null)).toBe("none");
|
expect(singleClickRowAction(null)).toBe("none");
|
||||||
expect(singleClickRowAction(undefined)).toBe("none");
|
expect(singleClickRowAction(undefined)).toBe("none");
|
||||||
@@ -63,6 +67,17 @@ describe("doubleClickRowAction", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("isSourceOnlyObjectBrowserRow", () => {
|
||||||
|
it.each(["TRIGGER", "TYPE", "TYPE_BODY"] as const)("marks %s as source-only", (type) => {
|
||||||
|
expect(isSourceOnlyObjectBrowserRow(row(type))).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps procedure and function mutation menus separate", () => {
|
||||||
|
expect(isSourceOnlyObjectBrowserRow(row("PROCEDURE"))).toBe(false);
|
||||||
|
expect(isSourceOnlyObjectBrowserRow(row("FUNCTION"))).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("resolveRowClickAction", () => {
|
describe("resolveRowClickAction", () => {
|
||||||
const tableRow = row("TABLE", "users");
|
const tableRow = row("TABLE", "users");
|
||||||
const viewRow = row("VIEW", "v_users");
|
const viewRow = row("VIEW", "v_users");
|
||||||
|
|||||||
@@ -29,6 +29,22 @@ describe("PostgreSQL overloaded routines", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("programmable database objects", () => {
|
||||||
|
it("keeps Xugu trigger/type nodes distinct and preserves an invalid status", () => {
|
||||||
|
const objects: ObjectInfo[] = [
|
||||||
|
{ name: "TRG_AUDIT", object_type: "TRIGGER", schema: "APP", valid: false },
|
||||||
|
{ name: "ADDRESS_T", object_type: "TYPE", schema: "APP", valid: true },
|
||||||
|
{ name: "ADDRESS_T", object_type: "TYPE_BODY", schema: "APP", valid: true },
|
||||||
|
];
|
||||||
|
|
||||||
|
const nodes = buildSimpleObjectTreeNodes({ ...context, schema: "APP", objects });
|
||||||
|
|
||||||
|
expect(nodes).toEqual(
|
||||||
|
expect.arrayContaining([expect.objectContaining({ type: "trigger", objectName: "TRG_AUDIT", valid: false }), expect.objectContaining({ type: "type", objectName: "ADDRESS_T", valid: true }), expect.objectContaining({ type: "type-body", objectName: "ADDRESS_T", valid: true })]),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("PostgreSQL table hierarchy", () => {
|
describe("PostgreSQL table hierarchy", () => {
|
||||||
it("keeps schema pagination visible at the table-group root when a page ends inside nested partitions", () => {
|
it("keeps schema pagination visible at the table-group root when a page ends inside nested partitions", () => {
|
||||||
const nodes = buildTableTreeNodes({
|
const nodes = buildTableTreeNodes({
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { DatabaseType } from "@/types/database";
|
import type { DatabaseType } from "@/types/database";
|
||||||
|
|
||||||
export type SidebarObjectKind = "TABLE" | "VIEW" | "MATERIALIZED_VIEW" | "PROCEDURE" | "FUNCTION" | "SEQUENCE" | "PACKAGE" | "PACKAGE_BODY";
|
export type SidebarObjectKind = "TABLE" | "VIEW" | "MATERIALIZED_VIEW" | "PROCEDURE" | "FUNCTION" | "TRIGGER" | "SEQUENCE" | "PACKAGE" | "PACKAGE_BODY" | "TYPE" | "TYPE_BODY";
|
||||||
|
|
||||||
export interface DatabaseObjectCapabilities {
|
export interface DatabaseObjectCapabilities {
|
||||||
sidebarObjects: SidebarObjectKind[];
|
sidebarObjects: SidebarObjectKind[];
|
||||||
@@ -15,6 +15,7 @@ const ROUTINE_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "PROCEDURE", "FUN
|
|||||||
const POSTGRES_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "MATERIALIZED_VIEW", "PROCEDURE", "FUNCTION", "SEQUENCE"];
|
const POSTGRES_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "MATERIALIZED_VIEW", "PROCEDURE", "FUNCTION", "SEQUENCE"];
|
||||||
const POSTGRES_LIKE_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "MATERIALIZED_VIEW", "PROCEDURE", "FUNCTION"];
|
const POSTGRES_LIKE_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "MATERIALIZED_VIEW", "PROCEDURE", "FUNCTION"];
|
||||||
const ORACLE_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "MATERIALIZED_VIEW", "PROCEDURE", "FUNCTION", "PACKAGE", "PACKAGE_BODY"];
|
const ORACLE_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "MATERIALIZED_VIEW", "PROCEDURE", "FUNCTION", "PACKAGE", "PACKAGE_BODY"];
|
||||||
|
const XUGU_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "PROCEDURE", "FUNCTION", "TRIGGER", "SEQUENCE", "PACKAGE", "PACKAGE_BODY", "TYPE", "TYPE_BODY"];
|
||||||
|
|
||||||
const DATABASE_TYPE_OBJECTS = new Map<DatabaseType, SidebarObjectKind[]>([
|
const DATABASE_TYPE_OBJECTS = new Map<DatabaseType, SidebarObjectKind[]>([
|
||||||
// postgres
|
// postgres
|
||||||
@@ -31,6 +32,7 @@ const DATABASE_TYPE_OBJECTS = new Map<DatabaseType, SidebarObjectKind[]>([
|
|||||||
["oracle", ORACLE_OBJECTS],
|
["oracle", ORACLE_OBJECTS],
|
||||||
["dameng", ORACLE_OBJECTS],
|
["dameng", ORACLE_OBJECTS],
|
||||||
["oceanbase-oracle", ORACLE_OBJECTS],
|
["oceanbase-oracle", ORACLE_OBJECTS],
|
||||||
|
["xugu", XUGU_OBJECTS],
|
||||||
// table and view
|
// table and view
|
||||||
["sqlite", TABLE_VIEW_OBJECTS],
|
["sqlite", TABLE_VIEW_OBJECTS],
|
||||||
["rqlite", TABLE_VIEW_OBJECTS],
|
["rqlite", TABLE_VIEW_OBJECTS],
|
||||||
@@ -74,7 +76,10 @@ export function normalizeSidebarObjectKind(type: string): SidebarObjectKind {
|
|||||||
const value = type.toUpperCase();
|
const value = type.toUpperCase();
|
||||||
const normalized = value.replace(/[\s-]+/g, "_");
|
const normalized = value.replace(/[\s-]+/g, "_");
|
||||||
if (normalized.includes("PACKAGE_BODY")) return "PACKAGE_BODY";
|
if (normalized.includes("PACKAGE_BODY")) return "PACKAGE_BODY";
|
||||||
|
if (normalized.includes("TYPE_BODY")) return "TYPE_BODY";
|
||||||
if (normalized.includes("PACKAGE")) return "PACKAGE";
|
if (normalized.includes("PACKAGE")) return "PACKAGE";
|
||||||
|
if (normalized.includes("TRIGGER")) return "TRIGGER";
|
||||||
|
if (normalized.includes("TYPE")) return "TYPE";
|
||||||
if (normalized.includes("MATERIALIZED_VIEW")) return "MATERIALIZED_VIEW";
|
if (normalized.includes("MATERIALIZED_VIEW")) return "MATERIALIZED_VIEW";
|
||||||
if (value.includes("VIEW")) return "VIEW";
|
if (value.includes("VIEW")) return "VIEW";
|
||||||
if (value.includes("SEQ")) return "SEQUENCE";
|
if (value.includes("SEQ")) return "SEQUENCE";
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ const leafTypes: Set<TreeNodeType> = new Set([
|
|||||||
"function",
|
"function",
|
||||||
"package",
|
"package",
|
||||||
"package-body",
|
"package-body",
|
||||||
|
"type",
|
||||||
|
"type-body",
|
||||||
"object-browser",
|
"object-browser",
|
||||||
"redis-db",
|
"redis-db",
|
||||||
"mq-tenant",
|
"mq-tenant",
|
||||||
|
|||||||
@@ -10,19 +10,22 @@ const dataNodeTypes = new Set<TreeNodeType>(["table", "view", "materialized_view
|
|||||||
const documentBrowserNodeTypes = new Set<TreeNodeType>(["mongo-collection", "mongo-bucket"]);
|
const documentBrowserNodeTypes = new Set<TreeNodeType>(["mongo-collection", "mongo-bucket"]);
|
||||||
const toggleLeafNodeTypes = new Set<TreeNodeType>(["redis-db", "mq-tenant", "etcd-root", "zookeeper-root", "mongo-gridfs", "mongo-collection", "mongo-bucket", "vector-collection", "elasticsearch-index", "user-admin"]);
|
const toggleLeafNodeTypes = new Set<TreeNodeType>(["redis-db", "mq-tenant", "etcd-root", "zookeeper-root", "mongo-gridfs", "mongo-collection", "mongo-bucket", "vector-collection", "elasticsearch-index", "user-admin"]);
|
||||||
const objectBrowserNodeTypes = new Set<TreeNodeType>(["database", "schema", "object-browser"]);
|
const objectBrowserNodeTypes = new Set<TreeNodeType>(["database", "schema", "object-browser"]);
|
||||||
const sourceNodeTypes = new Set<TreeNodeType>(["materialized_view", "procedure", "function", "sequence", "package", "package-body"]);
|
const sourceNodeTypes = new Set<TreeNodeType>(["materialized_view", "procedure", "function", "trigger", "sequence", "package", "package-body", "type", "type-body"]);
|
||||||
const savedSqlNodeTypes = new Set<TreeNodeType>(["saved-sql-file"]);
|
const savedSqlNodeTypes = new Set<TreeNodeType>(["saved-sql-file"]);
|
||||||
const tableChildGroupNodeTypes = new Set<TreeNodeType>(["group-columns", "group-indexes", "group-fkeys", "group-triggers", "group-partitions"]);
|
const tableChildGroupNodeTypes = new Set<TreeNodeType>(["group-columns", "group-indexes", "group-fkeys", "group-triggers", "group-partitions"]);
|
||||||
const databaseChildGroupNodeTypes = new Set<TreeNodeType>(["group-tables", "group-views", "group-materialized-views", "group-procedures", "group-functions", "group-sequences", "group-packages"]);
|
const databaseChildGroupNodeTypes = new Set<TreeNodeType>(["group-tables", "group-views", "group-materialized-views", "group-procedures", "group-functions", "group-triggers", "group-sequences", "group-packages", "group-types"]);
|
||||||
|
|
||||||
export function objectSourceKindForTreeNode(type: TreeNodeType): ObjectSourceKind | null {
|
export function objectSourceKindForTreeNode(type: TreeNodeType): ObjectSourceKind | null {
|
||||||
if (type === "view") return "VIEW";
|
if (type === "view") return "VIEW";
|
||||||
if (type === "materialized_view") return "MATERIALIZED_VIEW";
|
if (type === "materialized_view") return "MATERIALIZED_VIEW";
|
||||||
if (type === "procedure") return "PROCEDURE";
|
if (type === "procedure") return "PROCEDURE";
|
||||||
if (type === "function") return "FUNCTION";
|
if (type === "function") return "FUNCTION";
|
||||||
|
if (type === "trigger") return "TRIGGER";
|
||||||
if (type === "sequence") return "SEQUENCE";
|
if (type === "sequence") return "SEQUENCE";
|
||||||
if (type === "package") return "PACKAGE";
|
if (type === "package") return "PACKAGE";
|
||||||
if (type === "package-body") return "PACKAGE_BODY";
|
if (type === "package-body") return "PACKAGE_BODY";
|
||||||
|
if (type === "type") return "TYPE";
|
||||||
|
if (type === "type-body") return "TYPE_BODY";
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export type ObjectBrowserRowAction = "table-info" | "open-table" | "open-source"
|
|||||||
/**
|
/**
|
||||||
* Determine the action for a single click on an object browser row.
|
* Determine the action for a single click on an object browser row.
|
||||||
* - TABLE → table-info (show table properties panel)
|
* - TABLE → table-info (show table properties panel)
|
||||||
* - VIEW/MATERIALIZED_VIEW/PROCEDURE/FUNCTION/SEQUENCE/PACKAGE/PACKAGE_BODY → open-source
|
* - VIEW/MATERIALIZED_VIEW/PROCEDURE/FUNCTION/TRIGGER/SEQUENCE/PACKAGE/PACKAGE_BODY/TYPE/TYPE_BODY → open-source
|
||||||
* - otherwise → none
|
* - otherwise → none
|
||||||
*/
|
*/
|
||||||
export function singleClickRowAction(row: ObjectBrowserRow | null | undefined): ObjectBrowserRowAction {
|
export function singleClickRowAction(row: ObjectBrowserRow | null | undefined): ObjectBrowserRowAction {
|
||||||
@@ -18,7 +18,7 @@ export function singleClickRowAction(row: ObjectBrowserRow | null | undefined):
|
|||||||
/**
|
/**
|
||||||
* Determine the action for a double click on an object browser row.
|
* Determine the action for a double click on an object browser row.
|
||||||
* - TABLE → open-table (open table data tab)
|
* - TABLE → open-table (open table data tab)
|
||||||
* - VIEW/MATERIALIZED_VIEW/PROCEDURE/FUNCTION/SEQUENCE/PACKAGE/PACKAGE_BODY → open-source
|
* - VIEW/MATERIALIZED_VIEW/PROCEDURE/FUNCTION/TRIGGER/SEQUENCE/PACKAGE/PACKAGE_BODY/TYPE/TYPE_BODY → open-source
|
||||||
* - otherwise → none
|
* - otherwise → none
|
||||||
*/
|
*/
|
||||||
export function doubleClickRowAction(row: ObjectBrowserRow | null | undefined): ObjectBrowserRowAction {
|
export function doubleClickRowAction(row: ObjectBrowserRow | null | undefined): ObjectBrowserRowAction {
|
||||||
@@ -62,6 +62,14 @@ export function shouldDeferSingleClick(row: ObjectBrowserRow | null | undefined,
|
|||||||
return single !== double && action === single;
|
return single !== double && action === single;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Objects with source metadata but no supported object-browser mutation API.
|
||||||
|
* Their menu intentionally exposes only source viewing and copying.
|
||||||
|
*/
|
||||||
|
export function isSourceOnlyObjectBrowserRow(row: ObjectBrowserRow): boolean {
|
||||||
|
return row.type === "TRIGGER" || row.type === "SEQUENCE" || row.type === "PACKAGE" || row.type === "PACKAGE_BODY" || row.type === "TYPE" || row.type === "TYPE_BODY";
|
||||||
|
}
|
||||||
|
|
||||||
function canOpenSource(row: ObjectBrowserRow): boolean {
|
function canOpenSource(row: ObjectBrowserRow): boolean {
|
||||||
return row.type === "VIEW" || row.type === "MATERIALIZED_VIEW" || row.type === "PROCEDURE" || row.type === "FUNCTION" || row.type === "SEQUENCE" || row.type === "PACKAGE" || row.type === "PACKAGE_BODY";
|
return row.type === "VIEW" || row.type === "MATERIALIZED_VIEW" || row.type === "PROCEDURE" || row.type === "FUNCTION" || row.type === "TRIGGER" || row.type === "SEQUENCE" || row.type === "PACKAGE" || row.type === "PACKAGE_BODY" || row.type === "TYPE" || row.type === "TYPE_BODY";
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,8 @@ export type ObjectBrowserRow = {
|
|||||||
name: string;
|
name: string;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
schema?: string;
|
schema?: string;
|
||||||
type: "TABLE" | "VIEW" | "MATERIALIZED_VIEW" | "PROCEDURE" | "FUNCTION" | "SEQUENCE" | "PACKAGE" | "PACKAGE_BODY";
|
type: "TABLE" | "VIEW" | "MATERIALIZED_VIEW" | "PROCEDURE" | "FUNCTION" | "TRIGGER" | "SEQUENCE" | "PACKAGE" | "PACKAGE_BODY" | "TYPE" | "TYPE_BODY";
|
||||||
|
valid?: boolean | null;
|
||||||
signature?: string | null;
|
signature?: string | null;
|
||||||
comment?: string | null;
|
comment?: string | null;
|
||||||
created_at?: string | null;
|
created_at?: string | null;
|
||||||
@@ -27,7 +28,10 @@ export function normalizeObjectBrowserType(type: string): ObjectBrowserRow["type
|
|||||||
const value = type.toUpperCase();
|
const value = type.toUpperCase();
|
||||||
const normalized = value.replace(/[\s-]+/g, "_");
|
const normalized = value.replace(/[\s-]+/g, "_");
|
||||||
if (normalized.includes("PACKAGE_BODY")) return "PACKAGE_BODY";
|
if (normalized.includes("PACKAGE_BODY")) return "PACKAGE_BODY";
|
||||||
|
if (normalized.includes("TYPE_BODY")) return "TYPE_BODY";
|
||||||
if (normalized.includes("PACKAGE")) return "PACKAGE";
|
if (normalized.includes("PACKAGE")) return "PACKAGE";
|
||||||
|
if (normalized.includes("TRIGGER")) return "TRIGGER";
|
||||||
|
if (normalized.includes("TYPE")) return "TYPE";
|
||||||
if (normalized.includes("MATERIALIZED_VIEW")) return "MATERIALIZED_VIEW";
|
if (normalized.includes("MATERIALIZED_VIEW")) return "MATERIALIZED_VIEW";
|
||||||
if (value.includes("VIEW")) return "VIEW";
|
if (value.includes("VIEW")) return "VIEW";
|
||||||
if (value.includes("SEQ")) return "SEQUENCE";
|
if (value.includes("SEQ")) return "SEQUENCE";
|
||||||
@@ -57,6 +61,7 @@ export function buildObjectBrowserRows(options: { objects: ObjectInfo[]; databas
|
|||||||
displayName,
|
displayName,
|
||||||
schema,
|
schema,
|
||||||
type,
|
type,
|
||||||
|
valid: object.valid,
|
||||||
signature,
|
signature,
|
||||||
comment: object.comment,
|
comment: object.comment,
|
||||||
created_at: object.created_at,
|
created_at: object.created_at,
|
||||||
|
|||||||
@@ -504,7 +504,7 @@ export function buildSimpleObjectTreeNodes({ nodeId, connectionId, database, sch
|
|||||||
|
|
||||||
for (const obj of objects) {
|
for (const obj of objects) {
|
||||||
const objectType = normalizeObjectType(obj.object_type);
|
const objectType = normalizeObjectType(obj.object_type);
|
||||||
if (!["TABLE", "VIEW", "MATERIALIZED_VIEW", "PROCEDURE", "FUNCTION", "SEQUENCE", "PACKAGE", "PACKAGE_BODY"].includes(objectType)) {
|
if (!["TABLE", "VIEW", "MATERIALIZED_VIEW", "PROCEDURE", "FUNCTION", "TRIGGER", "SEQUENCE", "PACKAGE", "PACKAGE_BODY", "TYPE", "TYPE_BODY"].includes(objectType)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -540,6 +540,7 @@ export function buildSimpleObjectTreeNodes({ nodeId, connectionId, database, sch
|
|||||||
objectName: name,
|
objectName: name,
|
||||||
signature: signature || undefined,
|
signature: signature || undefined,
|
||||||
comment: obj.comment,
|
comment: obj.comment,
|
||||||
|
valid: obj.valid ?? undefined,
|
||||||
connectionId,
|
connectionId,
|
||||||
database,
|
database,
|
||||||
schema: childSchema,
|
schema: childSchema,
|
||||||
@@ -557,9 +558,12 @@ function simpleObjectNodeType(objectType: DatabaseObjectTreeKind): TreeNodeType
|
|||||||
if (objectType === "MATERIALIZED_VIEW") return "materialized_view";
|
if (objectType === "MATERIALIZED_VIEW") return "materialized_view";
|
||||||
if (objectType === "PROCEDURE") return "procedure";
|
if (objectType === "PROCEDURE") return "procedure";
|
||||||
if (objectType === "FUNCTION") return "function";
|
if (objectType === "FUNCTION") return "function";
|
||||||
|
if (objectType === "TRIGGER") return "trigger";
|
||||||
if (objectType === "SEQUENCE") return "sequence";
|
if (objectType === "SEQUENCE") return "sequence";
|
||||||
if (objectType === "PACKAGE_BODY") return "package-body";
|
if (objectType === "PACKAGE_BODY") return "package-body";
|
||||||
if (objectType === "PACKAGE") return "package";
|
if (objectType === "PACKAGE") return "package";
|
||||||
|
if (objectType === "TYPE_BODY") return "type-body";
|
||||||
|
if (objectType === "TYPE") return "type";
|
||||||
return "table";
|
return "table";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -597,6 +601,13 @@ const groupDefs: Array<{
|
|||||||
nodeType: "group-functions",
|
nodeType: "group-functions",
|
||||||
childType: "function",
|
childType: "function",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "__triggers",
|
||||||
|
label: "tree.triggers",
|
||||||
|
objectTypes: ["TRIGGER"],
|
||||||
|
nodeType: "group-triggers",
|
||||||
|
childType: "trigger",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: "__sequences",
|
key: "__sequences",
|
||||||
label: "tree.sequences",
|
label: "tree.sequences",
|
||||||
@@ -611,9 +622,16 @@ const groupDefs: Array<{
|
|||||||
nodeType: "group-packages",
|
nodeType: "group-packages",
|
||||||
childType: (objectType) => (objectType === "PACKAGE_BODY" ? "package-body" : "package"),
|
childType: (objectType) => (objectType === "PACKAGE_BODY" ? "package-body" : "package"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "__types",
|
||||||
|
label: "tree.types",
|
||||||
|
objectTypes: ["TYPE", "TYPE_BODY"],
|
||||||
|
nodeType: "group-types",
|
||||||
|
childType: (objectType) => (objectType === "TYPE_BODY" ? "type-body" : "type"),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const objectGroupNodeTypes = new Set<TreeNodeType>(["group-tables", "group-views", "group-materialized-views", "group-procedures", "group-functions", "group-sequences", "group-packages"]);
|
const objectGroupNodeTypes = new Set<TreeNodeType>(["group-tables", "group-views", "group-materialized-views", "group-procedures", "group-functions", "group-triggers", "group-sequences", "group-packages", "group-types"]);
|
||||||
|
|
||||||
export function buildObjectGroupPlaceholderNodes({ nodeId, connectionId, database, schema, objectTypes }: { nodeId: string; connectionId: string; database: string; schema?: string; objectTypes: DatabaseObjectTreeKind[] }): TreeNode[] {
|
export function buildObjectGroupPlaceholderNodes({ nodeId, connectionId, database, schema, objectTypes }: { nodeId: string; connectionId: string; database: string; schema?: string; objectTypes: DatabaseObjectTreeKind[] }): TreeNode[] {
|
||||||
const supported = new Set(objectTypes);
|
const supported = new Set(objectTypes);
|
||||||
@@ -676,12 +694,13 @@ export function buildGroupedObjectTreeNodes({ nodeId, connectionId, database, sc
|
|||||||
const childSchema = obj.schema ? normalizeDatabaseObjectName(obj.schema) : schema;
|
const childSchema = obj.schema ? normalizeDatabaseObjectName(obj.schema) : schema;
|
||||||
const objectType = normalizeObjectType(obj.object_type);
|
const objectType = normalizeObjectType(obj.object_type);
|
||||||
const childType = typeof def.childType === "function" ? def.childType(objectType) : def.childType;
|
const childType = typeof def.childType === "function" ? def.childType(objectType) : def.childType;
|
||||||
const objectTypeSuffix = objectType === "PACKAGE" || objectType === "PACKAGE_BODY" ? `:${objectType}` : "";
|
const objectTypeSuffix = objectType === "PACKAGE" || objectType === "PACKAGE_BODY" || objectType === "TYPE" || objectType === "TYPE_BODY" ? `:${objectType}` : "";
|
||||||
return {
|
return {
|
||||||
id: `${nodeId}:${def.key}:${childSchema ? `${childSchema}:` : ""}${obj.name}${objectTypeSuffix}`,
|
id: `${nodeId}:${def.key}:${childSchema ? `${childSchema}:` : ""}${obj.name}${objectTypeSuffix}`,
|
||||||
label: obj.name,
|
label: obj.name,
|
||||||
type: childType,
|
type: childType,
|
||||||
comment: obj.comment,
|
comment: obj.comment,
|
||||||
|
valid: obj.valid ?? undefined,
|
||||||
connectionId,
|
connectionId,
|
||||||
database,
|
database,
|
||||||
schema: childSchema,
|
schema: childSchema,
|
||||||
|
|||||||
@@ -377,12 +377,13 @@ export interface TableInfo {
|
|||||||
parent_name?: string | null;
|
parent_name?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DatabaseObjectType = "TABLE" | "VIEW" | "MATERIALIZED_VIEW" | "PROCEDURE" | "FUNCTION" | "SEQUENCE" | "PACKAGE" | "PACKAGE_BODY";
|
export type DatabaseObjectType = "TABLE" | "VIEW" | "MATERIALIZED_VIEW" | "PROCEDURE" | "FUNCTION" | "TRIGGER" | "SEQUENCE" | "PACKAGE" | "PACKAGE_BODY" | "TYPE" | "TYPE_BODY";
|
||||||
|
|
||||||
export interface ObjectInfo {
|
export interface ObjectInfo {
|
||||||
name: string;
|
name: string;
|
||||||
object_type: DatabaseObjectType | string;
|
object_type: DatabaseObjectType | string;
|
||||||
schema?: string | null;
|
schema?: string | null;
|
||||||
|
valid?: boolean | null;
|
||||||
signature?: string | null;
|
signature?: string | null;
|
||||||
comment?: string | null;
|
comment?: string | null;
|
||||||
created_at?: string | null;
|
created_at?: string | null;
|
||||||
@@ -398,7 +399,7 @@ export interface ObjectStatistics {
|
|||||||
total_bytes?: number | null;
|
total_bytes?: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ObjectSourceKind = "VIEW" | "MATERIALIZED_VIEW" | "PROCEDURE" | "FUNCTION" | "SEQUENCE" | "PACKAGE" | "PACKAGE_BODY";
|
export type ObjectSourceKind = "VIEW" | "MATERIALIZED_VIEW" | "PROCEDURE" | "FUNCTION" | "TRIGGER" | "SEQUENCE" | "PACKAGE" | "PACKAGE_BODY" | "TYPE" | "TYPE_BODY";
|
||||||
|
|
||||||
export interface ObjectSource {
|
export interface ObjectSource {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -616,6 +617,8 @@ export type TreeNodeType =
|
|||||||
| "materialized_view"
|
| "materialized_view"
|
||||||
| "procedure"
|
| "procedure"
|
||||||
| "function"
|
| "function"
|
||||||
|
| "type"
|
||||||
|
| "type-body"
|
||||||
| "sequence"
|
| "sequence"
|
||||||
| "package"
|
| "package"
|
||||||
| "package-body"
|
| "package-body"
|
||||||
@@ -628,6 +631,7 @@ export type TreeNodeType =
|
|||||||
| "group-materialized-views"
|
| "group-materialized-views"
|
||||||
| "group-procedures"
|
| "group-procedures"
|
||||||
| "group-functions"
|
| "group-functions"
|
||||||
|
| "group-types"
|
||||||
| "group-sequences"
|
| "group-sequences"
|
||||||
| "group-packages"
|
| "group-packages"
|
||||||
| "group-partitions"
|
| "group-partitions"
|
||||||
@@ -697,6 +701,7 @@ export interface TreeNode {
|
|||||||
signature?: string;
|
signature?: string;
|
||||||
tableType?: string;
|
tableType?: string;
|
||||||
comment?: string | null;
|
comment?: string | null;
|
||||||
|
valid?: boolean | null;
|
||||||
objectCount?: number;
|
objectCount?: number;
|
||||||
loadedKeyCount?: number;
|
loadedKeyCount?: number;
|
||||||
totalKeyCount?: number;
|
totalKeyCount?: number;
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ pub async fn list_objects(pool: &MySqlPool, database: &str) -> Result<Vec<Object
|
|||||||
name: table.name,
|
name: table.name,
|
||||||
object_type: "TABLE".to_string(),
|
object_type: "TABLE".to_string(),
|
||||||
schema: Some(database.to_string()),
|
schema: Some(database.to_string()),
|
||||||
|
valid: None,
|
||||||
signature: None,
|
signature: None,
|
||||||
comment: table.comment,
|
comment: table.comment,
|
||||||
created_at: None,
|
created_at: None,
|
||||||
@@ -100,6 +101,7 @@ fn plugin_object(
|
|||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
object_type: "FUNCTION".to_string(),
|
object_type: "FUNCTION".to_string(),
|
||||||
schema: Some(database.to_string()),
|
schema: Some(database.to_string()),
|
||||||
|
valid: None,
|
||||||
signature: None,
|
signature: None,
|
||||||
comment: if comment_parts.is_empty() { None } else { Some(comment_parts.join(", ")) },
|
comment: if comment_parts.is_empty() { None } else { Some(comment_parts.join(", ")) },
|
||||||
created_at: None,
|
created_at: None,
|
||||||
|
|||||||
@@ -2148,6 +2148,7 @@ fn row_to_object(row: &mysql_async::Row, database: &str) -> ObjectInfo {
|
|||||||
name: get_str_by_name(row, "object_name"),
|
name: get_str_by_name(row, "object_name"),
|
||||||
object_type: get_str_by_name(row, "object_type"),
|
object_type: get_str_by_name(row, "object_type"),
|
||||||
schema: Some(database.to_string()),
|
schema: Some(database.to_string()),
|
||||||
|
valid: None,
|
||||||
signature: None,
|
signature: None,
|
||||||
comment: get_opt_str(row, "object_comment")
|
comment: get_opt_str(row, "object_comment")
|
||||||
.map(|s| fix_potential_double_encoding(&s))
|
.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,
|
name: table.name,
|
||||||
object_type: if table.table_type.eq_ignore_ascii_case("VIEW") { "VIEW" } else { "TABLE" }.to_string(),
|
object_type: if table.table_type.eq_ignore_ascii_case("VIEW") { "VIEW" } else { "TABLE" }.to_string(),
|
||||||
schema: Some(database.to_string()),
|
schema: Some(database.to_string()),
|
||||||
|
valid: None,
|
||||||
signature: None,
|
signature: None,
|
||||||
comment: table.comment,
|
comment: table.comment,
|
||||||
created_at: meta.and_then(|meta| meta.created_at.clone()),
|
created_at: meta.and_then(|meta| meta.created_at.clone()),
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ pub async fn list_objects(pool: &mysql_async::Pool, schema: &str) -> Result<Vec<
|
|||||||
name: get_str(row, 0),
|
name: get_str(row, 0),
|
||||||
object_type: get_str(row, 1),
|
object_type: get_str(row, 1),
|
||||||
schema: Some(schema.to_string()),
|
schema: Some(schema.to_string()),
|
||||||
|
valid: None,
|
||||||
signature: None,
|
signature: None,
|
||||||
comment: None,
|
comment: None,
|
||||||
created_at: None,
|
created_at: None,
|
||||||
|
|||||||
@@ -2125,6 +2125,7 @@ pub async fn list_objects(pool: &Pool, schema: &str) -> Result<Vec<ObjectInfo>,
|
|||||||
name: pg_row_try_string(row, 0),
|
name: pg_row_try_string(row, 0),
|
||||||
object_type: pg_row_try_string(row, 1),
|
object_type: pg_row_try_string(row, 1),
|
||||||
schema: Some(schema.to_string()),
|
schema: Some(schema.to_string()),
|
||||||
|
valid: None,
|
||||||
comment: row.try_get::<_, Option<String>>(2).ok().flatten().filter(|s| !s.is_empty()),
|
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()),
|
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()),
|
updated_at: row.try_get::<_, Option<String>>(4).ok().flatten().filter(|s| !s.is_empty()),
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ pub async fn list_objects(pool: &Pool, schema: &str) -> Result<Vec<ObjectInfo>,
|
|||||||
name: t.name.clone(),
|
name: t.name.clone(),
|
||||||
object_type: t.table_type.clone(),
|
object_type: t.table_type.clone(),
|
||||||
schema: None,
|
schema: None,
|
||||||
|
valid: None,
|
||||||
signature: None,
|
signature: None,
|
||||||
comment: t.comment.clone(),
|
comment: t.comment.clone(),
|
||||||
created_at: None,
|
created_at: None,
|
||||||
|
|||||||
@@ -1330,6 +1330,7 @@ pub async fn list_objects(client: &mut SqlServerClient, schema: &str) -> Result<
|
|||||||
name: row.get::<&str, _>(0).unwrap_or("").to_string(),
|
name: row.get::<&str, _>(0).unwrap_or("").to_string(),
|
||||||
object_type: row.get::<&str, _>(1).unwrap_or("TABLE").to_string(),
|
object_type: row.get::<&str, _>(1).unwrap_or("TABLE").to_string(),
|
||||||
schema: Some(schema.to_string()),
|
schema: Some(schema.to_string()),
|
||||||
|
valid: None,
|
||||||
signature: None,
|
signature: None,
|
||||||
comment: row.get::<&str, _>(4).filter(|s: &&str| !s.is_empty()).map(|s: &str| s.to_string()),
|
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()),
|
created_at: row.get::<chrono::NaiveDateTime, _>(2).map(|value| value.to_string()),
|
||||||
|
|||||||
@@ -320,9 +320,12 @@ fn object_type_keyword(object_type: &ObjectSourceKind) -> &'static str {
|
|||||||
ObjectSourceKind::MaterializedView => "MATERIALIZED_VIEW",
|
ObjectSourceKind::MaterializedView => "MATERIALIZED_VIEW",
|
||||||
ObjectSourceKind::Procedure => "PROCEDURE",
|
ObjectSourceKind::Procedure => "PROCEDURE",
|
||||||
ObjectSourceKind::Function => "FUNCTION",
|
ObjectSourceKind::Function => "FUNCTION",
|
||||||
|
ObjectSourceKind::Trigger => "TRIGGER",
|
||||||
ObjectSourceKind::Sequence => "SEQUENCE",
|
ObjectSourceKind::Sequence => "SEQUENCE",
|
||||||
ObjectSourceKind::Package => "PACKAGE",
|
ObjectSourceKind::Package => "PACKAGE",
|
||||||
ObjectSourceKind::PackageBody => "PACKAGE BODY",
|
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)
|
Some(ObjectSourceKind::Procedure)
|
||||||
} else if value.eq_ignore_ascii_case("FUNCTION") {
|
} else if value.eq_ignore_ascii_case("FUNCTION") {
|
||||||
Some(ObjectSourceKind::Function)
|
Some(ObjectSourceKind::Function)
|
||||||
|
} else if value.eq_ignore_ascii_case("TRIGGER") {
|
||||||
|
Some(ObjectSourceKind::Trigger)
|
||||||
} else if value.eq_ignore_ascii_case("SEQUENCE") {
|
} else if value.eq_ignore_ascii_case("SEQUENCE") {
|
||||||
Some(ObjectSourceKind::Sequence)
|
Some(ObjectSourceKind::Sequence)
|
||||||
} else if value.eq_ignore_ascii_case("PACKAGE") {
|
} else if value.eq_ignore_ascii_case("PACKAGE") {
|
||||||
Some(ObjectSourceKind::Package)
|
Some(ObjectSourceKind::Package)
|
||||||
} else if value.eq_ignore_ascii_case("PACKAGE BODY") || value.eq_ignore_ascii_case("PACKAGE_BODY") {
|
} else if value.eq_ignore_ascii_case("PACKAGE BODY") || value.eq_ignore_ascii_case("PACKAGE_BODY") {
|
||||||
Some(ObjectSourceKind::PackageBody)
|
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 {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -1251,6 +1260,14 @@ mod tests {
|
|||||||
assert_eq!(sql, "CREATE OR REPLACE PACKAGE BODY PAYROLL AS\nEND PAYROLL;");
|
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]
|
#[test]
|
||||||
fn postgres_procedure_rename_adds_drop_cleanup() {
|
fn postgres_procedure_rename_adds_drop_cleanup() {
|
||||||
let statements = build_executable_object_source_statements(input(
|
let statements = build_executable_object_source_statements(input(
|
||||||
|
|||||||
@@ -2219,6 +2219,7 @@ async fn external_driver_presto_like_objects(
|
|||||||
name: table.name,
|
name: table.name,
|
||||||
object_type: table.table_type,
|
object_type: table.table_type,
|
||||||
schema: Some(schema.to_string()),
|
schema: Some(schema.to_string()),
|
||||||
|
valid: None,
|
||||||
signature: None,
|
signature: None,
|
||||||
comment: table.comment,
|
comment: table.comment,
|
||||||
created_at: None,
|
created_at: None,
|
||||||
@@ -2601,6 +2602,7 @@ mod tests {
|
|||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
object_type: object_type.to_string(),
|
object_type: object_type.to_string(),
|
||||||
schema: Some("app".to_string()),
|
schema: Some("app".to_string()),
|
||||||
|
valid: None,
|
||||||
signature: None,
|
signature: None,
|
||||||
comment: None,
|
comment: None,
|
||||||
created_at: None,
|
created_at: None,
|
||||||
@@ -3318,6 +3320,7 @@ mod tests {
|
|||||||
name: "ORDERS".to_string(),
|
name: "ORDERS".to_string(),
|
||||||
object_type: "TABLE".to_string(),
|
object_type: "TABLE".to_string(),
|
||||||
schema: Some("DBX_TEST".to_string()),
|
schema: Some("DBX_TEST".to_string()),
|
||||||
|
valid: None,
|
||||||
signature: None,
|
signature: None,
|
||||||
comment: None,
|
comment: None,
|
||||||
created_at: None,
|
created_at: None,
|
||||||
@@ -3329,6 +3332,7 @@ mod tests {
|
|||||||
name: "ORDERS_VIEW".to_string(),
|
name: "ORDERS_VIEW".to_string(),
|
||||||
object_type: "VIEW".to_string(),
|
object_type: "VIEW".to_string(),
|
||||||
schema: Some("DBX_TEST".to_string()),
|
schema: Some("DBX_TEST".to_string()),
|
||||||
|
valid: None,
|
||||||
signature: None,
|
signature: None,
|
||||||
comment: None,
|
comment: None,
|
||||||
created_at: None,
|
created_at: None,
|
||||||
@@ -3340,6 +3344,7 @@ mod tests {
|
|||||||
name: "REFRESH_ORDERS".to_string(),
|
name: "REFRESH_ORDERS".to_string(),
|
||||||
object_type: "PROCEDURE".to_string(),
|
object_type: "PROCEDURE".to_string(),
|
||||||
schema: Some("DBX_TEST".to_string()),
|
schema: Some("DBX_TEST".to_string()),
|
||||||
|
valid: None,
|
||||||
signature: None,
|
signature: None,
|
||||||
comment: None,
|
comment: None,
|
||||||
created_at: None,
|
created_at: None,
|
||||||
@@ -3763,6 +3768,7 @@ async fn list_objects_once(
|
|||||||
name: table.name,
|
name: table.name,
|
||||||
object_type: table.table_type,
|
object_type: table.table_type,
|
||||||
schema: None,
|
schema: None,
|
||||||
|
valid: None,
|
||||||
signature: None,
|
signature: None,
|
||||||
comment: table.comment,
|
comment: table.comment,
|
||||||
created_at: None,
|
created_at: None,
|
||||||
@@ -3940,6 +3946,7 @@ async fn list_objects_once(
|
|||||||
name: table.name,
|
name: table.name,
|
||||||
object_type: table.table_type,
|
object_type: table.table_type,
|
||||||
schema: if schema.is_empty() { None } else { Some(schema.to_string()) },
|
schema: if schema.is_empty() { None } else { Some(schema.to_string()) },
|
||||||
|
valid: None,
|
||||||
signature: None,
|
signature: None,
|
||||||
comment: table.comment,
|
comment: table.comment,
|
||||||
created_at: None,
|
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::View | db::ObjectSourceKind::MaterializedView => "view",
|
||||||
db::ObjectSourceKind::Procedure
|
db::ObjectSourceKind::Procedure
|
||||||
| db::ObjectSourceKind::Function
|
| db::ObjectSourceKind::Function
|
||||||
|
| db::ObjectSourceKind::Trigger
|
||||||
| db::ObjectSourceKind::Sequence
|
| db::ObjectSourceKind::Sequence
|
||||||
| db::ObjectSourceKind::Package
|
| 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::View => "'V'",
|
||||||
db::ObjectSourceKind::Procedure => "'P'",
|
db::ObjectSourceKind::Procedure => "'P'",
|
||||||
db::ObjectSourceKind::Function => "'FN','IF','TF','FS','FT'",
|
db::ObjectSourceKind::Function => "'FN','IF','TF','FS','FT'",
|
||||||
|
db::ObjectSourceKind::Trigger => "'TR'",
|
||||||
db::ObjectSourceKind::Sequence
|
db::ObjectSourceKind::Sequence
|
||||||
| db::ObjectSourceKind::Package
|
| db::ObjectSourceKind::Package
|
||||||
| db::ObjectSourceKind::PackageBody
|
| db::ObjectSourceKind::PackageBody
|
||||||
|
| db::ObjectSourceKind::Type
|
||||||
|
| db::ObjectSourceKind::TypeBody
|
||||||
| db::ObjectSourceKind::MaterializedView => "''",
|
| db::ObjectSourceKind::MaterializedView => "''",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5055,7 +5068,11 @@ fn postgres_object_source_sql_inner(
|
|||||||
sql_string(name)
|
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::MaterializedView => "MATERIALIZED_VIEW",
|
||||||
db::ObjectSourceKind::Procedure => "PROCEDURE",
|
db::ObjectSourceKind::Procedure => "PROCEDURE",
|
||||||
db::ObjectSourceKind::Function => "FUNCTION",
|
db::ObjectSourceKind::Function => "FUNCTION",
|
||||||
|
db::ObjectSourceKind::Trigger => "TRIGGER",
|
||||||
db::ObjectSourceKind::Sequence => "SEQUENCE",
|
db::ObjectSourceKind::Sequence => "SEQUENCE",
|
||||||
db::ObjectSourceKind::Package => "PACKAGE",
|
db::ObjectSourceKind::Package => "PACKAGE",
|
||||||
db::ObjectSourceKind::PackageBody => "PACKAGE_BODY",
|
db::ObjectSourceKind::PackageBody => "PACKAGE_BODY",
|
||||||
|
db::ObjectSourceKind::Type => "TYPE",
|
||||||
|
db::ObjectSourceKind::TypeBody => "TYPE_BODY",
|
||||||
};
|
};
|
||||||
if schema.trim().is_empty() {
|
if schema.trim().is_empty() {
|
||||||
format!("SELECT DBMS_METADATA.GET_DDL({}, {}) FROM DUAL", sql_string(object_type), sql_string(name))
|
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::View => format!("SHOW CREATE VIEW {qualified_name}"),
|
||||||
db::ObjectSourceKind::Procedure => format!("SHOW CREATE PROCEDURE {qualified_name}"),
|
db::ObjectSourceKind::Procedure => format!("SHOW CREATE PROCEDURE {qualified_name}"),
|
||||||
db::ObjectSourceKind::Function => format!("SHOW CREATE FUNCTION {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::Package
|
||||||
| db::ObjectSourceKind::PackageBody
|
| db::ObjectSourceKind::PackageBody
|
||||||
|
| db::ObjectSourceKind::Type
|
||||||
|
| db::ObjectSourceKind::TypeBody
|
||||||
| db::ObjectSourceKind::MaterializedView => String::new(),
|
| db::ObjectSourceKind::MaterializedView => String::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5313,6 +5336,7 @@ async fn oracle_agent_list_objects(
|
|||||||
name,
|
name,
|
||||||
object_type,
|
object_type,
|
||||||
schema,
|
schema,
|
||||||
|
valid: None,
|
||||||
signature: None,
|
signature: None,
|
||||||
comment: None,
|
comment: None,
|
||||||
created_at: None,
|
created_at: None,
|
||||||
|
|||||||
@@ -235,6 +235,7 @@ mod tests {
|
|||||||
name: "orders".to_string(),
|
name: "orders".to_string(),
|
||||||
object_type: "BASE TABLE".to_string(),
|
object_type: "BASE TABLE".to_string(),
|
||||||
schema: None,
|
schema: None,
|
||||||
|
valid: None,
|
||||||
signature: None,
|
signature: None,
|
||||||
comment: None,
|
comment: None,
|
||||||
created_at: None,
|
created_at: None,
|
||||||
@@ -246,6 +247,7 @@ mod tests {
|
|||||||
name: "active_orders".to_string(),
|
name: "active_orders".to_string(),
|
||||||
object_type: "MATERIALIZED_VIEW".to_string(),
|
object_type: "MATERIALIZED_VIEW".to_string(),
|
||||||
schema: None,
|
schema: None,
|
||||||
|
valid: None,
|
||||||
signature: None,
|
signature: None,
|
||||||
comment: None,
|
comment: None,
|
||||||
created_at: None,
|
created_at: None,
|
||||||
@@ -257,6 +259,7 @@ mod tests {
|
|||||||
name: "payroll".to_string(),
|
name: "payroll".to_string(),
|
||||||
object_type: "PACKAGE BODY".to_string(),
|
object_type: "PACKAGE BODY".to_string(),
|
||||||
schema: None,
|
schema: None,
|
||||||
|
valid: None,
|
||||||
signature: None,
|
signature: None,
|
||||||
comment: None,
|
comment: None,
|
||||||
created_at: None,
|
created_at: None,
|
||||||
@@ -308,6 +311,7 @@ mod tests {
|
|||||||
name: "ORDERS".to_string(),
|
name: "ORDERS".to_string(),
|
||||||
object_type: "TABLE".to_string(),
|
object_type: "TABLE".to_string(),
|
||||||
schema: Some("HR".to_string()),
|
schema: Some("HR".to_string()),
|
||||||
|
valid: None,
|
||||||
signature: None,
|
signature: None,
|
||||||
comment: None,
|
comment: None,
|
||||||
created_at: None,
|
created_at: None,
|
||||||
@@ -319,6 +323,7 @@ mod tests {
|
|||||||
name: "bin$deleted".to_string(),
|
name: "bin$deleted".to_string(),
|
||||||
object_type: "TABLE".to_string(),
|
object_type: "TABLE".to_string(),
|
||||||
schema: Some("HR".to_string()),
|
schema: Some("HR".to_string()),
|
||||||
|
valid: None,
|
||||||
signature: None,
|
signature: None,
|
||||||
comment: None,
|
comment: None,
|
||||||
created_at: None,
|
created_at: None,
|
||||||
|
|||||||
@@ -4594,6 +4594,9 @@ where
|
|||||||
db::ObjectSourceKind::Sequence | db::ObjectSourceKind::Package | db::ObjectSourceKind::PackageBody => {
|
db::ObjectSourceKind::Sequence | db::ObjectSourceKind::Package | db::ObjectSourceKind::PackageBody => {
|
||||||
object.source.clone()
|
object.source.clone()
|
||||||
}
|
}
|
||||||
|
db::ObjectSourceKind::Trigger | db::ObjectSourceKind::Type | db::ObjectSourceKind::TypeBody => {
|
||||||
|
object.source.clone()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let statements = build_executable_object_source_statements(EditableObjectSourceSqlInput {
|
let statements = build_executable_object_source_statements(EditableObjectSourceSqlInput {
|
||||||
database_type: DatabaseType::Postgres,
|
database_type: DatabaseType::Postgres,
|
||||||
|
|||||||
@@ -62,6 +62,8 @@ pub struct ObjectInfo {
|
|||||||
pub object_type: String,
|
pub object_type: String,
|
||||||
pub schema: Option<String>,
|
pub schema: Option<String>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[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 signature: Option<String>,
|
||||||
pub comment: Option<String>,
|
pub comment: Option<String>,
|
||||||
pub created_at: Option<String>,
|
pub created_at: Option<String>,
|
||||||
@@ -93,9 +95,12 @@ pub enum ObjectSourceKind {
|
|||||||
MaterializedView,
|
MaterializedView,
|
||||||
Procedure,
|
Procedure,
|
||||||
Function,
|
Function,
|
||||||
|
Trigger,
|
||||||
Sequence,
|
Sequence,
|
||||||
Package,
|
Package,
|
||||||
PackageBody,
|
PackageBody,
|
||||||
|
Type,
|
||||||
|
TypeBody,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
@@ -310,3 +315,18 @@ pub struct OwnerInfo {
|
|||||||
pub object_type: String,
|
pub object_type: String,
|
||||||
pub owner: 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -194,6 +194,7 @@ pub async fn list_objects(
|
|||||||
name: table.name,
|
name: table.name,
|
||||||
object_type: table.table_type,
|
object_type: table.table_type,
|
||||||
schema: Some(database.to_string()),
|
schema: Some(database.to_string()),
|
||||||
|
valid: None,
|
||||||
signature: None,
|
signature: None,
|
||||||
comment: table.comment,
|
comment: table.comment,
|
||||||
created_at: None,
|
created_at: None,
|
||||||
|
|||||||
@@ -211,6 +211,7 @@ pub async fn list_objects(
|
|||||||
name: table.name,
|
name: table.name,
|
||||||
object_type: table.table_type,
|
object_type: table.table_type,
|
||||||
schema: Some(database.clone()),
|
schema: Some(database.clone()),
|
||||||
|
valid: None,
|
||||||
signature: None,
|
signature: None,
|
||||||
comment: table.comment,
|
comment: table.comment,
|
||||||
created_at: None,
|
created_at: None,
|
||||||
|
|||||||
Reference in new issue
Block a user