fix(saved-sql): scope SQL names and guard target persistence
This commit is contained in:
8 files changed
+715
-73
No files matched your search
@@ -2743,18 +2743,23 @@ async function changeActiveConnection(tabId: string, connectionId: string) {
|
||||
if (!connection) return;
|
||||
const initialDatabase = resolveDefaultDatabase(connection, []);
|
||||
queryStore.updateConnection(tab.id, connectionId, initialDatabase);
|
||||
let isCurrentTarget = queryStore.createExecutionTargetGuard(tab.id);
|
||||
if (tab.externalSqlPath) rememberExternalSqlFileTarget(tab.externalSqlPath, { connectionId, database: initialDatabase, catalog: undefined, schema: undefined });
|
||||
connectionStore.activeConnectionId = connectionId;
|
||||
try {
|
||||
await connectionStore.ensureConnected(connectionId);
|
||||
if (!isCurrentTarget()) return;
|
||||
const options = await getDatabaseOptions(connectionId);
|
||||
if (!isCurrentTarget()) return;
|
||||
const database = resolveDefaultDatabase(connection, options);
|
||||
queryStore.updateDatabase(tab.id, database);
|
||||
isCurrentTarget = queryStore.createExecutionTargetGuard(tab.id);
|
||||
if (tab.externalSqlPath) rememberExternalSqlFileTarget(tab.externalSqlPath, { connectionId, database, catalog: undefined, schema: undefined });
|
||||
if (connection.default_schema || connection.db_type === "oracle") {
|
||||
try {
|
||||
// A configured default wins. Otherwise Oracle returns the session's current schema first.
|
||||
const orderedSchemas = connection.default_schema ? [] : await api.listSchemas(connectionId, database);
|
||||
if (!isCurrentTarget()) return;
|
||||
const schema = schemaAfterConnectionSwitch(connection.db_type, orderedSchemas, connection.default_schema);
|
||||
const latestTab = queryStore.tabs.find((candidate) => candidate.id === tab.id);
|
||||
if (schema && latestTab && latestTab.connectionId === connectionId) {
|
||||
@@ -2766,6 +2771,7 @@ async function changeActiveConnection(tabId: string, connectionId: string) {
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
if (!isCurrentTarget()) return;
|
||||
toast(
|
||||
t("connection.connectFailed", {
|
||||
message: translateBackendError(t, e),
|
||||
|
||||
@@ -21,10 +21,11 @@ import { focusSidebarRenameInput } from "@/lib/sidebar/sidebarRenameFocus";
|
||||
import { savedSqlFolderBranchFileCount } from "@/lib/savedSql/savedSqlFolderCounts";
|
||||
import { collectSavedSqlDirectoryImportFiles } from "@/lib/savedSql/savedSqlDirectoryImport";
|
||||
import { savedSqlErrorMessage } from "@/lib/savedSql/savedSqlErrors";
|
||||
import { savedSqlDatabaseScopeKey } from "@/lib/savedSql/savedSqlDatabaseTree";
|
||||
import { ensureSqlExtension, stripSqlExtension } from "@/lib/savedSql/savedSqlFileName";
|
||||
import { savedSqlImportTarget } from "@/lib/savedSql/savedSqlImportTarget";
|
||||
import { savedSqlExecutionTargetFromTab, type SavedSqlOpenTargetMode } from "@/lib/savedSql/savedSqlExecutionTarget";
|
||||
import { exportSavedSqlFileContent } from "@/lib/savedSql/savedSqlExport";
|
||||
import { uniqueSavedSqlExportFileName, exportSavedSqlFileContent } from "@/lib/savedSql/savedSqlExport";
|
||||
import { orderedListRangeAnchorIndex, orderedListSelectionIntent } from "@/lib/selection/orderedListSelection";
|
||||
import { resolveExternalSqlFileTarget, unassociatedExternalSqlFileTarget } from "@/lib/sql/externalSqlFileTarget";
|
||||
import type { SavedSqlFile, SavedSqlFolder } from "@/types/database";
|
||||
@@ -96,6 +97,10 @@ function uniqueImportedName(name: string, takenNames: Set<string>) {
|
||||
}
|
||||
}
|
||||
|
||||
function savedSqlImportNameScopeKey(target: Pick<SavedSqlFile, "connectionId" | "catalog" | "database">, folderId?: string) {
|
||||
return JSON.stringify([savedSqlDatabaseScopeKey(target), folderId || null]);
|
||||
}
|
||||
|
||||
async function exportSingleFile(file: SavedSqlFile) {
|
||||
try {
|
||||
const loadedFile = await savedSqlStore.ensureFileContent(file.id);
|
||||
@@ -136,10 +141,11 @@ async function exportFolderContents(folder?: SavedSqlFolder) {
|
||||
await mkdir(childDir, { recursive: true });
|
||||
await writeFolder(child, childDir);
|
||||
}
|
||||
const exportedNames = new Set<string>();
|
||||
for (const file of savedSqlStore.filesInFolder(libraryFolder.id)) {
|
||||
const loadedFile = await savedSqlStore.ensureFileContent(file.id);
|
||||
if (!loadedFile) continue;
|
||||
const filePath = await join(dir, sanitizeFileSystemSegment(ensureSqlExtension(file.name)));
|
||||
const filePath = await join(dir, uniqueSavedSqlExportFileName(file.name, exportedNames));
|
||||
await writeTextFile(filePath, loadedFile.sql);
|
||||
}
|
||||
};
|
||||
@@ -157,10 +163,11 @@ async function exportFolderContents(folder?: SavedSqlFolder) {
|
||||
if (unfiled.length > 0) {
|
||||
const unfiledDir = await join(rootDir, sanitizeFileSystemSegment(t("sqlLibrary.unfiled")));
|
||||
await mkdir(unfiledDir, { recursive: true });
|
||||
const exportedNames = new Set<string>();
|
||||
for (const file of unfiled) {
|
||||
const loadedFile = await savedSqlStore.ensureFileContent(file.id);
|
||||
if (!loadedFile) continue;
|
||||
const filePath = await join(unfiledDir, sanitizeFileSystemSegment(ensureSqlExtension(file.name)));
|
||||
const filePath = await join(unfiledDir, uniqueSavedSqlExportFileName(file.name, exportedNames));
|
||||
await writeTextFile(filePath, loadedFile.sql);
|
||||
}
|
||||
}
|
||||
@@ -218,18 +225,19 @@ async function importDirectoryIntoLibrary(targetFolder?: SavedSqlFolder) {
|
||||
}
|
||||
|
||||
const folderCache = new Map<string, SavedSqlFolder>();
|
||||
const takenNamesByFolder = new Map<string, Set<string>>();
|
||||
const takenNamesByScope = new Map<string, Set<string>>();
|
||||
const folderConnectionId = targetFolder?.connectionId ?? "";
|
||||
|
||||
for (const file of importFiles) {
|
||||
const sourceTarget = resolveExternalSqlFileTarget(file.path, (connectionId) => !!connectionStore.getConfig(connectionId), unassociatedExternalSqlFileTarget());
|
||||
const importTarget = savedSqlImportTarget(sourceTarget, targetFolder);
|
||||
const folderId = await resolveImportedFolder(folderConnectionId, targetFolder?.id, file.folderNames, folderCache);
|
||||
const folderKey = folderId || "";
|
||||
let takenNames = takenNamesByFolder.get(folderKey);
|
||||
const nameScopeKey = savedSqlImportNameScopeKey(importTarget, folderId);
|
||||
let takenNames = takenNamesByScope.get(nameScopeKey);
|
||||
if (!takenNames) {
|
||||
takenNames = new Set((folderId ? savedSqlStore.filesInFolder(folderId) : savedSqlStore.filesWithoutFolder()).map((savedFile) => savedFile.name));
|
||||
takenNamesByFolder.set(folderKey, takenNames);
|
||||
const filesInTargetFolder = folderId ? savedSqlStore.filesInFolder(folderId) : savedSqlStore.filesWithoutFolder();
|
||||
takenNames = new Set(filesInTargetFolder.filter((savedFile) => savedSqlDatabaseScopeKey(savedFile) === savedSqlDatabaseScopeKey(importTarget)).map((savedFile) => savedFile.name));
|
||||
takenNamesByScope.set(nameScopeKey, takenNames);
|
||||
}
|
||||
const path = file.path;
|
||||
const content = await api.readExternalSqlFile(path, externalSqlEditorMaxBytes(settingsStore.editorSettings.externalSqlEditorMaxMb));
|
||||
@@ -445,7 +453,9 @@ async function openNewQueryInFolder(folder?: SavedSqlFolder) {
|
||||
const connectionId = folder?.connectionId || connectionStore.activeConnectionId || connectionStore.connections[0]?.id;
|
||||
if (!connectionId) return;
|
||||
|
||||
const takenNames = folder ? new Set(savedSqlStore.filesInFolder(folder.id).map((f) => f.name)) : new Set(savedSqlStore.filesWithoutFolder().map((f) => f.name));
|
||||
const target = { connectionId, database: "" };
|
||||
const filesInTargetFolder = folder ? savedSqlStore.filesInFolder(folder.id) : savedSqlStore.filesWithoutFolder();
|
||||
const takenNames = new Set(filesInTargetFolder.filter((file) => savedSqlDatabaseScopeKey(file) === savedSqlDatabaseScopeKey(target)).map((file) => file.name));
|
||||
const name = uniqueImportedName("new_query.sql", takenNames);
|
||||
try {
|
||||
const file = await savedSqlStore.saveFile({
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { uniqueSavedSqlExportFileName } from "@/lib/savedSql/savedSqlExport";
|
||||
|
||||
describe("saved SQL export names", () => {
|
||||
it.each([
|
||||
["report:daily.sql", "report:daily.sql", "report_daily (2).sql"],
|
||||
["report_daily (2).sql", "report:daily.sql", "REPORT:DAILY.SQL"],
|
||||
["report/daily", "report\\daily", "report_daily.sql"],
|
||||
])("keeps all contents when sanitized names and generated suffixes collide: %j", (...names) => {
|
||||
const taken = new Set<string>();
|
||||
const output = new Map<string, string>();
|
||||
names.forEach((name, index) => {
|
||||
const path = uniqueSavedSqlExportFileName(name, taken);
|
||||
expect(path).not.toMatch(/[<>:"/\\|?*]/);
|
||||
output.set(path.toLocaleLowerCase(), `SELECT ${index};`);
|
||||
});
|
||||
expect([...output.values()]).toEqual(["SELECT 0;", "SELECT 1;", "SELECT 2;"]);
|
||||
});
|
||||
|
||||
it("preserves a safe filename and adds suffixes only for collisions", () => {
|
||||
const taken = new Set<string>();
|
||||
expect(uniqueSavedSqlExportFileName("Report.SQL", taken)).toBe("Report.SQL");
|
||||
expect(uniqueSavedSqlExportFileName("report", taken)).toBe("report (2).sql");
|
||||
expect(uniqueSavedSqlExportFileName("report", taken)).toBe("report (3).sql");
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { isTauriRuntime } from "@/lib/backend/tauriRuntime";
|
||||
import { ensureSqlExtension } from "@/lib/savedSql/savedSqlFileName";
|
||||
import { ensureSqlExtension, stripSqlExtension } from "@/lib/savedSql/savedSqlFileName";
|
||||
|
||||
export function savedSqlExportFileName(name: string): string {
|
||||
return (
|
||||
@@ -9,6 +9,19 @@ export function savedSqlExportFileName(name: string): string {
|
||||
);
|
||||
}
|
||||
|
||||
export function uniqueSavedSqlExportFileName(name: string, takenNames: Set<string>): string {
|
||||
const normalized = savedSqlExportFileName(name);
|
||||
const occupied = new Set([...takenNames].map((taken) => taken.toLocaleLowerCase()));
|
||||
const base = stripSqlExtension(normalized);
|
||||
let candidate = normalized;
|
||||
let counter = 2;
|
||||
while (occupied.has(candidate.toLocaleLowerCase())) {
|
||||
candidate = `${base} (${counter++}).sql`;
|
||||
}
|
||||
takenNames.add(candidate);
|
||||
return candidate;
|
||||
}
|
||||
|
||||
export async function exportSavedSqlFileContent(sql: string, fileName: string): Promise<"saved" | "cancelled"> {
|
||||
const defaultFileName = savedSqlExportFileName(fileName);
|
||||
if (isTauriRuntime()) {
|
||||
|
||||
@@ -4,6 +4,8 @@ import { isRedisMonitorCommand, startRedisMonitor } from "@/lib/redis/redisMonit
|
||||
import { uuid } from "@/lib/common/utils";
|
||||
import { computed, markRaw, nextTick, onScopeDispose, reactive, ref, watch } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useToast } from "@/composables/useToast";
|
||||
import { savedSqlErrorMessage } from "@/lib/savedSql/savedSqlErrors";
|
||||
import { sanitizeTabPageUiState } from "@/lib/tabs/tabUiState";
|
||||
import type { BatchSqlExecution, ConnectionConfig, DatabaseType, IndexInfo, NacosConfigEditorViewport, ObjectBrowserFilter, ObjectBrowserViewport, ObjectSource, ObjectSourceKind, QueryResult, QueryResultSourceColumnRef, QueryTab, TableInfoTab, TableStructureEditorTarget } from "@/types/database";
|
||||
import { orderPinnedFirst } from "@/lib/app/pinnedItems";
|
||||
@@ -4945,17 +4947,43 @@ export const useQueryStore = defineStore("query", () => {
|
||||
return true;
|
||||
}
|
||||
|
||||
const savedSqlTargetRequests = new WeakMap<QueryTab, number>();
|
||||
let savedSqlTargetPersistenceActive = true;
|
||||
onScopeDispose(() => {
|
||||
savedSqlTargetPersistenceActive = false;
|
||||
});
|
||||
|
||||
function createExecutionTargetGuard(id: string): () => boolean {
|
||||
const tab = tabs.value.find((candidate) => candidate.id === id);
|
||||
const target = savedSqlExecutionTargetFromTab(tab);
|
||||
if (!tab || !target) return () => false;
|
||||
const revision = savedSqlTargetRequests.get(tab);
|
||||
const savedSqlId = tab.savedSqlId;
|
||||
return () =>
|
||||
savedSqlTargetPersistenceActive && tabs.value.includes(tab) && savedSqlTargetRequests.get(tab) === revision && tab.savedSqlId === savedSqlId && tab.connectionId === target.connectionId && tab.database === target.database && tab.catalog === target.catalog && tab.schema === target.schema;
|
||||
}
|
||||
|
||||
function persistSavedSqlExecutionTarget(tab: QueryTab, options: UpdateExecutionTargetOptions) {
|
||||
const revision = (savedSqlTargetRequests.get(tab) ?? 0) + 1;
|
||||
savedSqlTargetRequests.set(tab, revision);
|
||||
if (options.persistSavedSqlTarget === false || tab.mode !== "query" || !tab.savedSqlId) return;
|
||||
const savedSqlStore = useSavedSqlStore();
|
||||
const savedSqlId = tab.savedSqlId;
|
||||
void savedSqlStore
|
||||
.updateFileExecutionTarget(tab.savedSqlId, {
|
||||
.updateFileExecutionTarget(savedSqlId, {
|
||||
connectionId: tab.connectionId,
|
||||
database: tab.database,
|
||||
catalog: tab.catalog,
|
||||
schema: tab.schema,
|
||||
})
|
||||
.catch((error) => console.warn("[DBX][saved-sql:target:error]", error));
|
||||
.catch((error) => {
|
||||
console.warn("[DBX][saved-sql:target:error]", error);
|
||||
// A failed older request must not undo a newer target selection.
|
||||
if (!savedSqlTargetPersistenceActive || savedSqlTargetRequests.get(tab) !== revision || tab.savedSqlId !== savedSqlId || !tabs.value.includes(tab)) return;
|
||||
const saved = savedSqlStore.getFile(savedSqlId);
|
||||
if (saved) applySavedSqlExecutionTarget(tab, saved);
|
||||
useToast().toast(i18n.global.t("savedSql.saveFailed", { message: savedSqlErrorMessage(error, i18n.global.t) }), 5000);
|
||||
});
|
||||
}
|
||||
|
||||
function updateDatabase(id: string, database: string, options: UpdateExecutionTargetOptions = {}) {
|
||||
@@ -8711,6 +8739,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
hydrateSavedSqlTabs,
|
||||
togglePinnedTab,
|
||||
reorderTab,
|
||||
createExecutionTargetGuard,
|
||||
updateDatabase,
|
||||
updateCatalog,
|
||||
updateSchema,
|
||||
|
||||
@@ -45,8 +45,7 @@ interface SavedSqlNameScope {
|
||||
}
|
||||
|
||||
interface PendingSavedSqlName {
|
||||
ownerId: string;
|
||||
count: number;
|
||||
owners: Map<string, number>;
|
||||
}
|
||||
|
||||
export class SavedSqlNameConflictError extends Error {
|
||||
@@ -106,9 +105,8 @@ function savedSqlNameKey(name: string): string {
|
||||
return ensureSqlExtension(name).toLocaleLowerCase();
|
||||
}
|
||||
|
||||
function savedSqlNameScopeKey(file: Pick<SavedSqlNameScope, "connectionId" | "folderId">): string {
|
||||
// Execution targets do not change the SQL library's folder-scoped names.
|
||||
return JSON.stringify(["library-folder", file.connectionId, file.folderId || null]);
|
||||
function savedSqlNameScopeKey(file: Pick<SavedSqlNameScope, "connectionId" | "catalog" | "database" | "folderId">): string {
|
||||
return JSON.stringify(["library-folder", savedSqlDatabaseScopeKey(file), file.folderId || null]);
|
||||
}
|
||||
|
||||
function savedSqlNameIdentity(file: SavedSqlNameScope): string {
|
||||
@@ -156,6 +154,7 @@ export const useSavedSqlStore = defineStore("savedSql", () => {
|
||||
const fileTargetRevisions = new Map<string, number>();
|
||||
const pendingFileTargetSaves = new Map<string, Promise<SavedSqlFile | undefined>>();
|
||||
const persistedFileTargets = new Map<string, SavedSqlExecutionTargetInput & { updatedAt: string }>();
|
||||
const persistedFileTargetNameReleases = new Map<string, () => void>();
|
||||
const pendingNamesByScope = new Map<string, Map<string, PendingSavedSqlName>>();
|
||||
|
||||
const version = ref(0);
|
||||
@@ -218,11 +217,13 @@ export const useSavedSqlStore = defineStore("savedSql", () => {
|
||||
return files.value.find((file) => file.id === id);
|
||||
}
|
||||
|
||||
function reserveFileName(file: SavedSqlNameScope): () => void {
|
||||
function reserveFileName(file: SavedSqlNameScope, options: { allowExisting?: boolean } = {}): () => void {
|
||||
const scopeKey = savedSqlNameScopeKey(file);
|
||||
const nameKey = savedSqlNameKey(file.name);
|
||||
const persistedConflict = files.value.some((candidate) => candidate.id !== file.id && savedSqlNameScopeKey(candidate) === scopeKey && savedSqlNameKey(candidate.name) === nameKey);
|
||||
if (persistedConflict) throw new SavedSqlNameConflictError(file.name);
|
||||
if (!options.allowExisting) {
|
||||
const persistedConflict = files.value.some((candidate) => candidate.id !== file.id && savedSqlNameScopeKey(candidate) === scopeKey && savedSqlNameKey(candidate.name) === nameKey);
|
||||
if (persistedConflict) throw new SavedSqlNameConflictError(file.name);
|
||||
}
|
||||
|
||||
let pendingNames = pendingNamesByScope.get(scopeKey);
|
||||
if (!pendingNames) {
|
||||
@@ -230,9 +231,9 @@ export const useSavedSqlStore = defineStore("savedSql", () => {
|
||||
pendingNamesByScope.set(scopeKey, pendingNames);
|
||||
}
|
||||
const pending = pendingNames.get(nameKey);
|
||||
if (pending && pending.ownerId !== file.id) throw new SavedSqlNameConflictError(file.name);
|
||||
if (pending) pending.count++;
|
||||
else pendingNames.set(nameKey, { ownerId: file.id, count: 1 });
|
||||
if (!options.allowExisting && pending && [...pending.owners.keys()].some((ownerId) => ownerId !== file.id)) throw new SavedSqlNameConflictError(file.name);
|
||||
if (pending) pending.owners.set(file.id, (pending.owners.get(file.id) ?? 0) + 1);
|
||||
else pendingNames.set(nameKey, { owners: new Map([[file.id, 1]]) });
|
||||
|
||||
let released = false;
|
||||
return () => {
|
||||
@@ -240,9 +241,11 @@ export const useSavedSqlStore = defineStore("savedSql", () => {
|
||||
released = true;
|
||||
const currentNames = pendingNamesByScope.get(scopeKey);
|
||||
const current = currentNames?.get(nameKey);
|
||||
if (!current || current.ownerId !== file.id) return;
|
||||
current.count--;
|
||||
if (current.count <= 0) currentNames?.delete(nameKey);
|
||||
const count = current?.owners.get(file.id);
|
||||
if (!current || count === undefined) return;
|
||||
if (count > 1) current.owners.set(file.id, count - 1);
|
||||
else current.owners.delete(file.id);
|
||||
if (current.owners.size === 0) currentNames?.delete(nameKey);
|
||||
if (currentNames?.size === 0) pendingNamesByScope.delete(scopeKey);
|
||||
};
|
||||
}
|
||||
@@ -363,6 +366,19 @@ export const useSavedSqlStore = defineStore("savedSql", () => {
|
||||
}
|
||||
}
|
||||
|
||||
function setPersistedFileTarget(file: SavedSqlFile) {
|
||||
const releaseName = reserveFileName(file, { allowExisting: true });
|
||||
persistedFileTargetNameReleases.get(file.id)?.();
|
||||
persistedFileTargetNameReleases.set(file.id, releaseName);
|
||||
persistedFileTargets.set(file.id, {
|
||||
connectionId: file.connectionId,
|
||||
catalog: normalizedCatalog(file.catalog),
|
||||
database: file.database,
|
||||
schema: file.schema,
|
||||
updatedAt: file.updatedAt,
|
||||
});
|
||||
}
|
||||
|
||||
function updateFileExecutionTarget(id: string, target: SavedSqlExecutionTargetInput): Promise<SavedSqlFile | undefined> {
|
||||
const existing = getFile(id);
|
||||
if (!existing) return Promise.resolve(undefined);
|
||||
@@ -372,13 +388,7 @@ export const useSavedSqlStore = defineStore("savedSql", () => {
|
||||
}
|
||||
|
||||
if (!persistedFileTargets.has(id)) {
|
||||
persistedFileTargets.set(id, {
|
||||
connectionId: existing.connectionId,
|
||||
catalog: normalizedCatalog(existing.catalog),
|
||||
database: existing.database,
|
||||
schema: existing.schema,
|
||||
updatedAt: existing.updatedAt,
|
||||
});
|
||||
setPersistedFileTarget(existing);
|
||||
}
|
||||
const revision = (fileTargetRevisions.get(id) ?? 0) + 1;
|
||||
fileTargetRevisions.set(id, revision);
|
||||
@@ -391,38 +401,32 @@ export const useSavedSqlStore = defineStore("savedSql", () => {
|
||||
.then(async () => {
|
||||
if (fileTargetRevisions.get(id) !== revision) return getFile(id);
|
||||
|
||||
const loaded = await ensureFileContent(id);
|
||||
if (!loaded || fileTargetRevisions.get(id) !== revision) return getFile(id);
|
||||
|
||||
const candidate: SavedSqlFile = {
|
||||
...loaded,
|
||||
...normalizedTarget,
|
||||
sqlLoaded: true,
|
||||
updatedAt: nowIso(),
|
||||
};
|
||||
files.value = files.value.map((file) => (file.id === id ? candidate : file));
|
||||
bumpVersion();
|
||||
|
||||
const persistedTarget = persistedFileTargets.get(id);
|
||||
const persistedNameIdentity = persistedTarget
|
||||
? savedSqlNameIdentity({
|
||||
...candidate,
|
||||
connectionId: persistedTarget.connectionId,
|
||||
catalog: persistedTarget.catalog,
|
||||
database: persistedTarget.database,
|
||||
})
|
||||
: savedSqlNameIdentity(candidate);
|
||||
let releaseName: (() => void) | undefined;
|
||||
try {
|
||||
const loaded = await ensureFileContent(id);
|
||||
if (!loaded || fileTargetRevisions.get(id) !== revision) return getFile(id);
|
||||
|
||||
const candidate: SavedSqlFile = {
|
||||
...loaded,
|
||||
...normalizedTarget,
|
||||
sqlLoaded: true,
|
||||
updatedAt: nowIso(),
|
||||
};
|
||||
files.value = files.value.map((file) => (file.id === id ? candidate : file));
|
||||
bumpVersion();
|
||||
|
||||
const persistedTarget = persistedFileTargets.get(id);
|
||||
const persistedNameIdentity = persistedTarget
|
||||
? savedSqlNameIdentity({
|
||||
...candidate,
|
||||
connectionId: persistedTarget.connectionId,
|
||||
catalog: persistedTarget.catalog,
|
||||
database: persistedTarget.database,
|
||||
})
|
||||
: savedSqlNameIdentity(candidate);
|
||||
if (persistedNameIdentity !== savedSqlNameIdentity(candidate)) releaseName = reserveFileName(candidate);
|
||||
const saved = normalizeSavedSqlFile(await api.saveSavedSqlFile(candidate));
|
||||
persistedFileTargets.set(id, {
|
||||
connectionId: saved.connectionId,
|
||||
catalog: normalizedCatalog(saved.catalog),
|
||||
database: saved.database,
|
||||
schema: saved.schema,
|
||||
updatedAt: saved.updatedAt,
|
||||
});
|
||||
setPersistedFileTarget(saved);
|
||||
if (fileTargetRevisions.get(id) !== revision) return getFile(id);
|
||||
const current = getFile(id);
|
||||
const persisted = { ...saved, sql: current?.sql ?? saved.sql, sqlLoaded: current?.sqlLoaded ?? true };
|
||||
@@ -447,6 +451,8 @@ export const useSavedSqlStore = defineStore("savedSql", () => {
|
||||
if (pendingFileTargetSaves.get(id) !== save) return;
|
||||
pendingFileTargetSaves.delete(id);
|
||||
persistedFileTargets.delete(id);
|
||||
persistedFileTargetNameReleases.get(id)?.();
|
||||
persistedFileTargetNameReleases.delete(id);
|
||||
};
|
||||
void save.then(cleanup, cleanup);
|
||||
return save;
|
||||
|
||||
Reference in new issue
Block a user