Revert "fix(objects): guard system source edits"
This reverts commit 17bc2e66d4.
This commit is contained in:
parent
2172b624b1
commit
af16ac219c
|
|
@ -218,26 +218,6 @@ function openAiPanel() {
|
|||
}
|
||||
}
|
||||
|
||||
function isActiveObjectSourceReadOnly() {
|
||||
return !!activeTab.value?.objectSource?.readOnlyReason;
|
||||
}
|
||||
|
||||
function guardWritableObjectSource() {
|
||||
if (!isActiveObjectSourceReadOnly()) return true;
|
||||
toast(t("objects.sourceReadOnlySystemObject"), 5000);
|
||||
return false;
|
||||
}
|
||||
|
||||
function tryExecuteActiveTab() {
|
||||
if (!guardWritableObjectSource()) return;
|
||||
tryExecute();
|
||||
}
|
||||
|
||||
function tryExplainActiveTab() {
|
||||
if (!guardWritableObjectSource()) return;
|
||||
tryExplain();
|
||||
}
|
||||
|
||||
function analyzeHistoryWithAi(entry: HistoryEntry) {
|
||||
const connectionId = entry.connection_id || activeTab.value?.connectionId;
|
||||
if (!connectionId) {
|
||||
|
|
@ -274,7 +254,6 @@ async function openSaveSqlDialog() {
|
|||
const tab = activeTab.value;
|
||||
if (!tab || !tab.sql.trim()) return;
|
||||
if (tab.objectSource) {
|
||||
if (!guardWritableObjectSource()) return;
|
||||
await saveActiveObjectSource(tab);
|
||||
return;
|
||||
}
|
||||
|
|
@ -304,10 +283,6 @@ async function saveActiveObjectSource(tab: NonNullable<typeof activeTab.value>)
|
|||
const connection = connectionStore.getConfig(tab.connectionId);
|
||||
const source = tab.objectSource;
|
||||
if (!connection || !source) return;
|
||||
if (source.readOnlyReason) {
|
||||
toast(t("objects.sourceReadOnlySystemObject"), 5000);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const statements = buildExecutableObjectSourceStatements({
|
||||
|
|
@ -595,7 +570,7 @@ function handleKeydown(e: KeyboardEvent) {
|
|||
) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
tryExecuteActiveTab();
|
||||
tryExecute();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -789,9 +764,9 @@ onUnmounted(() => {
|
|||
:active-tab="activeTab"
|
||||
:active-connection="activeConnection"
|
||||
:executable-sql="executableSql"
|
||||
@execute="tryExecuteActiveTab"
|
||||
@execute="tryExecute()"
|
||||
@cancel="cancelActiveExecution()"
|
||||
@explain="tryExplainActiveTab"
|
||||
@explain="tryExplain()"
|
||||
@format-sql="formatActiveSql"
|
||||
@save-sql="void openSaveSqlDialog()"
|
||||
@open-sql="openSqlFile"
|
||||
|
|
@ -812,9 +787,9 @@ onUnmounted(() => {
|
|||
:cursor-pos="cursorPos"
|
||||
@update:active-output-view="activeOutputView = $event"
|
||||
@fix-with-ai="fixWithAi"
|
||||
@execute="tryExecuteActiveTab"
|
||||
@execute="tryExecute()"
|
||||
@cancel="cancelActiveExecution()"
|
||||
@explain="tryExplainActiveTab"
|
||||
@explain="tryExplain()"
|
||||
@editor-update="
|
||||
(v: string) => {
|
||||
if (queryStore.activeTabId) queryStore.updateSql(queryStore.activeTabId, v);
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ const ExplainPlanViewer = defineAsyncComponent(() => import("@/components/explai
|
|||
const QueryChart = defineAsyncComponent(() => import("@/components/chart/QueryChart.vue"));
|
||||
import { useQueryStore } from "@/stores/queryStore";
|
||||
import { canCancelQueryExecution, queryExecutionLabelKey } from "@/lib/queryExecutionState";
|
||||
import { shouldShowQueryOutputPane } from "@/lib/contentAreaLayout";
|
||||
import { databaseDisplayNameForTab } from "@/lib/tabPresentation";
|
||||
import { isTableDataEditable } from "@/lib/tableEditing";
|
||||
import type { QueryTab, ConnectionConfig } from "@/types/database";
|
||||
|
|
@ -115,9 +114,6 @@ const activeQueryError = computed(() => {
|
|||
return String(result.rows[0]?.[0] ?? "");
|
||||
});
|
||||
|
||||
const showQueryOutputPane = computed(() => shouldShowQueryOutputPane(props.activeTab));
|
||||
const objectSourceReadOnly = computed(() => !!props.activeTab.objectSource?.readOnlyReason);
|
||||
|
||||
// Column info panel handlers
|
||||
async function onHandleClickColumn(
|
||||
matchedCols: Array<{ name: string; table: string; schema?: string }>,
|
||||
|
|
@ -212,7 +208,7 @@ defineExpose({ focusSearch, refreshData });
|
|||
<!-- Query mode: editor + results -->
|
||||
<template v-if="activeTab.mode === 'query'">
|
||||
<Splitpanes horizontal class="flex-1">
|
||||
<Pane :size="showQueryOutputPane ? 40 : 100" :min-size="showQueryOutputPane ? 15 : 100">
|
||||
<Pane :size="40" :min-size="15">
|
||||
<div class="h-full flex flex-col relative">
|
||||
<QueryEditor
|
||||
class="flex-1"
|
||||
|
|
@ -223,7 +219,6 @@ defineExpose({ focusSearch, refreshData });
|
|||
:format-dialect="activeSqlFormatDialect"
|
||||
:format-request-id="formatSqlRequestId"
|
||||
:execution-error="activeQueryError"
|
||||
:read-only="objectSourceReadOnly"
|
||||
@update:model-value="emit('editorUpdate', $event)"
|
||||
@selection-change="emit('editorSelectionChange', $event)"
|
||||
@cursor-change="emit('editorCursorChange', $event)"
|
||||
|
|
@ -243,7 +238,7 @@ defineExpose({ focusSearch, refreshData });
|
|||
/>
|
||||
</div>
|
||||
</Pane>
|
||||
<Pane v-if="showQueryOutputPane" :size="60" :min-size="20">
|
||||
<Pane :size="60" :min-size="20">
|
||||
<div class="h-full flex flex-col">
|
||||
<div
|
||||
v-if="
|
||||
|
|
|
|||
|
|
@ -65,11 +65,7 @@ const activeConnectionValue = computed(() => props.activeConnection?.id || "");
|
|||
const activeSchemaValue = computed(() => props.activeTab.schema || "");
|
||||
const isSingleDb = computed(() => isSingleDatabase(props.activeConnection?.db_type));
|
||||
const schemaDatabaseKey = computed(() => props.activeTab.database || (isSingleDb.value ? "_" : ""));
|
||||
const objectSourceReadOnly = computed(() => !!props.activeTab.objectSource?.readOnlyReason);
|
||||
const saveTooltip = computed(() => {
|
||||
if (objectSourceReadOnly.value) return t("objects.sourceReadOnlySystemObject");
|
||||
return props.activeTab.objectSource ? t("objects.saveSource") : t("toolbar.saveSql");
|
||||
});
|
||||
const saveTooltip = computed(() => (props.activeTab.objectSource ? t("objects.saveSource") : t("toolbar.saveSql")));
|
||||
|
||||
const showSchemaSelector = computed(() => {
|
||||
const connection = props.activeConnection;
|
||||
|
|
@ -128,10 +124,7 @@ function connectionById(connectionId: string): ConnectionConfig | undefined {
|
|||
: 'bg-emerald-500/10 text-emerald-700 hover:bg-emerald-500/20 hover:text-emerald-800 dark:text-emerald-300 dark:hover:text-emerald-200'
|
||||
"
|
||||
:disabled="
|
||||
objectSourceReadOnly ||
|
||||
activeTab.isCancelling ||
|
||||
activeTab.isExplaining ||
|
||||
(!activeTab.isExecuting && !executableSql.trim())
|
||||
activeTab.isCancelling || activeTab.isExplaining || (!activeTab.isExecuting && !executableSql.trim())
|
||||
"
|
||||
@click="activeTab.isExecuting ? emit('cancel') : emit('execute')"
|
||||
>
|
||||
|
|
@ -155,9 +148,7 @@ function connectionById(connectionId: string): ConnectionConfig | undefined {
|
|||
? ''
|
||||
: 'text-violet-600 hover:bg-violet-500/10 hover:text-violet-700 dark:text-violet-300 dark:hover:text-violet-200'
|
||||
"
|
||||
:disabled="
|
||||
objectSourceReadOnly || activeTab.isExecuting || (!activeTab.isExplaining && !executableSql.trim())
|
||||
"
|
||||
:disabled="activeTab.isExecuting || (!activeTab.isExplaining && !executableSql.trim())"
|
||||
@click="activeTab.isExplaining ? emit('cancel') : emit('explain')"
|
||||
>
|
||||
<Square v-if="activeTab.isExplaining" class="h-3.5 w-3.5 fill-current" />
|
||||
|
|
@ -174,7 +165,7 @@ function connectionById(connectionId: string): ConnectionConfig | undefined {
|
|||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-6 w-6 text-amber-600 hover:bg-amber-500/10 hover:text-amber-700 dark:text-amber-300 dark:hover:text-amber-200"
|
||||
:disabled="objectSourceReadOnly || activeTab.isExecuting || activeTab.isExplaining || !activeTab.sql.trim()"
|
||||
:disabled="activeTab.isExecuting || activeTab.isExplaining || !activeTab.sql.trim()"
|
||||
@click="emit('formatSql')"
|
||||
>
|
||||
<AlignLeft class="h-3.5 w-3.5" />
|
||||
|
|
@ -188,7 +179,7 @@ function connectionById(connectionId: string): ConnectionConfig | undefined {
|
|||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-6 w-6 text-blue-600 hover:bg-blue-500/10 hover:text-blue-700 dark:text-blue-300 dark:hover:text-blue-200"
|
||||
:disabled="objectSourceReadOnly || !activeTab.sql.trim()"
|
||||
:disabled="!activeTab.sql.trim()"
|
||||
@click="emit('saveSql')"
|
||||
>
|
||||
<Save class="h-3.5 w-3.5" />
|
||||
|
|
|
|||
|
|
@ -34,11 +34,7 @@ import type { ConnectionConfig, ObjectInfo, ObjectSourceKind } from "@/types/dat
|
|||
import { isSchemaAware } from "@/lib/databaseCapabilities";
|
||||
import { buildTableSelectSql, qualifiedTableName } from "@/lib/tableSelectSql";
|
||||
import { useToast } from "@/composables/useToast";
|
||||
import {
|
||||
buildExecutableObjectSourceStatements,
|
||||
objectSourceReadOnlyReason,
|
||||
objectSourceSaveExecutionMode,
|
||||
} from "@/lib/objectSourceEditor";
|
||||
import { buildExecutableObjectSourceStatements, objectSourceSaveExecutionMode } from "@/lib/objectSourceEditor";
|
||||
import { buildRenameObjectSql, supportsObjectRename } from "@/lib/objectRenameSql";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import { useQueryStore } from "@/stores/queryStore";
|
||||
|
|
@ -112,7 +108,6 @@ const sourceFormatDialect = computed<SqlFormatDialect>(() => {
|
|||
return "generic";
|
||||
}
|
||||
});
|
||||
const sourceRowReadOnlyReason = computed(() => (sourceRow.value ? readOnlyReasonForRow(sourceRow.value) : null));
|
||||
const objectFilters = computed<ObjectFilter[]>(() =>
|
||||
(
|
||||
[
|
||||
|
|
@ -174,17 +169,7 @@ function canOpenSource(row: ObjectBrowserRow) {
|
|||
}
|
||||
|
||||
function canRename(row: ObjectBrowserRow) {
|
||||
return !readOnlyReasonForRow(row) && supportsObjectRename(props.connection.db_type, row.type);
|
||||
}
|
||||
|
||||
function readOnlyReasonForRow(row: ObjectBrowserRow) {
|
||||
if (row.type !== "VIEW" && row.type !== "PROCEDURE" && row.type !== "FUNCTION") return null;
|
||||
return objectSourceReadOnlyReason({
|
||||
databaseType: props.connection.db_type,
|
||||
schema: row.schema || selectedSchema.value || props.database,
|
||||
name: row.name,
|
||||
objectType: row.type,
|
||||
});
|
||||
return supportsObjectRename(props.connection.db_type, row.type);
|
||||
}
|
||||
|
||||
function sourceTitle(row: ObjectBrowserRow | null) {
|
||||
|
|
@ -220,7 +205,7 @@ async function openSource(row: ObjectBrowserRow) {
|
|||
);
|
||||
sourceContent.value = result.source;
|
||||
sourceDraft.value = result.source;
|
||||
sourceEditing.value = !readOnlyReasonForRow(row);
|
||||
sourceEditing.value = true;
|
||||
} catch (e: any) {
|
||||
sourceError.value = e?.message || String(e);
|
||||
} finally {
|
||||
|
|
@ -250,19 +235,11 @@ function qualifiedName(row: ObjectBrowserRow): string {
|
|||
}
|
||||
|
||||
function requestDrop(row: ObjectBrowserRow) {
|
||||
if (readOnlyReasonForRow(row)) {
|
||||
toast(t("objects.sourceReadOnlySystemObject"), 5000);
|
||||
return;
|
||||
}
|
||||
dropTarget.value = row;
|
||||
showDropConfirm.value = true;
|
||||
}
|
||||
|
||||
function requestRename(row: ObjectBrowserRow) {
|
||||
if (readOnlyReasonForRow(row)) {
|
||||
toast(t("objects.sourceReadOnlySystemObject"), 5000);
|
||||
return;
|
||||
}
|
||||
renameTarget.value = row;
|
||||
renameInput.value = row.name;
|
||||
renameError.value = "";
|
||||
|
|
@ -290,10 +267,6 @@ async function confirmRename() {
|
|||
const row = renameTarget.value;
|
||||
const newName = renameInput.value.trim();
|
||||
if (!row || !newName || newName === row.name) return;
|
||||
if (readOnlyReasonForRow(row)) {
|
||||
renameError.value = t("objects.sourceReadOnlySystemObject");
|
||||
return;
|
||||
}
|
||||
renameError.value = "";
|
||||
try {
|
||||
const schema = row.schema || selectedSchema.value || props.database;
|
||||
|
|
@ -322,11 +295,6 @@ async function confirmRename() {
|
|||
async function confirmDrop() {
|
||||
if (!dropTarget.value) return;
|
||||
const row = dropTarget.value;
|
||||
if (readOnlyReasonForRow(row)) {
|
||||
toast(t("objects.sourceReadOnlySystemObject"), 5000);
|
||||
dropTarget.value = null;
|
||||
return;
|
||||
}
|
||||
const typeSql =
|
||||
row.type === "VIEW"
|
||||
? "VIEW"
|
||||
|
|
@ -395,10 +363,6 @@ function copySource() {
|
|||
|
||||
function editSource() {
|
||||
if (!sourceRow.value || !sourceContent.value) return;
|
||||
if (sourceRowReadOnlyReason.value) {
|
||||
toast(t("objects.sourceReadOnlySystemObject"), 5000);
|
||||
return;
|
||||
}
|
||||
sourceDraft.value = sourceContent.value;
|
||||
sourceSaveError.value = "";
|
||||
sourceEditing.value = true;
|
||||
|
|
@ -412,10 +376,6 @@ function cancelEditSource() {
|
|||
|
||||
async function saveSource() {
|
||||
if (!sourceRow.value || !sourceDraft.value.trim()) return;
|
||||
if (sourceRowReadOnlyReason.value) {
|
||||
toast(t("objects.sourceReadOnlySystemObject"), 5000);
|
||||
return;
|
||||
}
|
||||
const row = sourceRow.value;
|
||||
const schema = row.schema || selectedSchema.value || props.database;
|
||||
sourceSaving.value = true;
|
||||
|
|
@ -691,13 +651,11 @@ watch(
|
|||
<ContextMenuItem v-if="canRename(item)" @click="requestRename(item)">
|
||||
<Pencil class="w-4 h-4 mr-2" /> {{ t("contextMenu.renameObject") }}
|
||||
</ContextMenuItem>
|
||||
<template v-if="!readOnlyReasonForRow(item)">
|
||||
<ContextMenuSeparator />
|
||||
<ContextMenuItem class="text-destructive" @click="requestDrop(item)">
|
||||
<Trash2 class="w-4 h-4 mr-2" />
|
||||
{{ item.type === "PROCEDURE" ? t("contextMenu.dropProcedure") : t("contextMenu.dropFunction") }}
|
||||
</ContextMenuItem>
|
||||
</template>
|
||||
<ContextMenuSeparator />
|
||||
<ContextMenuItem class="text-destructive" @click="requestDrop(item)">
|
||||
<Trash2 class="w-4 h-4 mr-2" />
|
||||
{{ item.type === "PROCEDURE" ? t("contextMenu.dropProcedure") : t("contextMenu.dropFunction") }}
|
||||
</ContextMenuItem>
|
||||
</template>
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
|
|
@ -743,8 +701,7 @@ watch(
|
|||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-5 w-5"
|
||||
:disabled="!sourceContent || !!sourceRowReadOnlyReason"
|
||||
:title="sourceRowReadOnlyReason ? t('objects.sourceReadOnlySystemObject') : undefined"
|
||||
:disabled="!sourceContent"
|
||||
@click="editSource"
|
||||
>
|
||||
<PencilLine class="h-3 w-3" />
|
||||
|
|
@ -767,7 +724,6 @@ watch(
|
|||
:database="props.database"
|
||||
:dialect="sourceDialect"
|
||||
:format-dialect="sourceFormatDialect"
|
||||
:read-only="!!sourceRowReadOnlyReason"
|
||||
force-word-wrap
|
||||
@save="saveSource"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -92,7 +92,6 @@ import {
|
|||
treeNodeRowAction,
|
||||
treeNodeRowDoubleClickAction,
|
||||
} from "@/lib/treeNodeClick";
|
||||
import { objectSourceReadOnlyReason } from "@/lib/objectSourceEditor";
|
||||
import { formatCsv, formatJson, formatSqlInsert } from "@/lib/exportFormats";
|
||||
import { fetchTableDataForExport } from "@/lib/tableDataExport";
|
||||
import {
|
||||
|
|
@ -666,18 +665,6 @@ function viewObjectSource() {
|
|||
schema,
|
||||
name: node.label,
|
||||
objectType,
|
||||
readOnlyReason: (() => {
|
||||
const databaseType = currentDatabaseType();
|
||||
if (!databaseType) return undefined;
|
||||
return (
|
||||
objectSourceReadOnlyReason({
|
||||
databaseType,
|
||||
schema,
|
||||
name: node.label,
|
||||
objectType,
|
||||
}) ?? undefined
|
||||
);
|
||||
})(),
|
||||
});
|
||||
})
|
||||
.catch((e: any) => {
|
||||
|
|
@ -686,10 +673,6 @@ function viewObjectSource() {
|
|||
}
|
||||
|
||||
function requestDropObject() {
|
||||
if (currentObjectSourceReadOnlyReason.value) {
|
||||
toast(t("objects.sourceReadOnlySystemObject"), 5000);
|
||||
return;
|
||||
}
|
||||
showDropObjectConfirm.value = true;
|
||||
}
|
||||
|
||||
|
|
@ -703,21 +686,7 @@ function nodeRenameObjectType(): RenameableObjectType | null {
|
|||
|
||||
const canRenameObject = computed(() => {
|
||||
const objectType = nodeRenameObjectType();
|
||||
return (
|
||||
!!objectType && !currentObjectSourceReadOnlyReason.value && supportsObjectRename(currentDatabaseType(), objectType)
|
||||
);
|
||||
});
|
||||
|
||||
const currentObjectSourceReadOnlyReason = computed(() => {
|
||||
const objectType = objectSourceKindForTreeNode(props.node.type);
|
||||
const databaseType = currentDatabaseType();
|
||||
if (!objectType || !databaseType) return null;
|
||||
return objectSourceReadOnlyReason({
|
||||
databaseType,
|
||||
schema: props.node.schema,
|
||||
name: props.node.label,
|
||||
objectType,
|
||||
});
|
||||
return !!objectType && supportsObjectRename(currentDatabaseType(), objectType);
|
||||
});
|
||||
|
||||
function openRenameObjectDialog() {
|
||||
|
|
@ -2005,13 +1974,11 @@ const isDragging = computed(() => dragState.active && dragState.draggedId === pr
|
|||
<Pencil class="w-4 h-4 mr-2" />
|
||||
{{ t("contextMenu.renameObject") }}
|
||||
</ContextMenuItem>
|
||||
<template v-if="!currentObjectSourceReadOnlyReason">
|
||||
<ContextMenuSeparator />
|
||||
<ContextMenuItem class="text-destructive" @click="requestDropObject">
|
||||
<Trash2 class="w-4 h-4 mr-2" />
|
||||
{{ node.type === "procedure" ? t("contextMenu.dropProcedure") : t("contextMenu.dropFunction") }}
|
||||
</ContextMenuItem>
|
||||
</template>
|
||||
<ContextMenuSeparator />
|
||||
<ContextMenuItem class="text-destructive" @click="requestDropObject">
|
||||
<Trash2 class="w-4 h-4 mr-2" />
|
||||
{{ node.type === "procedure" ? t("contextMenu.dropProcedure") : t("contextMenu.dropFunction") }}
|
||||
</ContextMenuItem>
|
||||
</template>
|
||||
|
||||
<template v-if="isGroupLabel(node)">
|
||||
|
|
|
|||
|
|
@ -762,7 +762,6 @@ export default {
|
|||
cancelEdit: "Cancel",
|
||||
sourceSaved: "Source saved",
|
||||
sourceSaveFailed: "Failed to save source: {message}",
|
||||
sourceReadOnlySystemObject: "System objects are protected and cannot be modified or deleted",
|
||||
schemaColumn: "Schema",
|
||||
comment: "Comment",
|
||||
loadingSchemas: "Loading schemas...",
|
||||
|
|
|
|||
|
|
@ -667,7 +667,6 @@ export default {
|
|||
cancelEdit: "Cancelar",
|
||||
sourceSaved: "Código fuente guardado",
|
||||
sourceSaveFailed: "Error al guardar el código fuente: {message}",
|
||||
sourceReadOnlySystemObject: "Los objetos del sistema están protegidos y no se pueden modificar ni eliminar",
|
||||
schemaColumn: "Esquema",
|
||||
comment: "Comentario",
|
||||
loadingSchemas: "Cargando esquemas...",
|
||||
|
|
|
|||
|
|
@ -742,7 +742,6 @@ export default {
|
|||
cancelEdit: "取消",
|
||||
sourceSaved: "源码已保存",
|
||||
sourceSaveFailed: "保存源码失败:{message}",
|
||||
sourceReadOnlySystemObject: "系统对象受保护,不能修改或删除",
|
||||
schemaColumn: "Schema",
|
||||
comment: "注释",
|
||||
loadingSchemas: "加载 Schema...",
|
||||
|
|
|
|||
|
|
@ -1,13 +0,0 @@
|
|||
import type { QueryTab } from "@/types/database";
|
||||
|
||||
type QueryOutputPaneState = Pick<
|
||||
QueryTab,
|
||||
"mode" | "objectSource" | "result" | "explainPlan" | "explainError" | "isExecuting" | "isExplaining"
|
||||
>;
|
||||
|
||||
export function shouldShowQueryOutputPane(tab: QueryOutputPaneState): boolean {
|
||||
if (tab.mode !== "query") return false;
|
||||
if (!tab.objectSource) return true;
|
||||
|
||||
return !!(tab.result || tab.explainPlan || tab.explainError || tab.isExecuting || tab.isExplaining);
|
||||
}
|
||||
|
|
@ -9,14 +9,6 @@ type BuildEditableObjectSourceSqlInput = {
|
|||
};
|
||||
|
||||
export type ObjectSourceSaveExecutionMode = "single" | "script";
|
||||
export type ObjectSourceReadOnlyReason = "system-object";
|
||||
|
||||
type ObjectSourceReadOnlyInput = {
|
||||
databaseType: DatabaseType;
|
||||
schema?: string | null;
|
||||
name: string;
|
||||
objectType: ObjectSourceKind;
|
||||
};
|
||||
|
||||
const postgresLikeRoutineRenameTypes = new Set<DatabaseType>([
|
||||
"postgres",
|
||||
|
|
@ -106,37 +98,3 @@ export function buildExecutableObjectSourceSql(input: BuildEditableObjectSourceS
|
|||
export function objectSourceSaveExecutionMode(_databaseType: DatabaseType): ObjectSourceSaveExecutionMode {
|
||||
return "single";
|
||||
}
|
||||
|
||||
const readOnlySystemSchemas = new Set([
|
||||
"information_schema",
|
||||
"pg_catalog",
|
||||
"sys",
|
||||
"system",
|
||||
"mysql",
|
||||
"performance_schema",
|
||||
"xdb",
|
||||
"outln",
|
||||
"dbsnmp",
|
||||
"ctisys",
|
||||
"sysauditor",
|
||||
"syssso",
|
||||
]);
|
||||
|
||||
const damengSysdbaRoutinePrefixes = ["SP_TS_", "SP_ARCH_", "SP_DB_", "SP_DROP_CONS_", "SP_TAB_", "SP_UPDATE_SYS"];
|
||||
|
||||
export function objectSourceReadOnlyReason(input: ObjectSourceReadOnlyInput): ObjectSourceReadOnlyReason | null {
|
||||
const schema = input.schema?.trim().toLowerCase();
|
||||
if (schema && readOnlySystemSchemas.has(schema)) return "system-object";
|
||||
|
||||
const name = input.name.trim().toUpperCase();
|
||||
if (
|
||||
input.databaseType === "dameng" &&
|
||||
input.schema?.trim().toUpperCase() === "SYSDBA" &&
|
||||
(input.objectType === "PROCEDURE" || input.objectType === "FUNCTION") &&
|
||||
damengSysdbaRoutinePrefixes.some((prefix) => name.startsWith(prefix))
|
||||
) {
|
||||
return "system-object";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -290,7 +290,6 @@ export interface QueryTab {
|
|||
schema?: string;
|
||||
name: string;
|
||||
objectType: ObjectSourceKind;
|
||||
readOnlyReason?: "system-object";
|
||||
};
|
||||
tableMeta?: {
|
||||
schema?: string;
|
||||
|
|
|
|||
|
|
@ -1,54 +0,0 @@
|
|||
import { strict as assert } from "node:assert";
|
||||
import test from "node:test";
|
||||
import { shouldShowQueryOutputPane } from "../../apps/desktop/src/lib/contentAreaLayout.ts";
|
||||
|
||||
test("object source tabs hide the query output pane while idle", () => {
|
||||
assert.equal(
|
||||
shouldShowQueryOutputPane({
|
||||
mode: "query",
|
||||
objectSource: { objectType: "PROCEDURE", name: "refresh_cache" },
|
||||
isExecuting: false,
|
||||
isExplaining: false,
|
||||
}),
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
test("object source tabs show the query output pane after execution starts or returns feedback", () => {
|
||||
assert.equal(
|
||||
shouldShowQueryOutputPane({
|
||||
mode: "query",
|
||||
objectSource: { objectType: "FUNCTION", name: "calc_total" },
|
||||
isExecuting: true,
|
||||
isExplaining: false,
|
||||
}),
|
||||
true,
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
shouldShowQueryOutputPane({
|
||||
mode: "query",
|
||||
objectSource: { objectType: "PROCEDURE", name: "refresh_cache" },
|
||||
isExecuting: false,
|
||||
isExplaining: false,
|
||||
result: {
|
||||
columns: ["Error"],
|
||||
rows: [["syntax error"]],
|
||||
affected_rows: 0,
|
||||
execution_time_ms: 2,
|
||||
},
|
||||
}),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test("regular query tabs keep the output pane visible before execution", () => {
|
||||
assert.equal(
|
||||
shouldShowQueryOutputPane({
|
||||
mode: "query",
|
||||
isExecuting: false,
|
||||
isExplaining: false,
|
||||
}),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
|
@ -3,7 +3,6 @@ import test from "node:test";
|
|||
import {
|
||||
buildExecutableObjectSourceSql,
|
||||
buildExecutableObjectSourceStatements,
|
||||
objectSourceReadOnlyReason,
|
||||
objectSourceSaveExecutionMode,
|
||||
} from "../../apps/desktop/src/lib/objectSourceEditor.ts";
|
||||
|
||||
|
|
@ -105,37 +104,3 @@ test("object source SQL joins generated save statements for previews", () => {
|
|||
'CREATE OR REPLACE PROCEDURE "public"."refresh_cache_v2"(mode text)\nLANGUAGE SQL\nAS $$ SELECT 1 $$;\nDROP PROCEDURE IF EXISTS "public"."refresh_cache"(mode text);',
|
||||
);
|
||||
});
|
||||
|
||||
test("system schemas open object source as read-only", () => {
|
||||
assert.equal(
|
||||
objectSourceReadOnlyReason({
|
||||
databaseType: "oracle",
|
||||
schema: "SYS",
|
||||
name: "DBMS_STATS",
|
||||
objectType: "PROCEDURE",
|
||||
}),
|
||||
"system-object",
|
||||
);
|
||||
});
|
||||
|
||||
test("Dameng built-in maintenance routines open as read-only without blocking user SP names", () => {
|
||||
assert.equal(
|
||||
objectSourceReadOnlyReason({
|
||||
databaseType: "dameng",
|
||||
schema: "SYSDBA",
|
||||
name: "SP_TS_BAKSET_REMOVE_BATCH",
|
||||
objectType: "PROCEDURE",
|
||||
}),
|
||||
"system-object",
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
objectSourceReadOnlyReason({
|
||||
databaseType: "dameng",
|
||||
schema: "SYSDBA",
|
||||
name: "SP_HELLO",
|
||||
objectType: "PROCEDURE",
|
||||
}),
|
||||
null,
|
||||
);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue