fix(oceanbase): show oracle mode schemas in sidebar
This commit is contained in:
parent
2dc71548c3
commit
dff94d60f5
|
|
@ -37,7 +37,7 @@ import { SQLITE_DATABASE_FILE_EXTENSIONS } from "@/lib/databaseFileDetection";
|
|||
import { connectionAttemptOriginalErrorMessage, connectionAttemptTimeoutMessage, connectionAttemptTimeoutMs } from "@/lib/connectionAttemptTimeout";
|
||||
import { ArrowLeft, ArrowDown, ArrowUp, CheckSquare, ChevronRight, CircleHelp, Copy, ExternalLink, FilePlus2, FolderOpen, GripVertical, Grid3X3, KeyRound, Link2, List, ListFilter, Loader2, Pipette, Plus, Search, ShieldCheck, Square, Trash2 } from "@lucide/vue";
|
||||
import { buildDraftVisibleDatabasesConnectionId, connectionCanChooseVisibleDatabases, initialVisibleDatabaseSelection, visibleDatabaseSelectionIsStale } from "@/lib/connectionVisibleDatabases";
|
||||
import { canSaveVisibleDatabaseSelection, filterDatabaseNamesForConnection, isSystemDatabaseName, normalizeVisibleDatabaseSelection, buildDraftVisibleSchemasConnectionId, normalizeVisibleSchemaSelection } from "@/lib/visibleDatabases";
|
||||
import { canSaveVisibleDatabaseSelection, connectionUsesVisibleSchemaFilter, filterDatabaseNamesForConnection, isSystemDatabaseName, normalizeVisibleDatabaseSelection, buildDraftVisibleSchemasConnectionId, normalizeVisibleSchemaSelection } from "@/lib/visibleDatabases";
|
||||
import { isSchemaAware } from "@/lib/databaseFeatureSupport";
|
||||
import VisibleSchemasDialog from "@/components/sidebar/VisibleSchemasDialog.vue";
|
||||
import { oceanbaseModeConnectionPatch, oceanbaseSubModeFromConfig } from "@/lib/oceanbaseConnectionMode";
|
||||
|
|
@ -1323,7 +1323,7 @@ const canUseTransportLayers = computed(() => form.value.db_type !== "sqlite" &&
|
|||
const shouldShowAgentDriverInstallHint = computed(() => showAgentDriverInstallHint(form.value.db_type, agentDrivers.value, form.value.driver_profile));
|
||||
const h2DriverMissing = computed(() => form.value.db_type === "h2" && isH2FileMode.value && agentDrivers.value.find((d) => d.db_type === "h2")?.installed !== true);
|
||||
const canChooseVisibleDatabases = computed(() => connectionCanChooseVisibleDatabases(form.value));
|
||||
const visibleFilterUsesSchemas = computed(() => form.value.db_type === "oracle" || form.value.db_type === "dameng");
|
||||
const visibleFilterUsesSchemas = computed(() => connectionUsesVisibleSchemaFilter(form.value));
|
||||
const hasVisibleDatabaseFilter = computed(() => Array.isArray(form.value.visible_databases));
|
||||
const visibleDatabaseSummary = computed(() => {
|
||||
const configured = form.value.visible_databases;
|
||||
|
|
@ -1694,7 +1694,7 @@ function connectionConfigForSubmit(id: string): ConnectionConfig {
|
|||
delete legacy.proxy_port;
|
||||
delete legacy.proxy_username;
|
||||
delete legacy.proxy_password;
|
||||
if (config.db_type === "oracle" || config.db_type === "dameng") {
|
||||
if (connectionUsesVisibleSchemaFilter(config)) {
|
||||
config.visible_databases = undefined;
|
||||
} else {
|
||||
config.visible_databases = Array.isArray(config.visible_databases) && config.visible_databases.length > 0 ? config.visible_databases : undefined;
|
||||
|
|
@ -1983,7 +1983,7 @@ async function openVisibleDatabasesPicker() {
|
|||
}
|
||||
|
||||
async function loadVisibleDatabaseNames(connectionId: string, config: ConnectionConfig): Promise<string[]> {
|
||||
if (config.db_type === "oracle" || config.db_type === "dameng") {
|
||||
if (connectionUsesVisibleSchemaFilter(config)) {
|
||||
return api.listSchemas(connectionId, config.database || "");
|
||||
}
|
||||
if (config.db_type === "redis") {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { Button } from "@/components/ui/button";
|
|||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import { canSaveVisibleDatabaseSelection, filterDatabaseNamesForConnection, isSystemDatabaseName, normalizeVisibleDatabaseSelection } from "@/lib/visibleDatabases";
|
||||
import { canSaveVisibleDatabaseSelection, connectionUsesVisibleSchemaFilter, filterDatabaseNamesForConnection, isSystemDatabaseName, normalizeVisibleDatabaseSelection } from "@/lib/visibleDatabases";
|
||||
import * as api from "@/lib/api";
|
||||
|
||||
const props = defineProps<{
|
||||
|
|
@ -32,7 +32,7 @@ const isLoading = ref(false);
|
|||
const errorMessage = ref("");
|
||||
|
||||
const connection = computed(() => connectionStore.getConfig(props.connectionId));
|
||||
const filterMode = computed<FilterMode>(() => (connection.value?.db_type === "oracle" || connection.value?.db_type === "dameng" ? "schema" : "database"));
|
||||
const filterMode = computed<FilterMode>(() => (connectionUsesVisibleSchemaFilter(connection.value) ? "schema" : "database"));
|
||||
const databaseKey = computed(() => connection.value?.database || "");
|
||||
const isSchemaFilterMode = computed(() => filterMode.value === "schema");
|
||||
const titleKey = computed(() => (isSchemaFilterMode.value ? "visibleSchemas.title" : "visibleDatabases.title"));
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { ref } from "vue";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import { isSchemaAware as isSchemaAwareType, isSingleDatabase, usesTreeSchemaMode } from "@/lib/databaseCapabilities";
|
||||
import { filterDatabaseNamesForConnection } from "@/lib/visibleDatabases";
|
||||
import { filterSchemaNamesForConnection } from "@/lib/visibleDatabases";
|
||||
import type { ConnectionConfig } from "@/types/database";
|
||||
import * as api from "@/lib/api";
|
||||
|
||||
|
|
@ -9,8 +9,8 @@ export function hasSchemaOptionsCacheEntry(options: Record<string, string[]>, ke
|
|||
return Object.prototype.hasOwnProperty.call(options, key);
|
||||
}
|
||||
|
||||
export function schemaOptionsForConnection(schemaNames: string[], connection: Pick<ConnectionConfig, "db_type" | "driver_profile" | "visible_databases"> | undefined): string[] {
|
||||
return filterDatabaseNamesForConnection(schemaNames, connection);
|
||||
export function schemaOptionsForConnection(schemaNames: string[], connection: Pick<ConnectionConfig, "db_type" | "driver_profile" | "visible_databases" | "visible_schemas"> | undefined, database = ""): string[] {
|
||||
return filterSchemaNamesForConnection(schemaNames, connection, database);
|
||||
}
|
||||
|
||||
export function useSchemaOptions() {
|
||||
|
|
@ -39,7 +39,7 @@ export function useSchemaOptions() {
|
|||
loadingSchemaOptions.value[key] = true;
|
||||
try {
|
||||
await connectionStore.ensureConnected(connectionId);
|
||||
schemaOptions.value[key] = schemaOptionsForConnection(await api.listSchemas(connectionId, database), connection);
|
||||
schemaOptions.value[key] = schemaOptionsForConnection(await api.listSchemas(connectionId, database), connection, database);
|
||||
} catch (e) {
|
||||
schemaOptions.value[key] = [];
|
||||
throw e;
|
||||
|
|
|
|||
|
|
@ -99,13 +99,20 @@ export function filterDatabaseNamesForConnection(databaseNames: string[], connec
|
|||
return databaseNames.filter((name) => !isSystemDatabaseName(connection?.db_type, name));
|
||||
}
|
||||
|
||||
export function connectionUsesVisibleSchemaFilter(connection: Pick<ConnectionConfig, "db_type"> | undefined): boolean {
|
||||
return connection?.db_type === "oracle" || connection?.db_type === "dameng" || connection?.db_type === "oceanbase-oracle";
|
||||
}
|
||||
|
||||
export function visibleSchemaFilterIsEnabled(visibleSchemas: Record<string, string[]> | undefined, database: string): boolean {
|
||||
return Array.isArray(visibleSchemas?.[database]);
|
||||
}
|
||||
|
||||
export function filterSchemaNamesForConnection(schemaNames: string[], connection: Pick<ConnectionConfig, "db_type" | "visible_schemas"> | undefined, database: string): string[] {
|
||||
export function filterSchemaNamesForConnection(schemaNames: string[], connection: Pick<ConnectionConfig, "db_type" | "visible_schemas" | "visible_databases"> | undefined, database: string): string[] {
|
||||
const visibleSchemas = connection?.visible_schemas;
|
||||
if (!visibleSchemaFilterIsEnabled(visibleSchemas, database)) {
|
||||
if (connectionUsesVisibleSchemaFilter(connection) && visibleDatabaseFilterIsEnabled(connection?.visible_databases)) {
|
||||
return filterVisibleDatabaseNames(schemaNames, connection?.visible_databases);
|
||||
}
|
||||
return schemaNames.filter((name) => !isSystemDatabaseName(connection?.db_type, name));
|
||||
}
|
||||
const visible = new Set(visibleSchemas![database]);
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import { buildSqlServerDatabaseTreeNodes } from "@/lib/sqlServerTree";
|
|||
import { findDatabaseTreeNode } from "@/lib/treeRefreshTarget";
|
||||
import { shouldMarkDisconnected } from "@/lib/connectionHealth";
|
||||
import { connectionAttemptOriginalErrorMessage, connectionAttemptTimeoutMessage, connectionAttemptTimeoutMs } from "@/lib/connectionAttemptTimeout";
|
||||
import { filterDatabaseNamesForConnection, filterSchemaNamesForConnection, filterVisibleDatabaseNames, normalizeVisibleDatabaseSelection } from "@/lib/visibleDatabases";
|
||||
import { connectionUsesVisibleSchemaFilter, filterDatabaseNamesForConnection, filterSchemaNamesForConnection, filterVisibleDatabaseNames, normalizeVisibleDatabaseSelection } from "@/lib/visibleDatabases";
|
||||
import {
|
||||
buildObjectGroupPlaceholderNodes,
|
||||
buildGroupedObjectTreeNodes,
|
||||
|
|
@ -1235,8 +1235,9 @@ export const useConnectionStore = defineStore("connection", () => {
|
|||
const children = withSavedSqlRoot(connectionId, buildDuckDbConnectionTreeNodes(connectionId, databases, schemas), node);
|
||||
setChildren(node, children);
|
||||
await savePersistedTreeChildren(cacheKey, children);
|
||||
} else if (config?.db_type === "dameng" || config?.db_type === "oracle") {
|
||||
const effectiveDb = config.database || "";
|
||||
} else if (config && connectionUsesVisibleSchemaFilter(config)) {
|
||||
const schemaFilterConfig = config;
|
||||
const effectiveDb = schemaFilterConfig.database || "";
|
||||
const cacheKey = schemaCacheKey(connectionId, effectiveDb, "schemas");
|
||||
if (!options?.force) {
|
||||
const cached = await loadPersistedTreeChildren(node, cacheKey);
|
||||
|
|
@ -1246,7 +1247,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
|||
}
|
||||
}
|
||||
const schemas = await withMetadataLoadTimeout(connectionId, api.listSchemas(connectionId, effectiveDb, true), "schemas");
|
||||
const visibleSchemas = filterSchemaNamesForConnection(schemas, config, effectiveDb || "");
|
||||
const visibleSchemas = filterSchemaNamesForConnection(schemas, schemaFilterConfig, effectiveDb || "");
|
||||
const schemaNodes: TreeNode[] = sortSidebarNames(visibleSchemas).map((s) => ({
|
||||
id: `${connectionId}:${s}:${s}`,
|
||||
label: s,
|
||||
|
|
|
|||
|
|
@ -29,21 +29,24 @@ fn get_opt_i32(row: &mysql_async::Row, idx: usize) -> Option<i32> {
|
|||
row_get::<i32, _>(row, idx).or_else(|| row_get::<i64, _>(row, idx).and_then(|v| i32::try_from(v).ok()))
|
||||
}
|
||||
|
||||
fn list_user_schemas_sql() -> &'static str {
|
||||
"SELECT USERNAME FROM ALL_USERS \
|
||||
WHERE USERNAME NOT IN ('SYS','LBACSYS','__public') \
|
||||
ORDER BY USERNAME"
|
||||
}
|
||||
|
||||
pub async fn list_databases(pool: &mysql_async::Pool) -> Result<Vec<DatabaseInfo>, String> {
|
||||
let mut conn = pool.get_conn().await.map_err(|e| e.to_string())?;
|
||||
let result = conn
|
||||
.query_iter(
|
||||
"SELECT USERNAME FROM ALL_USERS \
|
||||
WHERE USERNAME NOT IN ('SYS','LBACSYS','ORAAUDITOR','__public') \
|
||||
ORDER BY USERNAME",
|
||||
)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
let result = conn.query_iter(list_user_schemas_sql()).await.map_err(|e| e.to_string())?;
|
||||
let rows: Vec<mysql_async::Row> = result.collect_and_drop().await.map_err(|e| e.to_string())?;
|
||||
|
||||
Ok(rows.iter().map(|row| DatabaseInfo { name: get_str(row, 0) }).collect())
|
||||
}
|
||||
|
||||
pub async fn list_schemas(pool: &mysql_async::Pool) -> Result<Vec<String>, String> {
|
||||
list_databases(pool).await.map(|databases| databases.into_iter().map(|database| database.name).collect())
|
||||
}
|
||||
|
||||
pub async fn list_tables(pool: &mysql_async::Pool, schema: &str) -> Result<Vec<TableInfo>, String> {
|
||||
let sql = format!(
|
||||
"SELECT TABLE_NAME, 'TABLE' AS TABLE_TYPE FROM ALL_TABLES WHERE OWNER = {s} \
|
||||
|
|
@ -289,4 +292,9 @@ mod tests {
|
|||
assert!(sql.contains("'PACKAGE'"));
|
||||
assert!(sql.contains("'PACKAGE BODY'"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ob_oracle_user_schema_sql_keeps_auditor_schema_available() {
|
||||
assert!(!list_user_schemas_sql().contains("ORAAUDITOR"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -714,6 +714,9 @@ async fn list_schemas_once(
|
|||
let pool = connections.get(&pool_key).ok_or("Pool not found")?;
|
||||
|
||||
match pool {
|
||||
PoolKind::Mysql(p, mode) if *mode == MysqlMode::OceanBaseOracle => db::ob_oracle::list_schemas(p)
|
||||
.await
|
||||
.map(|schemas| filter_visible_schema_names(schemas, visible_schema_filter.as_deref())),
|
||||
PoolKind::Postgres(p) => db::postgres::list_schemas(p)
|
||||
.await
|
||||
.map(|schemas| filter_visible_schema_names(schemas, visible_schema_filter.as_deref())),
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ pub(in crate::schema) async fn list_databases(
|
|||
|
||||
pub(in crate::schema) async fn list_schemas(pool: &PoolKind) -> Result<Vec<String>, String> {
|
||||
match pool {
|
||||
PoolKind::Mysql(p, mode) if *mode == MysqlMode::OceanBaseOracle => db::ob_oracle::list_schemas(p).await,
|
||||
PoolKind::Postgres(p) => db::postgres::list_schemas(p).await,
|
||||
_ => Ok(vec![]),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { test } from "vitest";
|
||||
import { buildDraftVisibleDatabasesConnectionId, connectionCanChooseVisibleDatabases, visibleDatabaseSelectionIsStale, initialVisibleDatabaseSelection } from "../../apps/desktop/src/lib/connectionVisibleDatabases.ts";
|
||||
import { connectionUsesVisibleSchemaFilter } from "../../apps/desktop/src/lib/visibleDatabases.ts";
|
||||
import type { ConnectionConfig } from "../../apps/desktop/src/types/database.ts";
|
||||
|
||||
function config(overrides: Partial<ConnectionConfig> = {}): ConnectionConfig {
|
||||
|
|
@ -52,6 +53,11 @@ test("ZooKeeper connections do not offer visible database selection", () => {
|
|||
assert.equal(connectionCanChooseVisibleDatabases(config({ db_type: "zookeeper" })), false);
|
||||
});
|
||||
|
||||
test("OceanBase Oracle uses schema filtering for visible object selection", () => {
|
||||
assert.equal(connectionUsesVisibleSchemaFilter(config({ db_type: "oceanbase-oracle" })), true);
|
||||
assert.equal(connectionUsesVisibleSchemaFilter(config({ db_type: "mysql", driver_profile: "oceanbase" })), false);
|
||||
});
|
||||
|
||||
test("visible database selection is stale when connection target changes", () => {
|
||||
const previous = config({ host: "db.internal", visible_databases: ["app"] });
|
||||
assert.equal(visibleDatabaseSelectionIsStale(previous, config({ host: "db.internal" })), false);
|
||||
|
|
|
|||
|
|
@ -21,3 +21,27 @@ test("schema options keep default system-schema filtering without explicit filte
|
|||
["CAM"],
|
||||
);
|
||||
});
|
||||
|
||||
test("schema options respect visible schemas for OceanBase Oracle mode", () => {
|
||||
assert.deepEqual(
|
||||
schemaOptionsForConnection(
|
||||
["ORAAUDITOR", "APP", "SYS"],
|
||||
{
|
||||
db_type: "oceanbase-oracle",
|
||||
visible_schemas: { SYS: ["ORAAUDITOR"] },
|
||||
},
|
||||
"SYS",
|
||||
),
|
||||
["ORAAUDITOR"],
|
||||
);
|
||||
});
|
||||
|
||||
test("schema options support legacy visible database filters for OceanBase Oracle mode", () => {
|
||||
assert.deepEqual(
|
||||
schemaOptionsForConnection(["ORAAUDITOR", "APP", "SYS"], {
|
||||
db_type: "oceanbase-oracle",
|
||||
visible_databases: ["ORAAUDITOR"],
|
||||
}),
|
||||
["ORAAUDITOR"],
|
||||
);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue