feat(mongodb): support db.getSiblingDB()
This commit is contained in:
1 parent
b44e1080a8
commit
f1e0f54a2b
9 files changed
+262
-8
No files matched your search
@@ -123,6 +123,27 @@ describe("queryStore MongoDB execution summary", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("executes the authoritative inDatabase wrapper against the sibling database and restores the session database", async () => {
|
||||
mocks.mongoFindDocuments.mockResolvedValue({ documents: [{ _id: "1" }], extended_documents: [], total: 1, total_is_exact: true });
|
||||
const { useQueryStore } = await import("@/stores/queryStore");
|
||||
const store = useQueryStore();
|
||||
const tabId = store.createTab("mongo-1", "app", "Query");
|
||||
|
||||
await store.executeTabSql(tabId, 'db.getSiblingDB("reports").events.find();\ndb.users.find()');
|
||||
|
||||
expect(mocks.mongoFindDocuments).toHaveBeenNthCalledWith(1, "mongo-1", "reports", "events", expect.any(Number), expect.any(Number), "{}", undefined, undefined, undefined, expect.any(String));
|
||||
expect(mocks.mongoFindDocuments).toHaveBeenNthCalledWith(2, "mongo-1", "app", "users", expect.any(Number), expect.any(Number), "{}", undefined, undefined, undefined, expect.any(String));
|
||||
const tab = store.tabs.find((item) => item.id === tabId)!;
|
||||
expect(tab.batchSqlExecution).toMatchObject({
|
||||
total: 2,
|
||||
completed: 2,
|
||||
items: [
|
||||
{ status: "success", statementIndex: 0 },
|
||||
{ status: "success", statementIndex: 1 },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("forwards the active execution ID to a pending explain and its cancellation", async () => {
|
||||
let rejectExplain!: (reason: Error) => void;
|
||||
mocks.mongoExplainFind.mockImplementation(
|
||||
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
mongoScalarToQueryResult,
|
||||
mongoWriteToQueryResult,
|
||||
splitMongoCommandRanges,
|
||||
splitSiblingDbPrefix,
|
||||
type MongoAggregateSafetyOptions,
|
||||
} from "@/lib/mongo/mongoShellCommand";
|
||||
import { refreshLoadedMongoIndexes } from "@/lib/mongo/mongoIndexMetadata";
|
||||
@@ -6494,6 +6495,13 @@ export const useQueryStore = defineStore("query", () => {
|
||||
|
||||
for (const parsedCommand of mongoCommands) {
|
||||
let mongoCommand = parsedCommand.command;
|
||||
// db.getSiblingDB("x").<command>: target that database for this command only.
|
||||
const sessionDatabase = currentDatabase;
|
||||
const targetsSiblingDatabase = mongoCommand.kind === "inDatabase";
|
||||
if (mongoCommand.kind === "inDatabase") {
|
||||
currentDatabase = mongoCommand.database;
|
||||
mongoCommand = mongoCommand.command;
|
||||
}
|
||||
const sourceStatement = parsedCommand.text;
|
||||
const sourceRange = options?.sourceOffset === undefined ? undefined : { from: options.sourceOffset + parsedCommand.from, to: options.sourceOffset + parsedCommand.to };
|
||||
const commandStartedAt = performance.now();
|
||||
@@ -6508,10 +6516,21 @@ export const useQueryStore = defineStore("query", () => {
|
||||
// The frontend parser remains responsible for editor ranges, while
|
||||
// dbx-core is authoritative for command semantics at execution time.
|
||||
mongoCommand = await api.mongoParseShellCommand(sourceStatement);
|
||||
// The authoritative parse keeps the `db.getSiblingDB("x").` wrapper;
|
||||
// re-apply the same one-command database override before dispatch.
|
||||
if (mongoCommand.kind === "inDatabase") {
|
||||
currentDatabase = mongoCommand.database;
|
||||
mongoCommand = mongoCommand.command;
|
||||
}
|
||||
switch (mongoCommand.kind) {
|
||||
case "find": {
|
||||
queryExecutionLog("info", "mongo-find:start", { traceId, collection: mongoCommand.collection, database: currentDatabase });
|
||||
const pagePlan = planMongoFindPagination(sourceStatement, mongoCommand, options?.pagination?.offset ?? 0, normalizeResultPageSize(options?.pagination?.limit ?? settingsStore.editorSettings.pageSize));
|
||||
// Pagination planning parses `db.<collection>.find(...)` directly;
|
||||
// strip a `db.getSiblingDB("x")` wrapper so the plan reflects the
|
||||
// wrapped command's own skip/limit chain.
|
||||
const siblingPrefix = splitSiblingDbPrefix(sourceStatement);
|
||||
const paginationSource = siblingPrefix ? `db${siblingPrefix.rest}` : sourceStatement;
|
||||
const pagePlan = planMongoFindPagination(paginationSource, mongoCommand, options?.pagination?.offset ?? 0, normalizeResultPageSize(options?.pagination?.limit ?? settingsStore.editorSettings.pageSize));
|
||||
if (!pagePlan) throw new Error(describeMongoCommandParseFailure(sourceStatement));
|
||||
// A stale request can point past an explicit .limit() bound. Keep
|
||||
// the backend call bounded so limit(0) cannot become unbounded.
|
||||
@@ -6820,6 +6839,9 @@ export const useQueryStore = defineStore("query", () => {
|
||||
// for the rest of the batch, matching the grouped-result UX.
|
||||
allResults.push(annotateMongoResult(toErrorResult(error)));
|
||||
mongoEditTarget = undefined;
|
||||
} finally {
|
||||
// A sibling-database command does not change the session database the way `use` does.
|
||||
if (targetsSiblingDatabase) currentDatabase = sessionDatabase;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8408,11 +8430,17 @@ export const useQueryStore = defineStore("query", () => {
|
||||
|
||||
if (effectiveDbType === "mongodb") {
|
||||
let mongoCommand;
|
||||
let exportDatabase = location.database;
|
||||
try {
|
||||
mongoCommand = await api.mongoParseShellCommand(sql);
|
||||
} catch {
|
||||
throw new Error(QUERY_RESULT_EXPORT_UNSUPPORTED_ERROR);
|
||||
}
|
||||
if (mongoCommand.kind === "inDatabase") {
|
||||
// `db.getSiblingDB("x").c.find()` exports against the wrapped database.
|
||||
exportDatabase = mongoCommand.database;
|
||||
mongoCommand = mongoCommand.command;
|
||||
}
|
||||
if (mongoCommand.kind !== "find") throw new Error(QUERY_RESULT_EXPORT_UNSUPPORTED_ERROR);
|
||||
|
||||
const pageLimit = Math.max(1, Math.trunc(exportSettings.exportBatchSize));
|
||||
@@ -8422,14 +8450,18 @@ export const useQueryStore = defineStore("query", () => {
|
||||
let totalRows = typeof tab.resultTotalRowCount === "number" ? Math.min(tab.resultTotalRowCount, exportRowLimit) : null;
|
||||
const exportStartedAt = performance.now();
|
||||
const exportExecutionId = uuid();
|
||||
// Pagination planning parses `db.<collection>.find(...)` directly; strip a
|
||||
// `db.getSiblingDB("x")` wrapper the same way the execution loop does.
|
||||
const exportSiblingPrefix = splitSiblingDbPrefix(sql);
|
||||
const exportPaginationSource = exportSiblingPrefix ? `db${exportSiblingPrefix.rest}` : sql;
|
||||
|
||||
while (documents.length < exportRowLimit) {
|
||||
const remaining = exportRowLimit - documents.length;
|
||||
const plan = planMongoFindPagination(sql, mongoCommand, pageOffset, Math.min(pageLimit, remaining));
|
||||
const plan = planMongoFindPagination(exportPaginationSource, mongoCommand, pageOffset, Math.min(pageLimit, remaining));
|
||||
if (!plan) throw new Error(QUERY_RESULT_EXPORT_UNSUPPORTED_ERROR);
|
||||
if (plan.requestLimit === 0) break;
|
||||
|
||||
const result = await api.mongoFindDocuments(location.connectionId, location.database, mongoCommand.collection, plan.requestSkip, plan.requestLimit, mongoCommand.filter, mongoCommand.projection, mongoCommand.sort, mongoCommand.collation, exportExecutionId);
|
||||
const result = await api.mongoFindDocuments(location.connectionId, exportDatabase, mongoCommand.collection, plan.requestSkip, plan.requestLimit, mongoCommand.filter, mongoCommand.projection, mongoCommand.sort, mongoCommand.collation, exportExecutionId);
|
||||
const pageDocuments = result.documents.slice(0, plan.requestLimit);
|
||||
documents.push(...pageDocuments);
|
||||
|
||||
|
||||
Reference in new issue
Block a user