feat(query): 优化多条查询语句结果展示与语句匹配问题 (#2697)
This commit is contained in:
parent
095cb867a5
commit
ff8e53d47b
|
|
@ -54,7 +54,7 @@ import { useConnectionStore } from "@/stores/connectionStore";
|
|||
import { TABLE_FONT_SIZE_MAX, TABLE_FONT_SIZE_MIN, useSettingsStore, type DataGridSearchMode } from "@/stores/settingsStore";
|
||||
import { useToast } from "@/composables/useToast";
|
||||
import { canCancelQueryExecution, queryExecutionLabelKey } from "@/lib/sql/queryExecutionState";
|
||||
import { databaseDisplayNameForTab, executionSummaryItems, nextExecutionSummaryView, resultGridCacheKey, resultRunItems, tabularResultItems } from "@/lib/tabs/tabPresentation";
|
||||
import { databaseDisplayNameForTab, executionSummaryItems, nextExecutionSummaryView, resultGridCacheKey, resultRunItems, resultSqlForGrid, tabularResultItems } from "@/lib/tabs/tabPresentation";
|
||||
import { defaultQueryResultArchiveFileName } from "@/lib/query/queryResultArchive";
|
||||
import { saveQueryResultArchiveFile } from "@/lib/query/queryResultArchiveFile";
|
||||
import { isTableDataEditable } from "@/lib/table/tableEditing";
|
||||
|
|
@ -295,6 +295,7 @@ const allResultExportSheets = computed(() =>
|
|||
const resultRuns = computed(() => resultRunItems(props.activeTab));
|
||||
const activeResultRunItem = computed(() => resultRuns.value.find((run) => run.active));
|
||||
const activeResultGridCacheKey = computed(() => resultGridCacheKey(props.activeTab));
|
||||
const activeResultSql = computed(() => resultSqlForGrid(props.activeTab));
|
||||
const resultArchiveExporting = ref(false);
|
||||
const canExportResultArchive = computed(() => props.activeTab.mode === "query" && (!!props.activeTab.result || !!props.activeTab.results?.length || !!props.activeTab.resultRuns?.length));
|
||||
const resultAutoSave = computed(() => props.activeTab.resultAutoSave === true);
|
||||
|
|
@ -775,7 +776,7 @@ defineExpose({ focusSearch, refreshData, handleModRTarget, requestQueryEditorExe
|
|||
size="sm"
|
||||
:variant="activeOutputView === 'result' && (activeTab.activeResultIndex ?? 0) === item.index ? 'default' : 'ghost'"
|
||||
class="h-6 max-w-48 shrink-0 overflow-hidden text-ellipsis whitespace-nowrap px-2 text-xs"
|
||||
:title="item.label || t('tabs.resultN', { n: item.n })"
|
||||
:title="item.title || item.label || t('tabs.resultN', { n: item.n })"
|
||||
@click="
|
||||
queryStore.setActiveResultIndex(activeTab.id, item.index);
|
||||
emit('update:activeOutputView', 'result');
|
||||
|
|
@ -994,7 +995,7 @@ defineExpose({ focusSearch, refreshData, handleModRTarget, requestQueryEditorExe
|
|||
:sort-direction="activeTab.resultSortDirection"
|
||||
:sort-mode="activeTab.resultSortMode"
|
||||
:initial-order-by-input="activeTab.orderByInput"
|
||||
:sql="activeTab.lastExecutedSql || activeTab.sql"
|
||||
:sql="activeResultSql"
|
||||
:loading="activeTab.isExecuting"
|
||||
:editable="!!activeTab.queryAnalysis || !!mongoQueryResultSaveHandler"
|
||||
:source-columns="activeTab.querySourceColumns"
|
||||
|
|
|
|||
|
|
@ -138,12 +138,24 @@ export function tabTooltipLines(tab: QueryTab, t: Translate): { label: string; v
|
|||
return lines;
|
||||
}
|
||||
|
||||
export function tabularResultItems(results: QueryResult[] | undefined): { result: QueryResult; index: number; n: number; label?: string }[] {
|
||||
export function queryResultStatementLabel(result: Pick<QueryResult, "sourceLabel" | "sourceStatement">, maxLength = 48): string | undefined {
|
||||
if (result.sourceLabel) return result.sourceLabel;
|
||||
const statement = result.sourceStatement?.replace(/\s+/g, " ").trim();
|
||||
if (!statement) return undefined;
|
||||
if (statement.length <= maxLength) return statement;
|
||||
return `${statement.slice(0, Math.max(0, maxLength - 3)).trimEnd()}...`;
|
||||
}
|
||||
|
||||
export function resultSqlForGrid(tab: Pick<QueryTab, "result" | "resultBaseSql" | "lastExecutedSql" | "sql">): string {
|
||||
return tab.result?.sourceStatement || tab.resultBaseSql || tab.lastExecutedSql || tab.sql;
|
||||
}
|
||||
|
||||
export function tabularResultItems(results: QueryResult[] | undefined): { result: QueryResult; index: number; n: number; label?: string; title?: string }[] {
|
||||
if (!results) return [];
|
||||
return results
|
||||
.map((result, index) => ({ result, index }))
|
||||
.filter((item) => item.result.columns.length > 0)
|
||||
.map((item, ordinal) => ({ ...item, n: ordinal + 1, label: item.result.sourceLabel }));
|
||||
.map((item, ordinal) => ({ ...item, n: ordinal + 1, label: queryResultStatementLabel(item.result), title: item.result.sourceStatement }));
|
||||
}
|
||||
|
||||
export function activeResultRun(tab: Pick<QueryTab, "resultRuns" | "activeResultRunId">) {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import {
|
|||
mongoUseToQueryResult,
|
||||
mongoVersionToQueryResult,
|
||||
mongoWriteToQueryResult,
|
||||
splitMongoCommands,
|
||||
splitMongoCommandRanges,
|
||||
type MongoAggregateSafetyOptions,
|
||||
} from "@/lib/mongo/mongoShellCommand";
|
||||
import { redisCommandResultToQueryResult } from "@/lib/redis/redisQueryResult";
|
||||
|
|
@ -124,15 +124,19 @@ function annotateQueryResultSources(results: QueryResult[], sql: string, databas
|
|||
let statementIndex = 0;
|
||||
for (const result of results) {
|
||||
const statement = statements[statementIndex++];
|
||||
if (result.columns.length === 0) continue;
|
||||
if (!statement) continue;
|
||||
result.sourceStatement = statement.sql;
|
||||
const label = queryResultSourceLabel(statement.sql, database);
|
||||
if (label) result.sourceLabel = label;
|
||||
annotateQueryResultSource(result, statement.sql, database);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
function annotateQueryResultSource(result: QueryResult, sourceStatement: string, database?: string): QueryResult {
|
||||
result.sourceStatement = sourceStatement;
|
||||
const label = queryResultSourceLabel(sourceStatement, database);
|
||||
if (label) result.sourceLabel = label;
|
||||
return result;
|
||||
}
|
||||
|
||||
async function withFrontendQueryTimeout<T>(promise: Promise<T>, timeoutSecs: number, message: string): Promise<T> {
|
||||
if (timeoutSecs === 0) return promise;
|
||||
|
||||
|
|
@ -1994,7 +1998,7 @@ export const useQueryStore = defineStore("query", () => {
|
|||
try {
|
||||
const connStore = useConnectionStore();
|
||||
let conn = connStore.getConfig(tab.connectionId);
|
||||
const parsedMongoCommands = conn?.db_type === "mongodb" ? splitMongoCommands(sql) : undefined;
|
||||
const parsedMongoCommands = conn?.db_type === "mongodb" ? splitMongoCommandRanges(sql) : undefined;
|
||||
let mongoCommands = parsedMongoCommands ?? [];
|
||||
const mongoNeedsConnection = mongoCommands.some(({ command }) => command.kind !== "use");
|
||||
|
||||
|
|
@ -2009,7 +2013,7 @@ export const useQueryStore = defineStore("query", () => {
|
|||
}
|
||||
conn = connStore.getConfig(tab.connectionId);
|
||||
if (parsedMongoCommands === undefined && conn?.db_type === "mongodb") {
|
||||
mongoCommands = splitMongoCommands(sql);
|
||||
mongoCommands = splitMongoCommandRanges(sql);
|
||||
}
|
||||
const effectiveDbType = effectiveDatabaseTypeForConnection(conn);
|
||||
const useAgentCursor = usesAgentCursorForQuery(conn?.db_type);
|
||||
|
|
@ -2036,7 +2040,7 @@ export const useQueryStore = defineStore("query", () => {
|
|||
for (const command of commands) {
|
||||
try {
|
||||
const result = await api.redisExecuteCommand(tab.connectionId, currentDb, command, skipSafety);
|
||||
allResults.push(markQueryResultRowsRaw(redisCommandResultToQueryResult(result.value, performance.now() - startedAt, result.command)));
|
||||
allResults.push(markQueryResultRowsRaw(annotateQueryResultSource(redisCommandResultToQueryResult(result.value, performance.now() - startedAt, result.command), command)));
|
||||
// Track db switches from SELECT N so later commands in the same batch run on the right db.
|
||||
currentDb = nextRedisCommandDb(currentDb, command, result.value);
|
||||
// Write commands (SET/DEL/...) mutate the key set — drop the cached key-name completion
|
||||
|
|
@ -2046,7 +2050,7 @@ export const useQueryStore = defineStore("query", () => {
|
|||
connStore.invalidateCompletionCache(tab.connectionId, String(currentDb));
|
||||
}
|
||||
} catch (e: any) {
|
||||
allResults.push({ columns: ["Error"], rows: [[e?.message ?? String(e)]], affected_rows: 0, execution_time_ms: 0 });
|
||||
allResults.push(annotateQueryResultSource({ columns: ["Error"], rows: [[e?.message ?? String(e)]], affected_rows: 0, execution_time_ms: 0 }, command));
|
||||
}
|
||||
}
|
||||
console.info("[DBX][executeTabSql:redis:done]", { traceId, commandCount: commands.length, elapsed: elapsed() });
|
||||
|
|
@ -2099,13 +2103,14 @@ export const useQueryStore = defineStore("query", () => {
|
|||
|
||||
for (const parsedCommand of mongoCommands) {
|
||||
const mongoCommand = parsedCommand.command;
|
||||
const sourceStatement = parsedCommand.text;
|
||||
const commandStartedAt = performance.now();
|
||||
try {
|
||||
switch (mongoCommand.kind) {
|
||||
case "find": {
|
||||
console.info("[DBX][executeTabSql:mongo-find:start]", { traceId, collection: mongoCommand.collection, database: currentDatabase });
|
||||
const result = await api.mongoFindDocuments(tab.connectionId, currentDatabase, mongoCommand.collection, mongoCommand.skip, mongoCommand.limit, mongoCommand.filter, mongoCommand.projection, mongoCommand.sort, executionId);
|
||||
const queryResult = markQueryResultRowsRaw(mongoDocumentsToQueryResult(result.documents, performance.now() - commandStartedAt, result.total));
|
||||
const queryResult = markQueryResultRowsRaw(annotateQueryResultSource(mongoDocumentsToQueryResult(result.documents, performance.now() - commandStartedAt, result.total), sourceStatement));
|
||||
allResults.push(queryResult);
|
||||
mongoEditTarget = mongoCommands.length === 1 && queryResult.columns.includes("_id") ? { collection: mongoCommand.collection, idColumn: "_id" } : undefined;
|
||||
console.info("[DBX][executeTabSql:mongo-find:done]", {
|
||||
|
|
@ -2121,7 +2126,7 @@ export const useQueryStore = defineStore("query", () => {
|
|||
case "version": {
|
||||
console.info("[DBX][executeTabSql:mongo-version:start]", { traceId, database: currentDatabase });
|
||||
const version = await api.mongoServerVersion(tab.connectionId, currentDatabase, executionId);
|
||||
allResults.push(markQueryResultRowsRaw(mongoVersionToQueryResult(version, performance.now() - commandStartedAt)));
|
||||
allResults.push(markQueryResultRowsRaw(annotateQueryResultSource(mongoVersionToQueryResult(version, performance.now() - commandStartedAt), sourceStatement)));
|
||||
mongoEditTarget = undefined;
|
||||
console.info("[DBX][executeTabSql:mongo-version:done]", {
|
||||
traceId,
|
||||
|
|
@ -2134,7 +2139,7 @@ export const useQueryStore = defineStore("query", () => {
|
|||
case "countDocuments": {
|
||||
console.info("[DBX][executeTabSql:mongo-count:start]", { traceId, collection: mongoCommand.collection, database: currentDatabase });
|
||||
const result = await api.mongoFindDocuments(tab.connectionId, currentDatabase, mongoCommand.collection, 0, 1, mongoCommand.filter, undefined, undefined, executionId);
|
||||
allResults.push(markQueryResultRowsRaw(mongoCountToQueryResult(result.total, performance.now() - commandStartedAt)));
|
||||
allResults.push(markQueryResultRowsRaw(annotateQueryResultSource(mongoCountToQueryResult(result.total, performance.now() - commandStartedAt), sourceStatement)));
|
||||
mongoEditTarget = undefined;
|
||||
console.info("[DBX][executeTabSql:mongo-count:done]", {
|
||||
traceId,
|
||||
|
|
@ -2153,7 +2158,7 @@ export const useQueryStore = defineStore("query", () => {
|
|||
console.info("[DBX][executeTabSql:mongo-aggregate:start]", { traceId, collection: mongoCommand.collection, database: currentDatabase });
|
||||
const aggregateMaxRows = normalizeResultPageSize(pageLimit ?? options?.pagination?.limit ?? settingsStore.editorSettings.pageSize);
|
||||
const result = await api.mongoAggregateDocuments(tab.connectionId, currentDatabase, mongoCommand.collection, mongoCommand.pipeline, aggregateMaxRows, executionId);
|
||||
allResults.push(markQueryResultRowsRaw(mongoDocumentsToQueryResult(result.documents, performance.now() - commandStartedAt, result.total)));
|
||||
allResults.push(markQueryResultRowsRaw(annotateQueryResultSource(mongoDocumentsToQueryResult(result.documents, performance.now() - commandStartedAt, result.total), sourceStatement)));
|
||||
mongoEditTarget = undefined;
|
||||
console.info("[DBX][executeTabSql:mongo-aggregate:done]", {
|
||||
traceId,
|
||||
|
|
@ -2168,7 +2173,7 @@ export const useQueryStore = defineStore("query", () => {
|
|||
case "getIndexes": {
|
||||
console.info("[DBX][executeTabSql:mongo-indexes:start]", { traceId, collection: mongoCommand.collection, database: currentDatabase });
|
||||
const indexes = await api.listIndexes(tab.connectionId, currentDatabase, "", mongoCommand.collection);
|
||||
allResults.push(markQueryResultRowsRaw(mongoIndexesToQueryResult(indexes, performance.now() - commandStartedAt)));
|
||||
allResults.push(markQueryResultRowsRaw(annotateQueryResultSource(mongoIndexesToQueryResult(indexes, performance.now() - commandStartedAt), sourceStatement)));
|
||||
mongoEditTarget = undefined;
|
||||
console.info("[DBX][executeTabSql:mongo-indexes:done]", {
|
||||
traceId,
|
||||
|
|
@ -2187,7 +2192,7 @@ export const useQueryStore = defineStore("query", () => {
|
|||
database: currentDatabase,
|
||||
});
|
||||
const stats = await api.mongoCollectionStats(tab.connectionId, currentDatabase, mongoCommand.collection, mongoCommand.scale, executionId);
|
||||
allResults.push(markQueryResultRowsRaw(mongoCollectionStatsToQueryResult(mongoCommand.metric, stats as unknown as Record<string, unknown>, performance.now() - commandStartedAt)));
|
||||
allResults.push(markQueryResultRowsRaw(annotateQueryResultSource(mongoCollectionStatsToQueryResult(mongoCommand.metric, stats as unknown as Record<string, unknown>, performance.now() - commandStartedAt), sourceStatement)));
|
||||
mongoEditTarget = undefined;
|
||||
console.info("[DBX][executeTabSql:mongo-collection-stats:done]", {
|
||||
traceId,
|
||||
|
|
@ -2217,19 +2222,19 @@ export const useQueryStore = defineStore("query", () => {
|
|||
mongoEditTarget = undefined;
|
||||
if (mongoCommand.kind === "insert") {
|
||||
const result = await api.mongoInsertDocuments(tab.connectionId, currentDatabase, mongoCommand.collection, mongoCommand.docsJson);
|
||||
allResults.push(markQueryResultRowsRaw(mongoWriteToQueryResult(result.affected_rows, performance.now() - commandStartedAt)));
|
||||
allResults.push(markQueryResultRowsRaw(annotateQueryResultSource(mongoWriteToQueryResult(result.affected_rows, performance.now() - commandStartedAt), sourceStatement)));
|
||||
} else if (mongoCommand.kind === "update") {
|
||||
const result = await api.mongoUpdateDocuments(tab.connectionId, currentDatabase, mongoCommand.collection, mongoCommand.filter, mongoCommand.update, mongoCommand.many);
|
||||
allResults.push(markQueryResultRowsRaw(mongoWriteToQueryResult(result.affected_rows, performance.now() - commandStartedAt)));
|
||||
allResults.push(markQueryResultRowsRaw(annotateQueryResultSource(mongoWriteToQueryResult(result.affected_rows, performance.now() - commandStartedAt), sourceStatement)));
|
||||
} else if (mongoCommand.kind === "createIndex") {
|
||||
const result = await api.mongoCreateIndex(tab.connectionId, currentDatabase, mongoCommand.collection, mongoCommand.keys, mongoCommand.options);
|
||||
allResults.push(markQueryResultRowsRaw(mongoCreateIndexToQueryResult(result.name, performance.now() - commandStartedAt)));
|
||||
allResults.push(markQueryResultRowsRaw(annotateQueryResultSource(mongoCreateIndexToQueryResult(result.name, performance.now() - commandStartedAt), sourceStatement)));
|
||||
} else if (mongoCommand.kind === "dropIndex" || mongoCommand.kind === "dropIndexes") {
|
||||
const result = await api.mongoDropIndexes(tab.connectionId, currentDatabase, mongoCommand.collection, mongoCommand.kind === "dropIndex" ? mongoCommand.index : mongoCommand.indexes, mongoCommand.kind === "dropIndex");
|
||||
allResults.push(markQueryResultRowsRaw(mongoDroppedIndexesToQueryResult(result.dropped_names, performance.now() - commandStartedAt)));
|
||||
allResults.push(markQueryResultRowsRaw(annotateQueryResultSource(mongoDroppedIndexesToQueryResult(result.dropped_names, performance.now() - commandStartedAt), sourceStatement)));
|
||||
} else {
|
||||
const result = await api.mongoDeleteDocuments(tab.connectionId, currentDatabase, mongoCommand.collection, mongoCommand.filter, mongoCommand.many);
|
||||
allResults.push(markQueryResultRowsRaw(mongoWriteToQueryResult(result.affected_rows, performance.now() - commandStartedAt)));
|
||||
allResults.push(markQueryResultRowsRaw(annotateQueryResultSource(mongoWriteToQueryResult(result.affected_rows, performance.now() - commandStartedAt), sourceStatement)));
|
||||
}
|
||||
console.info("[DBX][executeTabSql:mongo-write:done]", {
|
||||
traceId,
|
||||
|
|
@ -2242,7 +2247,7 @@ export const useQueryStore = defineStore("query", () => {
|
|||
}
|
||||
case "use": {
|
||||
currentDatabase = mongoCommand.database;
|
||||
allResults.push(markQueryResultRowsRaw(mongoUseToQueryResult(currentDatabase, performance.now() - commandStartedAt)));
|
||||
allResults.push(markQueryResultRowsRaw(annotateQueryResultSource(mongoUseToQueryResult(currentDatabase, performance.now() - commandStartedAt), sourceStatement)));
|
||||
mongoEditTarget = undefined;
|
||||
console.info("[DBX][executeTabSql:mongo-use:done]", {
|
||||
traceId,
|
||||
|
|
@ -2255,7 +2260,7 @@ export const useQueryStore = defineStore("query", () => {
|
|||
} catch (error: any) {
|
||||
// Surface per-command failures inline and continue collecting results
|
||||
// for the rest of the batch, matching the grouped-result UX.
|
||||
allResults.push(toErrorResult(error));
|
||||
allResults.push(annotateQueryResultSource(toErrorResult(error), sourceStatement));
|
||||
mongoEditTarget = undefined;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { test } from "vitest";
|
|||
import { createPinia, setActivePinia } from "pinia";
|
||||
import { isReactive } from "vue";
|
||||
import { decodeQueryResultArchive } from "../../apps/desktop/src/lib/query/queryResultArchive.ts";
|
||||
import { resultSqlForGrid } from "../../apps/desktop/src/lib/tabs/tabPresentation.ts";
|
||||
import { useConnectionStore } from "../../apps/desktop/src/stores/connectionStore.ts";
|
||||
import { useQueryStore } from "../../apps/desktop/src/stores/queryStore.ts";
|
||||
import { useSettingsStore } from "../../apps/desktop/src/stores/settingsStore.ts";
|
||||
|
|
@ -2487,6 +2488,101 @@ test("mongo multi-command execution runs writes sequentially and keeps grouped r
|
|||
assert.equal(tab?.results?.length, 2);
|
||||
assert.equal(tab?.activeResultIndex, 0);
|
||||
assert.equal(tab?.result?.affected_rows, 1);
|
||||
assert.deepEqual(
|
||||
tab?.results?.map((result) => result.sourceStatement),
|
||||
['db.users.insertOne({ name: "Ada" })', 'db.users.insertOne({ name: "Grace" })'],
|
||||
);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
restoreStorage();
|
||||
}
|
||||
});
|
||||
|
||||
test("redis multi-command execution records source statements for each result", async () => {
|
||||
const restoreStorage = installMemoryStorage();
|
||||
setActivePinia(createPinia());
|
||||
const connectionStore = useConnectionStore();
|
||||
const store = useQueryStore();
|
||||
const originalFetch = globalThis.fetch;
|
||||
const commandBodies: any[] = [];
|
||||
|
||||
connectionStore.addEphemeralConnection({
|
||||
...conn("redis-1"),
|
||||
db_type: "redis",
|
||||
port: 6379,
|
||||
});
|
||||
|
||||
globalThis.fetch = withConnectionHealthMock(async (input, init) => {
|
||||
const url = String(input);
|
||||
if (url === "/api/redis/execute-command") {
|
||||
const body = JSON.parse(String(init?.body ?? "{}"));
|
||||
commandBodies.push(body);
|
||||
if (body.command === "BAD") return new Response("bad command", { status: 500 });
|
||||
return new Response(JSON.stringify({ command: body.command, safety: "allowed", value: body.command === "GET user:1" ? "Ada" : "OK" }), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
return new Response("unexpected request", { status: 500 });
|
||||
});
|
||||
|
||||
try {
|
||||
const tabId = store.createTab("redis-1", "0", "Redis", "query", "");
|
||||
await store.executeTabSql(tabId, "GET user:1\nBAD\nPING");
|
||||
const tab = store.tabs.find((item) => item.id === tabId);
|
||||
|
||||
assert.deepEqual(
|
||||
commandBodies.map((body) => body.command),
|
||||
["GET user:1", "BAD", "PING"],
|
||||
);
|
||||
assert.deepEqual(
|
||||
tab?.results?.map((result) => result.sourceStatement),
|
||||
["GET user:1", "BAD", "PING"],
|
||||
);
|
||||
assert.deepEqual(tab?.results?.[1]?.columns, ["Error"]);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
restoreStorage();
|
||||
}
|
||||
});
|
||||
|
||||
test("mongo multi-command execution records source statements for error results", async () => {
|
||||
const restoreStorage = installMemoryStorage();
|
||||
setActivePinia(createPinia());
|
||||
const connectionStore = useConnectionStore();
|
||||
const store = useQueryStore();
|
||||
const originalFetch = globalThis.fetch;
|
||||
let insertCount = 0;
|
||||
|
||||
connectionStore.addEphemeralConnection({
|
||||
...conn("mongo-1"),
|
||||
db_type: "mongodb",
|
||||
port: 27017,
|
||||
});
|
||||
|
||||
globalThis.fetch = withConnectionHealthMock(async (input) => {
|
||||
const url = String(input);
|
||||
if (url === "/api/mongo/insert-documents") {
|
||||
insertCount += 1;
|
||||
if (insertCount === 2) return new Response("duplicate key", { status: 500 });
|
||||
return new Response(JSON.stringify({ affected_rows: 1 }), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
return new Response("unexpected request", { status: 500 });
|
||||
});
|
||||
|
||||
try {
|
||||
const tabId = store.createTab("mongo-1", "accounting", "Query", "query", "");
|
||||
await store.executeTabSql(tabId, 'db.users.insertOne({ name: "Ada" });\ndb.users.insertOne({ name: "Ada" });');
|
||||
const tab = store.tabs.find((item) => item.id === tabId);
|
||||
|
||||
assert.deepEqual(
|
||||
tab?.results?.map((result) => result.sourceStatement),
|
||||
['db.users.insertOne({ name: "Ada" })', 'db.users.insertOne({ name: "Ada" })'],
|
||||
);
|
||||
assert.deepEqual(tab?.results?.[1]?.columns, ["Error"]);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
restoreStorage();
|
||||
|
|
@ -3453,6 +3549,10 @@ test("query results keep readable table source labels with active database conte
|
|||
tab?.results?.map((result) => result.sourceStatement),
|
||||
["select * from users", "select * from orders"],
|
||||
);
|
||||
assert.equal(resultSqlForGrid(tab!), "select * from users");
|
||||
store.setActiveResultIndex(tabId, 1);
|
||||
tab = store.tabs.find((item) => item.id === tabId);
|
||||
assert.equal(resultSqlForGrid(tab!), "select * from orders");
|
||||
|
||||
await store.executeTabSql(defaultDatabaseTabId, "SELECT *\nFROM apis AS ap\nLIMIT 10;\n\nSELECT *\nFROM menus AS mn\nLIMIT 10;");
|
||||
tab = store.tabs.find((item) => item.id === defaultDatabaseTabId);
|
||||
|
|
@ -3473,6 +3573,7 @@ test("query results keep readable table source labels with active database conte
|
|||
await store.executeTabSql(tabId, "update users set active = true; select * from users");
|
||||
tab = store.tabs.find((item) => item.id === tabId);
|
||||
assert.equal(tab?.results?.[0]?.sourceLabel, undefined);
|
||||
assert.equal(tab?.results?.[0]?.sourceStatement, "update users set active = true");
|
||||
assert.equal(tab?.results?.[1]?.sourceLabel, "db.users");
|
||||
assert.equal(tab?.results?.[1]?.sourceStatement, "select * from users");
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -1,17 +1,7 @@
|
|||
import { strict as assert } from "node:assert";
|
||||
import { test } from "vitest";
|
||||
import { createPinia, setActivePinia } from "pinia";
|
||||
import {
|
||||
activeResultRun,
|
||||
databaseDisplayNameForTab,
|
||||
executionSummaryItems,
|
||||
nextExecutionSummaryView,
|
||||
resultGridCacheKey,
|
||||
resultRunItems,
|
||||
tabDisplayTitle,
|
||||
tabModeLabel,
|
||||
tabularResultItems,
|
||||
} from "../../apps/desktop/src/lib/tabs/tabPresentation.ts";
|
||||
import { activeResultRun, databaseDisplayNameForTab, executionSummaryItems, nextExecutionSummaryView, resultGridCacheKey, resultRunItems, resultSqlForGrid, tabDisplayTitle, tabModeLabel, tabularResultItems } from "../../apps/desktop/src/lib/tabs/tabPresentation.ts";
|
||||
import { useConnectionStore } from "../../apps/desktop/src/stores/connectionStore.ts";
|
||||
import type { ConnectionConfig, QueryResult, QueryTab } from "../../apps/desktop/src/types/database.ts";
|
||||
|
||||
|
|
@ -189,16 +179,32 @@ test("tabular result items hide statement results without returned columns", ()
|
|||
});
|
||||
|
||||
test("tabular result items expose source labels when available", () => {
|
||||
const results = [result([]), result(["id"], { sourceLabel: "public.users" }), result(["name"])];
|
||||
const results = [result([]), result(["id"], { sourceLabel: "public.users", sourceStatement: "select * from public.users" }), result(["name"], { sourceStatement: "select id, name, email, created_at from users where active = true order by created_at desc" })];
|
||||
|
||||
assert.deepEqual(
|
||||
tabularResultItems(results).map((item) => ({ index: item.index, n: item.n, label: item.label })),
|
||||
tabularResultItems(results).map((item) => ({ index: item.index, n: item.n, label: item.label, title: item.title })),
|
||||
[
|
||||
{ index: 1, n: 1, label: "public.users" },
|
||||
{ index: 2, n: 2, label: undefined },
|
||||
{ index: 1, n: 1, label: "public.users", title: "select * from public.users" },
|
||||
{ index: 2, n: 2, label: "select id, name, email, created_at from users...", title: "select id, name, email, created_at from users where active = true order by created_at desc" },
|
||||
],
|
||||
);
|
||||
assert.deepEqual(tabularResultItems([result(["id"], { sourceLabel: "db.users" })]).map((item) => item.label), ["db.users"]);
|
||||
assert.deepEqual(
|
||||
tabularResultItems([result(["id"], { sourceLabel: "db.users" })]).map((item) => item.label),
|
||||
["db.users"],
|
||||
);
|
||||
});
|
||||
|
||||
test("resultSqlForGrid prefers the active result source statement", () => {
|
||||
const tab = queryTab({
|
||||
sql: "select * from users; select * from orders",
|
||||
lastExecutedSql: "select * from users; select * from orders",
|
||||
resultBaseSql: "select * from users; select * from orders",
|
||||
result: result(["id"], { sourceStatement: "select * from orders" }),
|
||||
});
|
||||
|
||||
assert.equal(resultSqlForGrid(tab), "select * from orders");
|
||||
assert.equal(resultSqlForGrid(queryTab({ sql: "select 1", resultBaseSql: "select 2" })), "select 2");
|
||||
assert.equal(resultSqlForGrid(queryTab({ sql: "select 1", lastExecutedSql: "select 3" })), "select 3");
|
||||
});
|
||||
|
||||
test("result run items expose ordered labels and active state", () => {
|
||||
|
|
@ -229,7 +235,10 @@ test("result run items expose ordered labels and active state", () => {
|
|||
{ id: "run-2", title: "Run 2", sequence: 2, active: true },
|
||||
]);
|
||||
assert.equal(activeResultRun(tab)?.id, "run-2");
|
||||
assert.deepEqual(resultRunItems(queryTab()).map((item) => item.title), []);
|
||||
assert.deepEqual(
|
||||
resultRunItems(queryTab()).map((item) => item.title),
|
||||
[],
|
||||
);
|
||||
});
|
||||
|
||||
test("result grid cache key includes result run id and statement result index", () => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue