feat(mongodb): support editing find query results
This commit is contained in:
parent
585a6e2d40
commit
33422ec921
|
|
@ -2499,13 +2499,17 @@ const canGoNextPage = computed(() => {
|
|||
const canJumpLastPage = computed(() => canGoNextPage.value && (hasKnownTotalRowCount.value || allRowsLoaded.value || !!props.tableMeta || !!props.countSql));
|
||||
const totalRowCountBusy = computed(() => props.totalRowCountLoading === true || manualTotalRowCountLoading.value);
|
||||
const canCalculateTotalRowCount = computed(() => !isResultsContext.value && !!props.connectionId && (!!props.tableMeta || !!props.countSql));
|
||||
const showQueryEditReadyBadge = computed(() => isResultsContext.value && hasData.value && !!props.editable && !!props.tableMeta);
|
||||
const showQueryEditReadyBadge = computed(() => isResultsContext.value && hasData.value && !!props.editable && (!!props.tableMeta || !!props.customSaveHandler));
|
||||
const queryEditReadyTargetLabel = computed(() => props.tableMeta?.tableName ?? props.customSaveHandler?.targetLabel ?? "");
|
||||
const showKeylessEditWarning = computed(() => !!props.editable && !!props.tableMeta && canUseKeylessRowPredicate(props.databaseType, props.tableMeta.primaryKeys ?? []));
|
||||
const canShowWhereSearch = computed(() => !!props.onExecuteSql && !isResultsContext.value);
|
||||
const canUseWhereSearch = computed(() => !!props.tableMeta && !!props.onExecuteSql && !isResultsContext.value);
|
||||
type DataGridTableMeta = NonNullable<typeof props.tableMeta>;
|
||||
const hiveTableTransactional = ref<boolean | undefined>(undefined);
|
||||
const canEditExistingRows = computed(() => !!props.customSaveHandler || canEditExistingTableRows(props.databaseType, hiveTableTransactional.value, props.tableMeta?.primaryKeys ?? []));
|
||||
const customReadonlyColumns = computed(() => new Set((props.customSaveHandler?.readonlyColumns ?? []).map((column) => column.toLowerCase())));
|
||||
const canInsertRows = computed(() => !props.customSaveHandler || props.customSaveHandler.canInsert !== false);
|
||||
const canDeleteRows = computed(() => !props.customSaveHandler || props.customSaveHandler.canDelete !== false);
|
||||
watch(
|
||||
() => [props.databaseType, props.connectionId, props.database, props.tableMeta?.schema, props.tableMeta?.tableName],
|
||||
async () => {
|
||||
|
|
@ -2909,8 +2913,9 @@ function canEditRowItem(item: RowItem | undefined): boolean {
|
|||
|
||||
function canEditCellItem(item: RowItem | undefined, columnIndex: number): boolean {
|
||||
if (!canEditRowItem(item) || !canEditColumn(columnIndex)) return false;
|
||||
const column = props.result.columns[columnIndex] ?? "";
|
||||
if (customReadonlyColumns.value.has(column.toLowerCase())) return false;
|
||||
if (!item?.isNew) {
|
||||
const column = props.result.columns[columnIndex] ?? "";
|
||||
const sourceColumn = props.sourceColumns?.[columnIndex] ?? column;
|
||||
if (isClickHouseExistingRowReadonlyColumn(props.databaseType, sourceColumn, props.tableMeta?.primaryKeys ?? [], props.tableMeta?.columns ?? [])) return false;
|
||||
if (isTdengineExistingRowReadonlyColumn(props.databaseType, column, props.tableMeta?.columns ?? [])) return false;
|
||||
|
|
@ -3010,7 +3015,7 @@ function isDecimalColumnType(dataType: string): boolean {
|
|||
}
|
||||
|
||||
function canDeleteRowItem(item: RowItem | undefined): boolean {
|
||||
return !!props.editable && !!item && !item.isDeleted && (item.isNew || canEditExistingRows.value);
|
||||
return !!props.editable && canDeleteRows.value && !!item && !item.isDeleted && (item.isNew || canEditExistingRows.value);
|
||||
}
|
||||
|
||||
function resetInfiniteScrollState() {
|
||||
|
|
@ -3050,6 +3055,7 @@ function onToolbarRollback() {
|
|||
}
|
||||
|
||||
function addRow() {
|
||||
if (!canInsertRows.value) return;
|
||||
addEditorRow();
|
||||
focusAppendedTransposeRecord();
|
||||
}
|
||||
|
|
@ -4441,7 +4447,7 @@ function onCanvasContext(event: MouseEvent) {
|
|||
}
|
||||
const actualColIdx = visibleColumnIndexes.value[hit.visibleColIdx];
|
||||
if (actualColIdx === undefined) return;
|
||||
onCellContext(item.id, item.displayIndex, actualColIdx, hit.visibleColIdx);
|
||||
onCellContext(item.id, item.displayIndex, actualColIdx, hit.visibleColIdx, event);
|
||||
}
|
||||
|
||||
function onCanvasDblClick(event: MouseEvent) {
|
||||
|
|
@ -5355,7 +5361,7 @@ async function onGridKeydown(event: KeyboardEvent) {
|
|||
return;
|
||||
}
|
||||
if ((event.metaKey || event.ctrlKey) && !event.shiftKey && event.key.toLowerCase() === "n") {
|
||||
if (props.editable && (props.tableMeta || props.customSaveHandler)) {
|
||||
if (props.editable && (props.tableMeta || props.customSaveHandler) && canInsertRows.value) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
addRow();
|
||||
|
|
@ -5981,7 +5987,13 @@ async function copyAlterColumnSql() {
|
|||
toast(t("grid.copyAlterSqlFailed", { message: e?.message || String(e) }), 5000);
|
||||
}
|
||||
}
|
||||
function onCellContext(rowId: number, rowIndex: number, colIdx: number, visibleColIdx: number) {
|
||||
function clearNativeTextSelection() {
|
||||
window.getSelection()?.removeAllRanges();
|
||||
}
|
||||
|
||||
function onCellContext(rowId: number, rowIndex: number, colIdx: number, visibleColIdx: number, event?: MouseEvent) {
|
||||
event?.preventDefault();
|
||||
clearNativeTextSelection();
|
||||
contextHeaderColumn.value = null;
|
||||
contextHeaderColumnIndex.value = null;
|
||||
contextCell.value = { rowId, rowIndex, col: colIdx };
|
||||
|
|
@ -6806,11 +6818,13 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
|||
if (props.editable && contextRowItem.value) {
|
||||
const labels = rowActionLabels();
|
||||
items.push({ label: "", separator: true });
|
||||
items.push({
|
||||
label: labels.clone,
|
||||
action: () => (isMultiRow.value ? cloneRows(affectedRowIds()) : cloneRow(contextRowItem.value!.id)),
|
||||
icon: CopyPlus,
|
||||
});
|
||||
if (canInsertRows.value) {
|
||||
items.push({
|
||||
label: labels.clone,
|
||||
action: () => (isMultiRow.value ? cloneRows(affectedRowIds()) : cloneRow(contextRowItem.value!.id)),
|
||||
icon: CopyPlus,
|
||||
});
|
||||
}
|
||||
if (contextRowItem.value.isDeleted) {
|
||||
items.push({
|
||||
label: labels.restore,
|
||||
|
|
@ -7163,7 +7177,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
|||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" class="max-w-sm">
|
||||
{{ t("grid.queryEditReadyHint", { table: tableMeta?.tableName }) }}
|
||||
{{ t("grid.queryEditReadyHint", { table: queryEditReadyTargetLabel }) }}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip v-if="showKeylessEditWarning">
|
||||
|
|
@ -7216,7 +7230,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
|||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">{{ t("grid.goToColumn") }}</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip v-if="editable && (tableMeta || customSaveHandler)">
|
||||
<Tooltip v-if="editable && (tableMeta || customSaveHandler) && canInsertRows">
|
||||
<TooltipTrigger as-child>
|
||||
<Button variant="ghost" size="sm" :class="['data-grid-topbar-action-button h-5 shrink-0 text-xs px-1.5', compactDataGridToolbar ? 'data-grid-topbar-action-button--compact' : '']" @click="addRow">
|
||||
<Plus class="data-grid-topbar-action-icon w-3 h-3" />
|
||||
|
|
@ -7999,7 +8013,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
|||
@mouseleave="onCellMouseleave(item.displayIndex, col.actualColIdx)"
|
||||
@dblclick="canEditCellItem(item, col.actualColIdx) && startDomCellEdit(item.id, col.actualColIdx, formatCellCached(item.data[col.actualColIdx], col.actualColIdx), $event)"
|
||||
:data-visible-col-index="col.visibleColIdx"
|
||||
@contextmenu="onCellContext(item.id, item.displayIndex, col.actualColIdx, col.visibleColIdx)"
|
||||
@contextmenu="onCellContext(item.id, item.displayIndex, col.actualColIdx, col.visibleColIdx, $event)"
|
||||
>
|
||||
<template v-if="editingCell?.rowId === item.id && editingCell?.col === col.actualColIdx">
|
||||
<TemporalCellEditor v-if="temporalEditorKindForColumn(col.actualColIdx)" v-model="editValue" :kind="temporalEditorKindForColumn(col.actualColIdx)!" @cancel="cancelEdit" @commit="commitGridEdit" />
|
||||
|
|
|
|||
|
|
@ -60,9 +60,12 @@ import { tableMetaForDataTab } from "@/lib/tableDataTabMeta";
|
|||
import { formatShortcut } from "@/lib/shortcutRegistry";
|
||||
import { effectiveDatabaseTypeForConnection } from "@/lib/jdbcDialect";
|
||||
import { chartableColumnIndexes } from "@/lib/chartData";
|
||||
import * as api from "@/lib/api";
|
||||
import { buildMongoUpdateDocument, formatMongoShellLiteral, type MongoInputValue } from "@/lib/mongoDocumentValues";
|
||||
import type { SqlExecutionOverride } from "@/lib/sqlExecutionTarget";
|
||||
import type { DataGridSortMode } from "@/lib/dataGridSort";
|
||||
import { useTabScroll } from "@/composables/useTabScroll";
|
||||
import type { CustomSaveHandler } from "@/composables/useDataGridEditor";
|
||||
import type { QueryTab, ConnectionConfig, TableInfoTab, TreeNode, VectorCollectionMeta } from "@/types/database";
|
||||
import { sqlFormatDialectForDbType, type SqlFormatDialect } from "@/lib/sqlFormatter";
|
||||
|
||||
|
|
@ -279,6 +282,60 @@ const hasTabularResult = computed(() => {
|
|||
return visibleResultItems.value.length > 0;
|
||||
});
|
||||
const canShowResultOutput = computed(() => hasTabularResult.value || props.activeTab.isExecuting);
|
||||
type MongoQueryGridChanges = {
|
||||
dirtyRows: Map<number, Map<number, MongoInputValue>>;
|
||||
deletedRows: Set<number>;
|
||||
newRows: MongoInputValue[][];
|
||||
columns: string[];
|
||||
rows: MongoInputValue[][];
|
||||
};
|
||||
function mongoIdPreview(val: unknown): string {
|
||||
if (val === null || val === undefined) return "null";
|
||||
if (typeof val === "string" && /^[a-fA-F0-9]{24}$/.test(val)) return `ObjectId("${val}")`;
|
||||
return formatMongoShellLiteral(val);
|
||||
}
|
||||
function mongoCollectionExpression(collection: string): string {
|
||||
return `db.getCollection(${JSON.stringify(collection)})`;
|
||||
}
|
||||
const mongoQueryResultSaveHandler = computed<CustomSaveHandler | undefined>(() => {
|
||||
const tab = props.activeTab;
|
||||
const target = tab.mongoEditTarget;
|
||||
if (tab.mode !== "query" || activeEffectiveDatabaseType.value !== "mongodb" || !target || !tab.connectionId || !tab.database || !tab.result) return undefined;
|
||||
if (!tab.result.columns.includes(target.idColumn)) return undefined;
|
||||
|
||||
const save: CustomSaveHandler["save"] = async (changes: MongoQueryGridChanges) => {
|
||||
if (changes.newRows.length > 0 || changes.deletedRows.size > 0) {
|
||||
throw new Error("MongoDB query result editing only supports updating existing rows.");
|
||||
}
|
||||
const idColIdx = changes.columns.indexOf(target.idColumn);
|
||||
if (idColIdx < 0) throw new Error("No _id column");
|
||||
for (const [rowIdx, dirtyCols] of changes.dirtyRows) {
|
||||
const row = changes.rows[rowIdx];
|
||||
const id = row?.[idColIdx];
|
||||
if (id === null || id === undefined || String(id).trim() === "") continue;
|
||||
const updateDoc = buildMongoUpdateDocument(dirtyCols, changes.columns);
|
||||
if (Object.keys(updateDoc).length === 0) continue;
|
||||
await api.mongoUpdateDocument(tab.connectionId, tab.database, target.collection, String(id), JSON.stringify(updateDoc));
|
||||
}
|
||||
};
|
||||
|
||||
const preview: CustomSaveHandler["preview"] = async (changes: MongoQueryGridChanges) => {
|
||||
const idColIdx = changes.columns.indexOf(target.idColumn);
|
||||
if (idColIdx < 0) return [];
|
||||
const stmts: string[] = [];
|
||||
for (const [rowIdx, dirtyCols] of changes.dirtyRows) {
|
||||
const row = changes.rows[rowIdx];
|
||||
const id = row?.[idColIdx];
|
||||
if (id === null || id === undefined || String(id).trim() === "") continue;
|
||||
const updateDoc = buildMongoUpdateDocument(dirtyCols, changes.columns);
|
||||
if (Object.keys(updateDoc).length === 0) continue;
|
||||
stmts.push(`${mongoCollectionExpression(target.collection)}.updateOne({_id: ${mongoIdPreview(id)}}, ${formatMongoShellLiteral(updateDoc)})`);
|
||||
}
|
||||
return stmts;
|
||||
};
|
||||
|
||||
return { save, preview, canInsert: false, canDelete: false, readonlyColumns: [target.idColumn], targetLabel: target.collection };
|
||||
});
|
||||
const resultsPaneOpen = ref(false);
|
||||
const resultsPaneSize = ref(Number(safeLocalStorageGet("dbx-results-pane-size")) || DEFAULT_QUERY_RESULTS_PANE_SIZE);
|
||||
const editorPaneSize = computed(() => (resultsPaneOpen.value ? 100 - resultsPaneSize.value : 100));
|
||||
|
|
@ -821,8 +878,9 @@ defineExpose({ focusSearch, refreshData, handleModRTarget, requestQueryEditorExe
|
|||
:initial-order-by-input="activeTab.orderByInput"
|
||||
:sql="activeTab.lastExecutedSql || activeTab.sql"
|
||||
:loading="activeTab.isExecuting"
|
||||
:editable="!!activeTab.queryAnalysis"
|
||||
:editable="!!activeTab.queryAnalysis || !!mongoQueryResultSaveHandler"
|
||||
:source-columns="activeTab.querySourceColumns"
|
||||
:custom-save-handler="mongoQueryResultSaveHandler"
|
||||
context="results"
|
||||
:database-type="activeEffectiveDatabaseType"
|
||||
:connection-id="activeTab.connectionId"
|
||||
|
|
|
|||
|
|
@ -34,6 +34,10 @@ type GridScrollerRef =
|
|||
export interface CustomSaveHandler {
|
||||
save: (changes: { dirtyRows: Map<number, Map<number, CellValue>>; newRows: CellValue[][]; deletedRows: Set<number>; columns: string[]; rows: CellValue[][] }) => Promise<void>;
|
||||
preview?: (changes: { dirtyRows: Map<number, Map<number, CellValue>>; newRows: CellValue[][]; deletedRows: Set<number>; columns: string[]; rows: CellValue[][] }) => Promise<string[]>;
|
||||
canInsert?: boolean;
|
||||
canDelete?: boolean;
|
||||
readonlyColumns?: string[];
|
||||
targetLabel?: string;
|
||||
}
|
||||
|
||||
export interface UseDataGridEditorOptions {
|
||||
|
|
@ -834,6 +838,17 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
|
|||
options.emit("reload", sql.value, searchText.value, options.currentWhereInput.value, orderByInput.value.trim() || undefined, pageSize.value, (currentPage.value - 1) * pageSize.value);
|
||||
}
|
||||
|
||||
function applyDirtyRowsToResult() {
|
||||
for (const [sourceIndex, changes] of dirtyRows.value) {
|
||||
const row = result.value.rows[sourceIndex];
|
||||
if (row) {
|
||||
for (const [colIdx, value] of changes) {
|
||||
row[colIdx] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function saveChanges() {
|
||||
saveError.value = "";
|
||||
isSaving.value = true;
|
||||
|
|
@ -853,6 +868,7 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
|
|||
isSaving.value = false;
|
||||
return;
|
||||
}
|
||||
applyDirtyRowsToResult();
|
||||
dirtyRows.value.clear();
|
||||
newRows.value = [];
|
||||
deletedRows.value.clear();
|
||||
|
|
@ -931,14 +947,7 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
|
|||
} catch (e) {
|
||||
console.warn("[DBX] failed to record data grid history", e);
|
||||
}
|
||||
for (const [sourceIndex, changes] of dirtyRows.value) {
|
||||
const row = result.value.rows[sourceIndex];
|
||||
if (row) {
|
||||
for (const [colIdx, value] of changes) {
|
||||
row[colIdx] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
applyDirtyRowsToResult();
|
||||
dirtyRows.value.clear();
|
||||
newRows.value = [];
|
||||
deletedRows.value.clear();
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ export interface SavedOpenTab {
|
|||
objectBrowser?: QueryTab["objectBrowser"];
|
||||
objectSource?: QueryTab["objectSource"];
|
||||
tableMeta?: QueryTab["tableMeta"];
|
||||
mongoEditTarget?: QueryTab["mongoEditTarget"];
|
||||
resultEvicted?: boolean;
|
||||
resultCacheKey?: string;
|
||||
resultRuns?: SavedQueryResultRun[];
|
||||
|
|
@ -89,6 +90,7 @@ export function serializeOpenTabs(tabs: QueryTab[]): SavedOpenTab[] {
|
|||
objectBrowser: tab.objectBrowser,
|
||||
objectSource: tab.objectSource,
|
||||
tableMeta: tab.tableMeta,
|
||||
...(tab.mongoEditTarget !== undefined ? { mongoEditTarget: tab.mongoEditTarget } : {}),
|
||||
...(tab.mode !== "data" && tab.resultEvicted ? { resultEvicted: true } : {}),
|
||||
...(tab.mode !== "data" && tab.resultEvicted && tab.resultCacheKey !== undefined ? { resultCacheKey: tab.resultCacheKey } : {}),
|
||||
...(tab.mode === "query" && tab.resultRuns?.length
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export interface TabResultSnapshot {
|
|||
queryAnalysis?: QueryTab["queryAnalysis"];
|
||||
querySourceColumns?: QueryTab["querySourceColumns"];
|
||||
queryEditabilityReason?: QueryTab["queryEditabilityReason"];
|
||||
mongoEditTarget?: QueryTab["mongoEditTarget"];
|
||||
tableMeta?: QueryTab["tableMeta"];
|
||||
resultPageSql?: string;
|
||||
resultPageLimit?: number;
|
||||
|
|
@ -367,6 +368,7 @@ export function buildTabResultSnapshot(tab: QueryTab): TabResultSnapshot | undef
|
|||
queryAnalysis: tab.queryAnalysis ? clonePlain(tab.queryAnalysis) : undefined,
|
||||
querySourceColumns: tab.querySourceColumns ? [...tab.querySourceColumns] : undefined,
|
||||
queryEditabilityReason: tab.queryEditabilityReason,
|
||||
mongoEditTarget: tab.mongoEditTarget ? clonePlain(tab.mongoEditTarget) : undefined,
|
||||
tableMeta: tab.tableMeta ? clonePlain(tab.tableMeta) : undefined,
|
||||
resultPageSql: tab.resultPageSql,
|
||||
resultPageLimit: tab.resultPageLimit,
|
||||
|
|
|
|||
|
|
@ -254,6 +254,7 @@ export const useQueryStore = defineStore("query", () => {
|
|||
tab.queryAnalysis = undefined;
|
||||
tab.querySourceColumns = undefined;
|
||||
tab.queryEditabilityReason = undefined;
|
||||
tab.mongoEditTarget = undefined;
|
||||
if (tab.mode === "query") tab.tableMeta = undefined;
|
||||
tab.resultEvicted = options.evicted ? true : undefined;
|
||||
tab.resultCacheState = options.evicted ? tab.resultCacheState : undefined;
|
||||
|
|
@ -297,6 +298,7 @@ export const useQueryStore = defineStore("query", () => {
|
|||
tab.queryAnalysis = run.queryAnalysis;
|
||||
tab.querySourceColumns = run.querySourceColumns;
|
||||
tab.queryEditabilityReason = run.queryEditabilityReason;
|
||||
tab.mongoEditTarget = run.mongoEditTarget;
|
||||
tab.tableMeta = run.tableMeta;
|
||||
touchResult(tab);
|
||||
}
|
||||
|
|
@ -416,6 +418,7 @@ export const useQueryStore = defineStore("query", () => {
|
|||
queryAnalysis: tab.queryAnalysis,
|
||||
querySourceColumns: tab.querySourceColumns,
|
||||
queryEditabilityReason: tab.queryEditabilityReason,
|
||||
mongoEditTarget: tab.mongoEditTarget,
|
||||
tableMeta: tab.tableMeta,
|
||||
};
|
||||
persistResultRun(tab, run);
|
||||
|
|
@ -463,6 +466,7 @@ export const useQueryStore = defineStore("query", () => {
|
|||
queryAnalysis: tab.queryAnalysis,
|
||||
querySourceColumns: tab.querySourceColumns,
|
||||
queryEditabilityReason: tab.queryEditabilityReason,
|
||||
mongoEditTarget: tab.mongoEditTarget,
|
||||
tableMeta: tab.tableMeta,
|
||||
};
|
||||
persistResultRun(tab, run);
|
||||
|
|
@ -555,6 +559,7 @@ export const useQueryStore = defineStore("query", () => {
|
|||
objectBrowser: t.objectBrowser,
|
||||
objectSource: t.objectSource,
|
||||
tableMeta: t.tableMeta,
|
||||
mongoEditTarget: t.mongoEditTarget,
|
||||
resultEvicted: t.resultEvicted,
|
||||
resultCacheKey: t.resultCacheKey,
|
||||
})),
|
||||
|
|
@ -1222,6 +1227,7 @@ export const useQueryStore = defineStore("query", () => {
|
|||
tab.queryAnalysis = patch.queryAnalysis;
|
||||
tab.querySourceColumns = patch.querySourceColumns;
|
||||
tab.queryEditabilityReason = patch.queryEditabilityReason;
|
||||
tab.mongoEditTarget = undefined;
|
||||
tab.tableMeta = patch.tableMeta;
|
||||
}
|
||||
|
||||
|
|
@ -1540,6 +1546,7 @@ export const useQueryStore = defineStore("query", () => {
|
|||
current.queryAnalysis = undefined;
|
||||
current.querySourceColumns = undefined;
|
||||
current.queryEditabilityReason = undefined;
|
||||
current.mongoEditTarget = undefined;
|
||||
current.tableMeta = undefined;
|
||||
current.resultBaseSql = options?.resultBaseSql ?? sql;
|
||||
current.resultSortedSql = options?.resultSortedSql;
|
||||
|
|
@ -1598,6 +1605,7 @@ export const useQueryStore = defineStore("query", () => {
|
|||
current.queryAnalysis = undefined;
|
||||
current.querySourceColumns = undefined;
|
||||
current.queryEditabilityReason = undefined;
|
||||
current.mongoEditTarget = current.result.columns.includes("_id") ? { collection: mongoFind.collection, idColumn: "_id" } : undefined;
|
||||
current.tableMeta = undefined;
|
||||
current.resultBaseSql = options?.resultBaseSql ?? sql;
|
||||
current.resultSortedSql = options?.resultSortedSql;
|
||||
|
|
@ -1624,6 +1632,7 @@ export const useQueryStore = defineStore("query", () => {
|
|||
current.queryAnalysis = undefined;
|
||||
current.querySourceColumns = undefined;
|
||||
current.queryEditabilityReason = undefined;
|
||||
current.mongoEditTarget = undefined;
|
||||
current.tableMeta = undefined;
|
||||
current.resultBaseSql = options?.resultBaseSql ?? sql;
|
||||
current.resultSortedSql = options?.resultSortedSql;
|
||||
|
|
@ -1657,6 +1666,7 @@ export const useQueryStore = defineStore("query", () => {
|
|||
current.queryAnalysis = undefined;
|
||||
current.querySourceColumns = undefined;
|
||||
current.queryEditabilityReason = undefined;
|
||||
current.mongoEditTarget = undefined;
|
||||
current.tableMeta = undefined;
|
||||
current.resultBaseSql = options?.resultBaseSql ?? sql;
|
||||
current.resultSortedSql = options?.resultSortedSql;
|
||||
|
|
@ -1684,6 +1694,7 @@ export const useQueryStore = defineStore("query", () => {
|
|||
current.queryAnalysis = undefined;
|
||||
current.querySourceColumns = undefined;
|
||||
current.queryEditabilityReason = undefined;
|
||||
current.mongoEditTarget = undefined;
|
||||
current.tableMeta = undefined;
|
||||
current.resultBaseSql = options?.resultBaseSql ?? sql;
|
||||
current.resultSortedSql = options?.resultSortedSql;
|
||||
|
|
@ -1725,6 +1736,7 @@ export const useQueryStore = defineStore("query", () => {
|
|||
current.queryAnalysis = undefined;
|
||||
current.querySourceColumns = undefined;
|
||||
current.queryEditabilityReason = undefined;
|
||||
current.mongoEditTarget = undefined;
|
||||
current.tableMeta = undefined;
|
||||
current.resultBaseSql = options?.resultBaseSql ?? sql;
|
||||
current.resultSortedSql = options?.resultSortedSql;
|
||||
|
|
@ -1746,6 +1758,7 @@ export const useQueryStore = defineStore("query", () => {
|
|||
current.queryAnalysis = undefined;
|
||||
current.querySourceColumns = undefined;
|
||||
current.queryEditabilityReason = undefined;
|
||||
current.mongoEditTarget = undefined;
|
||||
current.tableMeta = undefined;
|
||||
current.resultBaseSql = options?.resultBaseSql ?? sql;
|
||||
current.resultSortedSql = options?.resultSortedSql;
|
||||
|
|
@ -1871,6 +1884,7 @@ export const useQueryStore = defineStore("query", () => {
|
|||
current.queryAnalysis = undefined;
|
||||
current.querySourceColumns = undefined;
|
||||
current.queryEditabilityReason = undefined;
|
||||
current.mongoEditTarget = undefined;
|
||||
if (current.mode !== "data") current.tableMeta = undefined;
|
||||
current.resultBaseSql = queryBaseSql;
|
||||
current.resultSortedSql = options?.resultSortedSql;
|
||||
|
|
@ -2065,6 +2079,7 @@ export const useQueryStore = defineStore("query", () => {
|
|||
tab.queryAnalysis = undefined;
|
||||
tab.querySourceColumns = undefined;
|
||||
tab.queryEditabilityReason = undefined;
|
||||
tab.mongoEditTarget = undefined;
|
||||
syncActiveResultRunFromDisplayed(tab);
|
||||
}
|
||||
|
||||
|
|
@ -2110,6 +2125,7 @@ export const useQueryStore = defineStore("query", () => {
|
|||
tab.queryAnalysis = snapshot.queryAnalysis;
|
||||
tab.querySourceColumns = snapshot.querySourceColumns;
|
||||
tab.queryEditabilityReason = snapshot.queryEditabilityReason;
|
||||
tab.mongoEditTarget = snapshot.mongoEditTarget;
|
||||
tab.tableMeta = snapshot.tableMeta;
|
||||
tab.resultPageSql = snapshot.resultPageSql;
|
||||
tab.resultPageLimit = snapshot.resultPageLimit;
|
||||
|
|
|
|||
|
|
@ -430,6 +430,7 @@ export interface QueryResultRun {
|
|||
queryAnalysis?: QueryTab["queryAnalysis"];
|
||||
querySourceColumns?: QueryTab["querySourceColumns"];
|
||||
queryEditabilityReason?: QueryTab["queryEditabilityReason"];
|
||||
mongoEditTarget?: QueryTab["mongoEditTarget"];
|
||||
tableMeta?: QueryTab["tableMeta"];
|
||||
}
|
||||
|
||||
|
|
@ -652,6 +653,10 @@ export interface QueryTab {
|
|||
};
|
||||
querySourceColumns?: Array<string | undefined>;
|
||||
queryEditabilityReason?: "not-select" | "cte" | "set-operation" | "aggregation" | "external-source" | "complex-source" | "computed-columns" | "no-table" | "no-primary-key" | "primary-key-not-returned" | "aliased-columns" | "metadata-unavailable";
|
||||
mongoEditTarget?: {
|
||||
collection: string;
|
||||
idColumn: "_id";
|
||||
};
|
||||
resultEvicted?: boolean;
|
||||
whereInput?: string;
|
||||
previewSql?: string;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
parseMongoGetIndexesCommand,
|
||||
parseMongoWriteCommand,
|
||||
} from "../../apps/desktop/src/lib/mongoShellCommand.ts";
|
||||
import { buildMongoUpdateDocument as buildMongoDocumentUpdate, formatMongoShellLiteral as formatMongoDocumentShellLiteral } from "../../apps/desktop/src/lib/mongoDocumentValues.ts";
|
||||
|
||||
test("parseMongoFindCommand parses db collection find with an empty JSON filter", () => {
|
||||
assert.deepEqual(parseMongoFindCommand("db.users.find({})"), {
|
||||
|
|
@ -197,3 +198,25 @@ test("mongoDocumentsToQueryResult turns mongo documents into grid rows", () => {
|
|||
assert.equal(result.execution_time_ms, 5);
|
||||
assert.equal(result.truncated, true);
|
||||
});
|
||||
|
||||
test("buildMongoUpdateDocument ignores _id and preserves typed values", () => {
|
||||
const changes = new Map<number, string | number | boolean | null>([
|
||||
[0, "other-id"],
|
||||
[1, "42"],
|
||||
[2, '{"role":"admin"}'],
|
||||
[3, null],
|
||||
]);
|
||||
|
||||
const update = buildMongoDocumentUpdate(changes, ["_id", "age", "profile", "nickname"]);
|
||||
|
||||
assert.deepEqual(update, {
|
||||
$set: {
|
||||
age: 42,
|
||||
profile: { role: "admin" },
|
||||
},
|
||||
$unset: {
|
||||
nickname: "",
|
||||
},
|
||||
});
|
||||
assert.equal(formatMongoDocumentShellLiteral(update), '{"$set":{"age":42,"profile":{"role":"admin"}},"$unset":{"nickname":""}}');
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue