fix(mongo): align collection loading state

This commit is contained in:
t8y2
2026-06-14 17:04:56 +08:00
parent 7b2156446c
commit 8932b0a308
9 changed files with 266 additions and 102 deletions
@@ -0,0 +1,45 @@
<script setup lang="ts">
import { Loader2, Square } from "@lucide/vue";
import { useI18n } from "vue-i18n";
import { Button } from "@/components/ui/button";
withDefaults(
defineProps<{
labelKey?: string;
elapsedSeconds?: number | string;
showCancel?: boolean;
cancelDisabled?: boolean;
cancelling?: boolean;
cancelLabelKey?: string;
}>(),
{
labelKey: "common.loading",
elapsedSeconds: undefined,
showCancel: false,
cancelDisabled: false,
cancelling: false,
cancelLabelKey: "toolbar.stopQuery",
},
);
const emit = defineEmits<{
cancel: [];
}>();
const { t } = useI18n();
</script>
<template>
<div class="flex flex-col items-center justify-center gap-3 text-muted-foreground text-sm">
<div class="flex items-center">
<Loader2 class="h-5 w-5 animate-spin mr-2" />
{{ t(labelKey) }}
<span v-if="elapsedSeconds !== undefined" class="ml-1 tabular-nums text-muted-foreground/80">· {{ elapsedSeconds }}s</span>
</div>
<Button v-if="showCancel" variant="destructive" size="sm" class="h-7 gap-1.5" :disabled="cancelDisabled" @click="emit('cancel')">
<Loader2 v-if="cancelling" class="h-3.5 w-3.5 animate-spin" />
<Square v-else class="h-3.5 w-3.5 fill-current" />
{{ t(cancelLabelKey) }}
</Button>
</div>
</template>
@@ -11,6 +11,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
import DangerConfirmDialog from "@/components/editor/DangerConfirmDialog.vue";
import ErrorBanner from "@/components/ui/ErrorBanner.vue";
import DataGrid from "@/components/grid/DataGrid.vue";
import QueryLoadingState from "@/components/common/QueryLoadingState.vue";
import * as api from "@/lib/api";
import { clampSearchSplitWidth } from "@/lib/dataGridSearchSplit";
import { buildDocumentFilterCondition, combineDocumentFilterConditions, currentDocumentFilterJson, defaultDocumentFilterRule, documentFilterModeNeedsValue, documentFilterModeOptions, documentStoreProviderFor, type DocumentFilterMode, type DocumentFilterRule } from "@/lib/documentStoreProvider";
@@ -40,6 +41,9 @@ const documents = ref<JsonRecord[]>([]);
const lastGridColumns = ref<string[]>([]);
const total = ref(0);
const loading = ref(false);
const documentLoadExecutionId = ref("");
const documentLoadCancelling = ref(false);
const documentLoadingElapsedSeconds = ref("0.0");
const page = ref(0);
const pageSize = ref(normalizeResultPageSize(settingsStore.editorSettings.pageSize));
const selectedIdx = ref<number | null>(null);
@@ -145,6 +149,8 @@ const gridResult = computed<QueryResult>(() => {
});
const documentFilterFieldOptions = computed(() => gridResult.value.columns);
const documentStructuredFilterCount = computed(() => (appliedDocumentFilter.value ? 1 : 0));
const documentLoadingLabelKey = computed(() => (documentLoadCancelling.value ? "common.stopping" : "common.loading"));
let documentLoadingTimer: ReturnType<typeof setInterval> | undefined;
function createDocumentFilterRule(): DocumentFilterRule {
return defaultDocumentFilterRule(uuid(), documentFilterFieldOptions.value[0] ?? "");
@@ -362,15 +368,35 @@ const customSaveHandler = computed<CustomSaveHandler>(() => ({
preview: previewDocumentChanges,
}));
function stopDocumentLoadingTimer() {
if (documentLoadingTimer) clearInterval(documentLoadingTimer);
documentLoadingTimer = undefined;
}
function startDocumentLoadingTimer() {
stopDocumentLoadingTimer();
const startedAt = Date.now();
documentLoadingElapsedSeconds.value = "0.0";
documentLoadingTimer = setInterval(() => {
documentLoadingElapsedSeconds.value = ((Date.now() - startedAt) / 1000).toFixed(1);
}, 100);
}
async function load() {
if (documentLoadExecutionId.value) void api.cancelQuery(documentLoadExecutionId.value);
const executionId = uuid();
loading.value = true;
documentLoadExecutionId.value = executionId;
documentLoadCancelling.value = false;
startDocumentLoadingTimer();
error.value = "";
const previousSelectedIdx = selectedIdx.value;
const previousSelectedId = previousSelectedIdx === null ? null : documentIdentity(documents.value[previousSelectedIdx]);
try {
const filter = currentDocumentFilter();
const sort = sortInput.value.trim() || undefined;
const result = await api.documentFindDocuments(props.connectionId, props.database, props.collection, page.value * pageSize.value, pageSize.value, filter, sort);
const result = await api.documentFindDocuments(props.connectionId, props.database, props.collection, page.value * pageSize.value, pageSize.value, filter, sort, executionId);
if (documentLoadExecutionId.value !== executionId) return;
const nextDocuments = result.documents.map(asRecord);
documents.value = nextDocuments;
if (nextDocuments.length > 0) {
@@ -386,9 +412,30 @@ async function load() {
total.value = result.total;
syncSelectedDocumentAfterLoad(previousSelectedIdx, previousSelectedId);
} catch (e: unknown) {
error.value = e instanceof Error ? e.message : String(e);
if (documentLoadExecutionId.value === executionId) error.value = e instanceof Error ? e.message : String(e);
} finally {
loading.value = false;
if (documentLoadExecutionId.value === executionId) {
loading.value = false;
documentLoadExecutionId.value = "";
documentLoadCancelling.value = false;
stopDocumentLoadingTimer();
}
}
}
async function cancelDocumentLoad() {
const executionId = documentLoadExecutionId.value;
if (!executionId || documentLoadCancelling.value) return;
documentLoadCancelling.value = true;
try {
await api.cancelQuery(executionId);
} finally {
if (documentLoadExecutionId.value === executionId) {
loading.value = false;
documentLoadExecutionId.value = "";
documentLoadCancelling.value = false;
stopDocumentLoadingTimer();
}
}
}
@@ -675,6 +722,8 @@ function highlightedJson(json: string): string {
onMounted(load);
onBeforeUnmount(() => {
if (documentLoadExecutionId.value) void api.cancelQuery(documentLoadExecutionId.value);
stopDocumentLoadingTimer();
endTableSearchSplitResize();
});
@@ -817,8 +866,18 @@ function resetTableSearchSplitWidth() {
</div>
<!-- Table view -->
<QueryLoadingState
v-if="viewMode === 'table' && loading && gridResult.columns.length === 0"
class="flex-1 min-h-0"
:label-key="documentLoadingLabelKey"
:elapsed-seconds="documentLoadingElapsedSeconds"
show-cancel
:cancel-disabled="!documentLoadExecutionId || documentLoadCancelling"
:cancelling="documentLoadCancelling"
@cancel="cancelDocumentLoad"
/>
<DataGrid
v-if="viewMode === 'table'"
v-else-if="viewMode === 'table'"
ref="dataGridRef"
class="flex-1 min-h-0"
:result="gridResult"
@@ -62,6 +62,7 @@ import {
TableProperties,
} from "@lucide/vue";
import { Button } from "@/components/ui/button";
import QueryLoadingState from "@/components/common/QueryLoadingState.vue";
import CustomContextMenu, { type ContextMenuItem } from "@/components/ui/CustomContextMenu.vue";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input";
@@ -7256,7 +7257,8 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
</div>
</div>
</CustomContextMenu>
<div v-if="!hasData" class="flex-1 flex items-center justify-center text-muted-foreground text-sm">
<QueryLoadingState v-if="!hasData && loading" class="flex-1 min-h-0" />
<div v-else-if="!hasData" class="flex-1 flex items-center justify-center text-muted-foreground text-sm">
{{ t("grid.querySuccess") }}
</div>
@@ -2,13 +2,14 @@
import { computed, ref, defineAsyncComponent, watch, nextTick, onMounted, onUnmounted } from "vue";
import type { CSSProperties } from "vue";
import { useI18n } from "vue-i18n";
import { Check, Columns3, Loader2, Search, Square, Bot, GitBranch, BarChart3, TableProperties, ChevronDown, ChevronUp, Inbox, RefreshCcw, Wrench, ListChecks, Download, X } from "@lucide/vue";
import { Check, Columns3, Loader2, Search, Bot, GitBranch, BarChart3, TableProperties, ChevronDown, ChevronUp, Inbox, RefreshCcw, Wrench, ListChecks, Download, X } from "@lucide/vue";
import { Splitpanes, Pane } from "splitpanes";
import "splitpanes/dist/splitpanes.css";
import { Button } from "@/components/ui/button";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Switch } from "@/components/ui/switch";
import LightTooltip from "@/components/ui/LightTooltip.vue";
import QueryLoadingState from "@/components/common/QueryLoadingState.vue";
import QueryEditor from "@/components/editor/QueryEditor.vue";
import ColumnInfoPanel from "@/components/editor/ColumnInfoPanel.vue";
import type { ColumnInfo } from "@/components/editor/ColumnInfoPanel.vue";
@@ -700,18 +701,16 @@ defineExpose({ focusSearch, refreshData, handleModRTarget });
</Button>
</template>
</DataGrid>
<div v-else-if="!activeTab.result && activeTab.isExecuting" class="flex-1 min-h-0 flex flex-col items-center justify-center gap-3 text-muted-foreground text-sm">
<div class="flex items-center">
<Loader2 class="h-5 w-5 animate-spin mr-2" />
{{ t(queryExecutionLabelKey(activeTab)) }}
<span class="ml-1 tabular-nums text-muted-foreground/80">· {{ queryRunningElapsedSeconds }}s</span>
</div>
<Button variant="destructive" size="sm" class="h-7 gap-1.5" :disabled="!canCancelQueryExecution(activeTab)" @click="emit('cancel')">
<Loader2 v-if="activeTab.isCancelling" class="h-3.5 w-3.5 animate-spin" />
<Square v-else class="h-3.5 w-3.5 fill-current" />
{{ t("toolbar.stopQuery") }}
</Button>
</div>
<QueryLoadingState
v-else-if="!activeTab.result && activeTab.isExecuting"
class="flex-1 min-h-0"
:label-key="queryExecutionLabelKey(activeTab)"
:elapsed-seconds="queryRunningElapsedSeconds"
show-cancel
:cancel-disabled="!canCancelQueryExecution(activeTab)"
:cancelling="activeTab.isCancelling"
@cancel="emit('cancel')"
/>
<div v-else-if="!activeTab.result" class="flex-1 min-h-0 flex flex-col items-center justify-center gap-1 text-muted-foreground text-sm">
<div>{{ t("editor.pressToExecute", { mod: shortcutModifier }) }}</div>
<div>{{ t("editor.pressToSaveSql", { mod: shortcutModifier }) }}</div>
@@ -847,18 +846,7 @@ defineExpose({ focusSearch, refreshData, handleModRTarget });
@paginate="(offset: number, limit: number, whereInput?: string, orderBy?: string) => emit('paginate', offset, limit, whereInput, orderBy)"
@sort="(column: string, columnIndex: number, direction: 'asc' | 'desc' | null, whereInput?: string) => emit('sort', column, columnIndex, direction, whereInput)"
/>
<div v-else-if="activeTab.isExecuting" class="h-full flex flex-col items-center justify-center gap-3 text-muted-foreground text-sm">
<div class="flex items-center">
<Loader2 class="h-5 w-5 animate-spin mr-2" />
{{ t(queryExecutionLabelKey(activeTab)) }}
<span class="ml-1 tabular-nums text-muted-foreground/80">· {{ queryRunningElapsedSeconds }}s</span>
</div>
<Button variant="destructive" size="sm" class="h-7 gap-1.5" :disabled="!canCancelQueryExecution(activeTab)" @click="emit('cancel')">
<Loader2 v-if="activeTab.isCancelling" class="h-3.5 w-3.5 animate-spin" />
<Square v-else class="h-3.5 w-3.5 fill-current" />
{{ t("toolbar.stopQuery") }}
</Button>
</div>
<QueryLoadingState v-else-if="activeTab.isExecuting" class="h-full" :label-key="queryExecutionLabelKey(activeTab)" :elapsed-seconds="queryRunningElapsedSeconds" show-cancel :cancel-disabled="!canCancelQueryExecution(activeTab)" :cancelling="activeTab.isCancelling" @cancel="emit('cancel')" />
<div v-else class="h-full flex flex-col items-center justify-center gap-3 text-muted-foreground text-sm">
<Inbox class="h-8 w-8 opacity-60" />
<div>{{ t("grid.dataUnavailable") }}</div>
+8 -7
View File
@@ -545,7 +545,8 @@ export async function executeInTransaction(connectionId: string, database: strin
}
export async function cancelQuery(executionId: string): Promise<boolean> {
return post("/api/query/cancel", { executionId });
const result = await post<boolean | { cancelled?: boolean }>("/api/query/cancel", { executionId });
return typeof result === "boolean" ? result : result.cancelled === true;
}
export async function analyzeSqlReferences(sql: string, dialect?: string): Promise<SqlReferenceAnalysis> {
@@ -1389,16 +1390,16 @@ export async function elasticsearchListIndices(connectionId: string): Promise<st
return mongoListCollections(connectionId, "default");
}
export async function mongoFindDocuments(connectionId: string, database: string, collection: string, skip: number, limit: number, filter?: string, sort?: string): Promise<MongoDocumentResult> {
return post("/api/mongo/find-documents", { connectionId, database, collection, skip, limit, filter, sort });
export async function mongoFindDocuments(connectionId: string, database: string, collection: string, skip: number, limit: number, filter?: string, sort?: string, executionId?: string): Promise<MongoDocumentResult> {
return post("/api/mongo/find-documents", { connectionId, database, collection, skip, limit, filter, sort, executionId });
}
export async function documentFindDocuments(connectionId: string, database: string, collection: string, skip: number, limit: number, filter?: string, sort?: string): Promise<MongoDocumentResult> {
return post("/api/document-store/find-documents", { connectionId, database, collection, skip, limit, filter, sort });
export async function documentFindDocuments(connectionId: string, database: string, collection: string, skip: number, limit: number, filter?: string, sort?: string, executionId?: string): Promise<MongoDocumentResult> {
return post("/api/document-store/find-documents", { connectionId, database, collection, skip, limit, filter, sort, executionId });
}
export async function mongoAggregateDocuments(connectionId: string, database: string, collection: string, pipelineJson: string, maxRows?: number): Promise<MongoDocumentResult> {
return post("/api/mongo/aggregate-documents", { connectionId, database, collection, pipelineJson, maxRows });
export async function mongoAggregateDocuments(connectionId: string, database: string, collection: string, pipelineJson: string, maxRows?: number, executionId?: string): Promise<MongoDocumentResult> {
return post("/api/mongo/aggregate-documents", { connectionId, database, collection, pipelineJson, maxRows, executionId });
}
export async function mongoInsertDocument(connectionId: string, database: string, collection: string, docJson: string): Promise<string> {
+6 -6
View File
@@ -1268,16 +1268,16 @@ export async function elasticsearchListIndices(connectionId: string): Promise<st
return mongoListCollections(connectionId, "default");
}
export async function mongoFindDocuments(connectionId: string, database: string, collection: string, skip: number, limit: number, filter?: string, sort?: string): Promise<MongoDocumentResult> {
return invoke("mongo_find_documents", { connectionId, database, collection, skip, limit, filter, sort });
export async function mongoFindDocuments(connectionId: string, database: string, collection: string, skip: number, limit: number, filter?: string, sort?: string, executionId?: string): Promise<MongoDocumentResult> {
return invoke("mongo_find_documents", { connectionId, database, collection, skip, limit, filter, sort, executionId });
}
export async function documentFindDocuments(connectionId: string, database: string, collection: string, skip: number, limit: number, filter?: string, sort?: string): Promise<MongoDocumentResult> {
return invoke("document_find_documents", { connectionId, database, collection, skip, limit, filter, sort });
export async function documentFindDocuments(connectionId: string, database: string, collection: string, skip: number, limit: number, filter?: string, sort?: string, executionId?: string): Promise<MongoDocumentResult> {
return invoke("document_find_documents", { connectionId, database, collection, skip, limit, filter, sort, executionId });
}
export async function mongoAggregateDocuments(connectionId: string, database: string, collection: string, pipelineJson: string, maxRows?: number): Promise<MongoDocumentResult> {
return invoke("mongo_aggregate_documents", { connectionId, database, collection, pipelineJson, maxRows });
export async function mongoAggregateDocuments(connectionId: string, database: string, collection: string, pipelineJson: string, maxRows?: number, executionId?: string): Promise<MongoDocumentResult> {
return invoke("mongo_aggregate_documents", { connectionId, database, collection, pipelineJson, maxRows, executionId });
}
export async function mongoInsertDocument(connectionId: string, database: string, collection: string, docJson: string): Promise<string> {
+3 -3
View File
@@ -1225,7 +1225,7 @@ export const useQueryStore = defineStore("query", () => {
if (mongoFind) {
await connStore.ensureConnected(tab.connectionId);
console.info("[DBX][executeTabSql:mongo-find:start]", { traceId, collection: mongoFind.collection });
const result = await api.mongoFindDocuments(tab.connectionId, tab.database, mongoFind.collection, mongoFind.skip, mongoFind.limit, mongoFind.filter, mongoFind.sort);
const result = await api.mongoFindDocuments(tab.connectionId, tab.database, mongoFind.collection, mongoFind.skip, mongoFind.limit, mongoFind.filter, mongoFind.sort, executionId);
console.info("[DBX][executeTabSql:mongo-find:done]", {
traceId,
rowCount: result.documents.length,
@@ -1252,7 +1252,7 @@ export const useQueryStore = defineStore("query", () => {
if (mongoCount) {
await connStore.ensureConnected(tab.connectionId);
console.info("[DBX][executeTabSql:mongo-count:start]", { traceId, collection: mongoCount.collection });
const result = await api.mongoFindDocuments(tab.connectionId, tab.database, mongoCount.collection, 0, 1, mongoCount.filter);
const result = await api.mongoFindDocuments(tab.connectionId, tab.database, mongoCount.collection, 0, 1, mongoCount.filter, undefined, executionId);
console.info("[DBX][executeTabSql:mongo-count:done]", {
traceId,
total: result.total,
@@ -1283,7 +1283,7 @@ export const useQueryStore = defineStore("query", () => {
}
await connStore.ensureConnected(tab.connectionId);
console.info("[DBX][executeTabSql:mongo-aggregate:start]", { traceId, collection: mongoAggregate.collection });
const result = await api.mongoAggregateDocuments(tab.connectionId, tab.database, mongoAggregate.collection, mongoAggregate.pipeline, pageLimit);
const result = await api.mongoAggregateDocuments(tab.connectionId, tab.database, mongoAggregate.collection, mongoAggregate.pipeline, pageLimit, executionId);
console.info("[DBX][executeTabSql:mongo-aggregate:done]", {
traceId,
rowCount: result.documents.length,
+63 -31
View File
@@ -1,3 +1,4 @@
use std::future::Future;
use std::sync::Arc;
use axum::extract::State;
@@ -7,6 +8,26 @@ use serde::Deserialize;
use crate::error::AppError;
use crate::state::WebState;
async fn run_cancellable<T, F>(state: &Arc<WebState>, execution_id: Option<String>, future: F) -> Result<T, AppError>
where
F: Future<Output = Result<T, String>>,
{
let registered = execution_id
.as_ref()
.filter(|id| !id.trim().is_empty())
.map(|id| state.app.running_queries.register(id.clone()));
if let Some(query) = registered.as_ref() {
let token = query.token();
tokio::select! {
biased;
_ = token.cancelled() => Err(AppError(dbx_core::query::canceled_error())),
result = future => result.map_err(AppError),
}
} else {
future.await.map_err(AppError)
}
}
/// Check if a connection is read-only and return an error if so.
async fn ensure_writable(
app: &dbx_core::connection::AppState,
@@ -45,6 +66,7 @@ pub struct MongoFindRequest {
pub limit: Option<i64>,
pub filter: Option<String>,
pub sort: Option<String>,
pub execution_id: Option<String>,
}
#[derive(Deserialize)]
@@ -55,6 +77,7 @@ pub struct MongoAggregateRequest {
pub collection: String,
pub pipeline_json: String,
pub max_rows: Option<usize>,
pub execution_id: Option<String>,
}
#[derive(Deserialize)]
@@ -138,18 +161,21 @@ pub async fn find_documents(
State(state): State<Arc<WebState>>,
Json(req): Json<MongoFindRequest>,
) -> Result<Json<serde_json::Value>, AppError> {
let result = dbx_core::mongo_ops::mongo_find_documents_core(
&state.app,
&req.connection_id,
&req.database,
&req.collection,
req.skip.unwrap_or(0),
req.limit.unwrap_or(50),
req.filter.as_deref(),
req.sort.as_deref(),
let result = run_cancellable(
&state,
req.execution_id.clone(),
dbx_core::mongo_ops::mongo_find_documents_core(
&state.app,
&req.connection_id,
&req.database,
&req.collection,
req.skip.unwrap_or(0),
req.limit.unwrap_or(50),
req.filter.as_deref(),
req.sort.as_deref(),
),
)
.await
.map_err(AppError)?;
.await?;
Ok(Json(serde_json::to_value(result).map_err(|e| AppError(e.to_string()))?))
}
@@ -157,18 +183,21 @@ pub async fn document_find_documents(
State(state): State<Arc<WebState>>,
Json(req): Json<MongoFindRequest>,
) -> Result<Json<serde_json::Value>, AppError> {
let result = dbx_core::mongo_ops::document_find_documents_core(
&state.app,
&req.connection_id,
&req.database,
&req.collection,
req.skip.unwrap_or(0),
req.limit.unwrap_or(50),
req.filter.as_deref(),
req.sort.as_deref(),
let result = run_cancellable(
&state,
req.execution_id.clone(),
dbx_core::mongo_ops::document_find_documents_core(
&state.app,
&req.connection_id,
&req.database,
&req.collection,
req.skip.unwrap_or(0),
req.limit.unwrap_or(50),
req.filter.as_deref(),
req.sort.as_deref(),
),
)
.await
.map_err(AppError)?;
.await?;
Ok(Json(serde_json::to_value(result).map_err(|e| AppError(e.to_string()))?))
}
@@ -176,16 +205,19 @@ pub async fn aggregate_documents(
State(state): State<Arc<WebState>>,
Json(req): Json<MongoAggregateRequest>,
) -> Result<Json<serde_json::Value>, AppError> {
let result = dbx_core::mongo_ops::mongo_aggregate_documents_core(
&state.app,
&req.connection_id,
&req.database,
&req.collection,
&req.pipeline_json,
req.max_rows,
let result = run_cancellable(
&state,
req.execution_id.clone(),
dbx_core::mongo_ops::mongo_aggregate_documents_core(
&state.app,
&req.connection_id,
&req.database,
&req.collection,
&req.pipeline_json,
req.max_rows,
),
)
.await
.map_err(AppError)?;
.await?;
Ok(Json(serde_json::to_value(result).map_err(|e| AppError(e.to_string()))?))
}
+62 -25
View File
@@ -1,9 +1,28 @@
use std::future::Future;
use std::sync::Arc;
use tauri::State;
use crate::commands::connection::{ensure_connection_writable, AppState};
use dbx_core::db::mongo_driver::MongoDocumentResult;
async fn run_cancellable<T, F>(state: &Arc<AppState>, execution_id: Option<String>, future: F) -> Result<T, String>
where
F: Future<Output = Result<T, String>>,
{
let registered_query =
execution_id.as_ref().filter(|id| !id.trim().is_empty()).map(|id| state.running_queries.register(id.clone()));
if let Some(query) = registered_query.as_ref() {
let token = query.token();
tokio::select! {
biased;
_ = token.cancelled() => Err(dbx_core::query::canceled_error()),
result = future => result,
}
} else {
future.await
}
}
#[tauri::command]
pub async fn mongo_list_databases(
state: State<'_, Arc<AppState>>,
@@ -32,16 +51,22 @@ pub async fn mongo_find_documents(
limit: i64,
filter: Option<String>,
sort: Option<String>,
execution_id: Option<String>,
) -> Result<MongoDocumentResult, String> {
dbx_core::mongo_ops::mongo_find_documents_core(
&state,
&connection_id,
&database,
&collection,
skip,
limit,
filter.as_deref(),
sort.as_deref(),
let app = state.inner().clone();
run_cancellable(
&app,
execution_id,
dbx_core::mongo_ops::mongo_find_documents_core(
&app,
&connection_id,
&database,
&collection,
skip,
limit,
filter.as_deref(),
sort.as_deref(),
),
)
.await
}
@@ -57,16 +82,22 @@ pub async fn document_find_documents(
limit: i64,
filter: Option<String>,
sort: Option<String>,
execution_id: Option<String>,
) -> Result<MongoDocumentResult, String> {
dbx_core::mongo_ops::document_find_documents_core(
&state,
&connection_id,
&database,
&collection,
skip,
limit,
filter.as_deref(),
sort.as_deref(),
let app = state.inner().clone();
run_cancellable(
&app,
execution_id,
dbx_core::mongo_ops::document_find_documents_core(
&app,
&connection_id,
&database,
&collection,
skip,
limit,
filter.as_deref(),
sort.as_deref(),
),
)
.await
}
@@ -79,14 +110,20 @@ pub async fn mongo_aggregate_documents(
collection: String,
pipeline_json: String,
max_rows: Option<usize>,
execution_id: Option<String>,
) -> Result<MongoDocumentResult, String> {
dbx_core::mongo_ops::mongo_aggregate_documents_core(
&state,
&connection_id,
&database,
&collection,
&pipeline_json,
max_rows,
let app = state.inner().clone();
run_cancellable(
&app,
execution_id,
dbx_core::mongo_ops::mongo_aggregate_documents_core(
&app,
&connection_id,
&database,
&collection,
&pipeline_json,
max_rows,
),
)
.await
}