feat(opengauss): support A-compatibility packages
This commit is contained in:
1 parent
760cad6d1b
commit
deb47df2eb
28 files changed
+1706
-139
No files matched your search
@@ -461,6 +461,11 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
const etcdAccessCapabilityGenerations = new Map<string, number>();
|
||||
const etcdAccessCapabilityLoads = new Map<string, Promise<EtcdAccessCapabilities>>();
|
||||
const identifierQuotes = ref<Record<string, string>>({});
|
||||
/** Per-database compatibility mode (key `${connectionId}\u0000${database}`),
|
||||
* decoupled from sidebar tree-node lifecycle so editor parsing and sidebar
|
||||
* capabilities stay correct before/independent of database-tree rendering. */
|
||||
const databaseCompatibilityModes = ref<Record<string, string>>({});
|
||||
const databaseCompatibilityRefreshes = new Map<string, Promise<void>>();
|
||||
const lastConnectionHealthCheckAt = ref<Record<string, number>>({});
|
||||
const agentDrivers = ref<AgentDriverInstallState[]>([]);
|
||||
let agentDriversRefreshPromise: Promise<void> | null = null;
|
||||
@@ -788,13 +793,130 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
return identifierQuotes.value[connectionId];
|
||||
}
|
||||
|
||||
function databaseCompatibilityKey(connectionId: string, database: string): string {
|
||||
return `${connectionId}\u0000${database.trim().toLowerCase()}`;
|
||||
}
|
||||
|
||||
function databaseCompatibilityModeFromTree(connectionId: string, database: string): string | undefined {
|
||||
const wanted = database.trim().toLowerCase();
|
||||
const visit = (nodes: readonly TreeNode[]): string | undefined => {
|
||||
for (const node of nodes) {
|
||||
if (node.connectionId === connectionId && node.type === "database" && node.database?.trim().toLowerCase() === wanted) {
|
||||
return node.compatibilityMode?.trim() || undefined;
|
||||
}
|
||||
const nested = node.children ? visit(node.children) : undefined;
|
||||
if (nested) return nested;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
return visit(treeNodes.value);
|
||||
}
|
||||
|
||||
/** Compatibility mode for one database. Live metadata wins over persisted tree data. */
|
||||
function databaseCompatibilityMode(connectionId: string | undefined, database: string | undefined): string | undefined {
|
||||
if (!connectionId || database == null) return undefined;
|
||||
return databaseCompatibilityModes.value[databaseCompatibilityKey(connectionId, database)] ?? databaseCompatibilityModeFromTree(connectionId, database);
|
||||
}
|
||||
|
||||
function setDatabaseCompatibilityModesFromDatabases(connectionId: string, databases: readonly { name: string; compatibility_mode?: string | null }[]) {
|
||||
if (!connectionId) return;
|
||||
const prefix = `${connectionId}\u0000`;
|
||||
const next = { ...databaseCompatibilityModes.value };
|
||||
// Prune entries for databases that no longer exist on this connection.
|
||||
let changed = false;
|
||||
for (const key of Object.keys(next)) {
|
||||
if (key.startsWith(prefix) && !databases.some((database) => databaseCompatibilityKey(connectionId, database.name.trim()) === key)) {
|
||||
delete next[key];
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
for (const database of databases) {
|
||||
const name = database.name.trim();
|
||||
if (!name) continue;
|
||||
const mode = database.compatibility_mode?.trim() || undefined;
|
||||
const key = databaseCompatibilityKey(connectionId, name);
|
||||
if (mode !== undefined && next[key] !== mode) {
|
||||
next[key] = mode;
|
||||
changed = true;
|
||||
} else if (mode === undefined && key in next) {
|
||||
delete next[key];
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (changed) databaseCompatibilityModes.value = next;
|
||||
}
|
||||
|
||||
/** Ensure the compatibility map is warm for an openGauss connection even when
|
||||
* the database tree has not loaded (restored tab, filtered node, …). Runs in
|
||||
* the background (void caller) and is timeout-bounded so it never blocks or
|
||||
* hangs the connect flow. The connection-state revision guards against stale
|
||||
* refreshes overwriting a newer connection's map after a disconnect/reconnect. */
|
||||
async function refreshConnectionDatabaseModesOnce(connectionId: string, config: ConnectionConfig) {
|
||||
try {
|
||||
if (config.db_type !== "opengauss") return;
|
||||
const revision = connectionStateRevision(connectionId);
|
||||
const databases = await withMetadataLoadTimeout(connectionId, api.listDatabases(connectionId), "compatibility modes").catch(() => undefined);
|
||||
if (!databases || !isCurrentConnectionStateRevision(connectionId, revision)) return;
|
||||
const before = databaseCompatibilityModes.value;
|
||||
setDatabaseCompatibilityModesFromDatabases(connectionId, databases);
|
||||
if (databaseCompatibilityModes.value === before) return;
|
||||
// The mode map changed; invalidate completion caches for every affected
|
||||
// database. The completion cache key does not include the compatibility
|
||||
// mode, so a result cached while the mode was unknown/wrong (e.g. package
|
||||
// members surfaced as top-level routines) would otherwise be served forever.
|
||||
for (const database of databases) {
|
||||
invalidateCompletionCache(connectionId, database.name.trim());
|
||||
}
|
||||
// Refresh any expanded openGauss database subtree so PACKAGE groups appear
|
||||
// without waiting for the next manual expand.
|
||||
for (const database of databases) {
|
||||
if (!database.compatibility_mode?.trim()) continue;
|
||||
if (database.compatibility_mode.trim().toUpperCase() !== "A") continue;
|
||||
const node = findNode(treeNodes.value, `${connectionId}:${database.name.trim()}`);
|
||||
if (!node || node.type !== "database" || !node.isExpanded) continue;
|
||||
void refreshTreeNode(node).catch(() => undefined);
|
||||
}
|
||||
} catch {
|
||||
// Compatibility metadata is optional and must never create an unhandled
|
||||
// rejection in the background connection lifecycle.
|
||||
}
|
||||
}
|
||||
|
||||
function refreshConnectionDatabaseModes(connectionId: string, config: ConnectionConfig): Promise<void> {
|
||||
const existing = databaseCompatibilityRefreshes.get(connectionId);
|
||||
if (existing) return existing;
|
||||
const refresh = refreshConnectionDatabaseModesOnce(connectionId, config).finally(() => {
|
||||
if (databaseCompatibilityRefreshes.get(connectionId) === refresh) databaseCompatibilityRefreshes.delete(connectionId);
|
||||
});
|
||||
databaseCompatibilityRefreshes.set(connectionId, refresh);
|
||||
return refresh;
|
||||
}
|
||||
|
||||
async function ensureDatabaseCompatibilityMode(connectionId: string, database: string | undefined): Promise<string | undefined> {
|
||||
const config = getConfig(connectionId);
|
||||
if (config?.db_type !== "opengauss" || database == null) return undefined;
|
||||
const current = databaseCompatibilityMode(connectionId, database);
|
||||
if (current !== undefined) return current;
|
||||
await refreshConnectionDatabaseModes(connectionId, { ...config, id: connectionId });
|
||||
return databaseCompatibilityMode(connectionId, database);
|
||||
}
|
||||
|
||||
function clearConnectionIdentifierQuote(connectionId: string) {
|
||||
clearConnectionDatabaseModes(connectionId);
|
||||
if (!(connectionId in identifierQuotes.value)) return;
|
||||
const next = { ...identifierQuotes.value };
|
||||
delete next[connectionId];
|
||||
identifierQuotes.value = next;
|
||||
}
|
||||
|
||||
function clearConnectionDatabaseModes(connectionId: string) {
|
||||
const prefix = `${connectionId}\u0000`;
|
||||
const entries = Object.entries(databaseCompatibilityModes.value).filter(([key]) => !key.startsWith(prefix));
|
||||
if (entries.length !== Object.keys(databaseCompatibilityModes.value).length) {
|
||||
databaseCompatibilityModes.value = Object.fromEntries(entries);
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshConnectionIdentifierQuote(connectionId: string, config: ConnectionConfig) {
|
||||
clearConnectionIdentifierQuote(connectionId);
|
||||
if (!connectionShouldLoadIdentifierQuote(config)) return;
|
||||
@@ -1902,23 +2024,26 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
return config?.db_type === "informix" ? `${version}-informix-owner-v2` : version;
|
||||
}
|
||||
|
||||
function supportedSidebarObjectTypes(config?: ConnectionConfig): DatabaseObjectTreeKind[] {
|
||||
function supportedSidebarObjectTypes(config?: ConnectionConfig, database?: string): DatabaseObjectTreeKind[] {
|
||||
const dbType = effectiveDatabaseTypeForConnection(config);
|
||||
return sidebarObjectKindsForDatabase(dbType);
|
||||
return sidebarObjectKindsForDatabase(dbType, databaseCompatibilityMode(config?.id, database));
|
||||
}
|
||||
|
||||
function sidebarObjectTypesForScope(config: ConnectionConfig | undefined, schema?: string): DatabaseObjectTreeKind[] {
|
||||
function sidebarObjectTypesForScope(config: ConnectionConfig | undefined, database: string | undefined, schema?: string): DatabaseObjectTreeKind[] {
|
||||
if (config?.db_type === "xugu" && isXuguPublicSynonymScope(schema)) {
|
||||
return ["SYNONYM"];
|
||||
}
|
||||
if (config?.db_type === "xugu" && isXuguSchedulerJobScope(schema)) {
|
||||
return ["JOB"];
|
||||
}
|
||||
return supportedSidebarObjectTypes(config);
|
||||
return supportedSidebarObjectTypes(config, database);
|
||||
}
|
||||
|
||||
function objectTreeCacheVersion(config: ConnectionConfig | undefined, schema: string | undefined, baseVersion: string): string {
|
||||
const scopedVersion = config?.db_type === "xugu" && isXuguPublicSynonymScope(schema) ? `${baseVersion}-public-synonyms` : config?.db_type === "xugu" && isXuguSchedulerJobScope(schema) ? `${baseVersion}-scheduler-jobs` : baseVersion;
|
||||
function objectTreeCacheVersion(config: ConnectionConfig | undefined, database: string | undefined, schema: string | undefined, baseVersion: string): string {
|
||||
let scopedVersion = config?.db_type === "xugu" && isXuguPublicSynonymScope(schema) ? `${baseVersion}-public-synonyms` : config?.db_type === "xugu" && isXuguSchedulerJobScope(schema) ? `${baseVersion}-scheduler-jobs` : baseVersion;
|
||||
if (config?.db_type === "opengauss" && databaseCompatibilityMode(config.id, database)?.trim().toUpperCase() === "A") {
|
||||
scopedVersion = `${scopedVersion}-a-packages-v1`;
|
||||
}
|
||||
return ownerAwareMetadataCacheVersion(config, scopedVersion);
|
||||
}
|
||||
|
||||
@@ -1955,7 +2080,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
const objectTreeProfileCacheKey = driverProfileObjectTreeProfileForConnection(config)?.cacheKey;
|
||||
// objects-v8: object-group listing SQL gained a pg_type branch for
|
||||
// PostgreSQL-family user-defined types; older cached lists miss TYPE nodes.
|
||||
const baseCacheVersion = objectTreeCacheVersion(config, node.schema, config?.db_type === "oracle" ? "objects-v7" : "objects-v8");
|
||||
const baseCacheVersion = objectTreeCacheVersion(config, node.database, node.schema, config?.db_type === "oracle" ? "objects-v7" : "objects-v8");
|
||||
const cacheVersion = objectTreeProfileCacheKey ? `${baseCacheVersion}:${objectTreeProfileCacheKey}` : baseCacheVersion;
|
||||
return schemaCacheKey(node.connectionId || "", node.database || "", node.schema || "", node.type, cacheVersion);
|
||||
}
|
||||
@@ -2194,7 +2319,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
});
|
||||
const refreshedGroup = grouped.find((group) => group.type === options.node.type);
|
||||
const children = refreshedGroup?.children ?? [];
|
||||
return supportsPackageMemberExpansion(databaseType) ? markPackageNodesExpandable(children) : children;
|
||||
return supportsPackageMemberExpansion(databaseType, databaseCompatibilityMode(options.node.connectionId, options.node.database)) ? markPackageNodesExpandable(children) : children;
|
||||
}
|
||||
|
||||
function completionTableDetail(comment: string | null | undefined): string | undefined {
|
||||
@@ -2504,7 +2629,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
objects: supplementalObjects,
|
||||
databaseType,
|
||||
});
|
||||
if (supportsPackageMemberExpansion(databaseType)) {
|
||||
if (supportsPackageMemberExpansion(databaseType, databaseCompatibilityMode(options.connectionId, options.database))) {
|
||||
supplementalChildren = markPackageNodesExpandable(supplementalChildren);
|
||||
}
|
||||
if (supplementalChildren.length === 0) return;
|
||||
@@ -2983,7 +3108,8 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
if (connectionUsesVisibleSchemaFilter(config)) {
|
||||
return schemaCacheKey(connectionId, config.database || "", config.db_type === "oracle" ? "schemas-v2" : "schemas", config.show_system_schemas === true ? "show-system" : "hide-system");
|
||||
}
|
||||
return schemaCacheKey(connectionId, "databases-v2");
|
||||
// v3 includes per-database compatibilityMode used by openGauss A-mode capabilities.
|
||||
return schemaCacheKey(connectionId, "databases-v3");
|
||||
}
|
||||
|
||||
async function hydrateTreeNodeFromCache(node: TreeNode | null, cacheKey: string | null): Promise<boolean> {
|
||||
@@ -3969,6 +4095,9 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
if (config.db_type !== "plugin") {
|
||||
void refreshConnectedDatabaseInfo(id, { ...config, id });
|
||||
await refreshConnectionIdentifierQuote(id, { ...config, id });
|
||||
// Compatibility modes warm asynchronously; the QueryEditor watcher and the
|
||||
// backend's own A-mode/EXISTS validation make a cold map safe.
|
||||
void refreshConnectionDatabaseModes(id, { ...config, id });
|
||||
}
|
||||
if (id !== config.id) markSuccessfulLocalConnectionAttempt(config.id, localAttempt);
|
||||
markSuccessfulLocalConnectionAttempt(id, localAttempt);
|
||||
@@ -4228,6 +4357,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
if (config.db_type !== "plugin") {
|
||||
void refreshConnectedDatabaseInfo(connectionId, config);
|
||||
await refreshConnectionIdentifierQuote(connectionId, config);
|
||||
void refreshConnectionDatabaseModes(connectionId, config);
|
||||
}
|
||||
markSuccessfulLocalConnectionAttempt(connectionId, localAttempt);
|
||||
markConnectionHealthChecked(connectionId);
|
||||
@@ -4479,6 +4609,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
}
|
||||
}
|
||||
const [databases, schemas] = await Promise.all([withMetadataLoadTimeout(connectionId, api.listDatabases(connectionId), "databases"), withMetadataLoadTimeout(connectionId, api.listSchemas(connectionId, "main"), "schemas")]);
|
||||
setDatabaseCompatibilityModesFromDatabases(connectionId, databases);
|
||||
const databaseNames = databases.map((database) => database.name);
|
||||
const visibleNames = filterDatabaseNamesForConnection(databaseNames, config);
|
||||
const visibleNameSet = new Set(visibleNames);
|
||||
@@ -4561,7 +4692,8 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
setChildren(targetNode, children);
|
||||
await savePersistedConnectionTreeChildren(cacheKey, targetNode.children || children);
|
||||
} else {
|
||||
const cacheKey = schemaCacheKey(connectionId, "databases-v2");
|
||||
// v3 persists per-database compatibilityMode for openGauss A-mode features.
|
||||
const cacheKey = schemaCacheKey(connectionId, "databases-v3");
|
||||
if (!options?.force) {
|
||||
const cached = await loadPersistedTreeChildren(node, cacheKey, load);
|
||||
if (cached.hit) {
|
||||
@@ -4571,6 +4703,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
}
|
||||
}
|
||||
const databases = await withMetadataLoadTimeout(connectionId, api.listDatabases(connectionId), "databases");
|
||||
setDatabaseCompatibilityModesFromDatabases(connectionId, databases);
|
||||
const visibleNames = filterDatabaseNamesForConnection(
|
||||
databases.map((database) => database.name),
|
||||
config,
|
||||
@@ -5537,7 +5670,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
const { database, catalog } = node;
|
||||
const configForScope = getConfig(connectionId);
|
||||
const simpleObjectDisplayForScope = useSettingsStore().editorSettings.sidebarObjectDisplay === "simple";
|
||||
const objectTypesForScope = simpleObjectDisplayForScope ? supportedSidebarObjectTypes(configForScope) : undefined;
|
||||
const objectTypesForScope = simpleObjectDisplayForScope ? supportedSidebarObjectTypes(configForScope, database) : undefined;
|
||||
const searchFilterForScope = activeTreeLoadSearchFilter(options);
|
||||
const pageSizeForScope = sidebarObjectGroupPageSize();
|
||||
return runTreeMetadataLoad(
|
||||
@@ -5619,7 +5752,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
async function loadTables(connectionId: string, database: string, schema?: string, options?: LoadTreeOptions) {
|
||||
const configForScope = getConfig(connectionId);
|
||||
const simpleObjectDisplayForScope = useSettingsStore().editorSettings.sidebarObjectDisplay === "simple";
|
||||
const objectTypesForScope = simpleObjectDisplayForScope ? sidebarObjectTypesForScope(configForScope, schema) : undefined;
|
||||
const objectTypesForScope = simpleObjectDisplayForScope ? sidebarObjectTypesForScope(configForScope, database, schema) : undefined;
|
||||
const searchFilter = activeTreeLoadSearchFilter(options);
|
||||
const querySchemaForScope = connectionObjectTreeQuerySchema(configForScope, database, schema);
|
||||
const effectiveSchemaForScope = connectionObjectTreeNodeSchema(configForScope, database, schema);
|
||||
@@ -5635,7 +5768,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
// collapses that to the database node id, so the loaded tables were attached to the
|
||||
// database node instead of the schema node, leaving `(default)` permanently empty.
|
||||
const nodeId = schema != null ? `${connectionId}:${database}:${schema}` : `${connectionId}:${database}`;
|
||||
const cacheKey = schemaCacheKey(connectionId, database, schema || "", objectTreeCacheVersion(configForScope, schema, "objects-simple-v8"));
|
||||
const cacheKey = schemaCacheKey(connectionId, database, schema || "", objectTreeCacheVersion(configForScope, database, schema, "objects-simple-v8"));
|
||||
if (await hydrateTreeNodeFromCache(findNode(treeNodes.value, nodeId), cacheKey)) {
|
||||
void loadTables(connectionId, database, schema, { ...options, force: true }).catch(() => undefined);
|
||||
return;
|
||||
@@ -5675,7 +5808,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
const objectTreeProfile = driverProfileObjectTreeProfileForConnection(config);
|
||||
const isPublicSynonymScope = config?.db_type === "xugu" && isXuguPublicSynonymScope(schema);
|
||||
const isSchedulerJobScope = config?.db_type === "xugu" && isXuguSchedulerJobScope(schema);
|
||||
const baseCacheVersion = objectTreeCacheVersion(config, schema, simpleObjectDisplay ? "objects-simple-v8" : "objects-grouped-v8");
|
||||
const baseCacheVersion = objectTreeCacheVersion(config, database, schema, simpleObjectDisplay ? "objects-simple-v8" : "objects-grouped-v8");
|
||||
const cacheVersion = !simpleObjectDisplay && objectTreeProfile?.cacheKey ? `${baseCacheVersion}:${objectTreeProfile.cacheKey}` : baseCacheVersion;
|
||||
const cacheKey = schemaCacheKey(connectionId, database, schema || "", cacheVersion);
|
||||
const querySchema = connectionObjectTreeQuerySchema(config, database, schema);
|
||||
@@ -5695,7 +5828,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
}
|
||||
}
|
||||
|
||||
const nonTableObjectTypes = simpleObjectDisplay ? sidebarObjectTypesForScope(config, schema).filter((objectType) => objectType !== "TABLE") : [];
|
||||
const nonTableObjectTypes = simpleObjectDisplay ? sidebarObjectTypesForScope(config, database, schema).filter((objectType) => objectType !== "TABLE") : [];
|
||||
let children: TreeNode[];
|
||||
let nextObjectCount: number | undefined;
|
||||
if (simpleObjectDisplay && !isPublicSynonymScope && !isSchedulerJobScope) {
|
||||
@@ -5727,7 +5860,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
connectionId,
|
||||
database,
|
||||
schema: effectiveSchema,
|
||||
objectTypes: sidebarObjectTypesForScope(config, schema),
|
||||
objectTypes: sidebarObjectTypesForScope(config, database, schema),
|
||||
groupOverrides: objectTreeProfile?.groupOverrides,
|
||||
});
|
||||
if (!schema && isPostgresLikeForExtensions(config?.db_type)) {
|
||||
@@ -7148,7 +7281,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
async function loadPackageMembers(node: TreeNode, options?: LoadTreeOptions): Promise<void> {
|
||||
if (node.type !== "package" || !node.connectionId || !node.database) return;
|
||||
const databaseType = effectiveDatabaseTypeForConnection(getConfig(node.connectionId));
|
||||
if (!supportsPackageMemberExpansion(databaseType)) return;
|
||||
if (!supportsPackageMemberExpansion(databaseType, databaseCompatibilityMode(node.connectionId, node.database))) return;
|
||||
const connectionId = node.connectionId;
|
||||
const database = node.database;
|
||||
const schema = node.schema;
|
||||
@@ -7278,7 +7411,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
}
|
||||
|
||||
const ORACLE_SYSTEM_COMPLETION_SCHEMAS = new Set(["SYS", "SYSTEM", "SYSMAN", "DBSNMP", "OUTLN", "XDB", "MDSYS", "CTXSYS", "WMSYS"]);
|
||||
const FILTERED_ROUTINE_COMPLETION_DATABASES = new Set<DatabaseType>(["mysql", "postgres", "sqlserver", "oracle"]);
|
||||
const FILTERED_ROUTINE_COMPLETION_DATABASES = new Set<DatabaseType>(["mysql", "postgres", "sqlserver", "oracle", "opengauss"]);
|
||||
|
||||
function completionPreferredSchema(connectionId: string, preferredSchema?: string): string | undefined {
|
||||
return preferredSchema?.trim() || getConfig(connectionId)?.username?.trim() || undefined;
|
||||
@@ -7730,6 +7863,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
await ensureConnected(connectionId);
|
||||
const config = getConfig(connectionId);
|
||||
const databases = await api.listDatabases(connectionId);
|
||||
setDatabaseCompatibilityModesFromDatabases(connectionId, databases);
|
||||
completionDatabasesCache.value[connectionId] = filterDatabaseNamesForConnection(
|
||||
databases.map((database) => database.name),
|
||||
config,
|
||||
@@ -9062,6 +9196,9 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
ensureEtcdAccessCapabilities,
|
||||
canWriteEtcdKey,
|
||||
connectionIdentifierQuote,
|
||||
databaseCompatibilityMode,
|
||||
ensureDatabaseCompatibilityMode,
|
||||
databaseCompatibilityModes,
|
||||
isTreeNodePinned,
|
||||
orderByPinnedTreeNodes,
|
||||
toggleTreeNodePin,
|
||||
|
||||
@@ -58,7 +58,8 @@ import { appendLargeValueCells, canUseTableDataLargeValuePreview, remapLargeValu
|
||||
import { simpleDataGridOrderByReferencesMissingColumn, sortDataGridRowIndexes, type DataGridSortDirection } from "@/lib/dataGrid/dataGridSort";
|
||||
import { normalizeResultPageSize } from "@/lib/dataGrid/paginationPageSize";
|
||||
import { agentProtocolQueryResultMaxRows, capQueryResultTotal, effectiveQueryResultMaxRows, limitQueryPagination, queryResultLimitReached } from "@/lib/dataGrid/queryResultRowLimit";
|
||||
import { elasticsearchRestRequestRanges, executableStatementRanges, splitSqlStatementRanges } from "@/lib/sql/sqlStatementRanges";
|
||||
import { elasticsearchRestRequestRanges, executableStatementRanges, splitSqlStatementRanges, sqlStatementParameterOptionsForCompatibility } from "@/lib/sql/sqlStatementRanges";
|
||||
import type { SqlParameterOptions } from "@/lib/sql/sqlParameters";
|
||||
import { replaceSqlServerLeadingUseQuery, sqlServerLeadingUseScript, sqlServerUseDatabaseFromStatement } from "@/lib/sql/sqlCompletionLookupTarget";
|
||||
import { classifySqlRisk } from "@/lib/sql/sqlRisk";
|
||||
import { externalSqlFileDisplayTitles, normalizeExternalSqlPath } from "@/lib/sql/sqlFileOpen";
|
||||
@@ -316,8 +317,8 @@ function preservedResultIndex(results: QueryResult[], currentIndex: number | und
|
||||
return currentIndex;
|
||||
}
|
||||
|
||||
function annotateQueryResultSources(results: QueryResult[], sql: string, database: string | undefined, databaseType?: DatabaseType, sourceOffset?: number): { results: QueryResult[]; sqlServerUseDatabase?: string } {
|
||||
const statements = splitSqlStatementRanges(sql, databaseType);
|
||||
function annotateQueryResultSources(results: QueryResult[], sql: string, database: string | undefined, databaseType?: DatabaseType, sourceOffset?: number, parameterOptions?: SqlParameterOptions): { results: QueryResult[]; sqlServerUseDatabase?: string } {
|
||||
const statements = splitSqlStatementRanges(sql, databaseType, parameterOptions);
|
||||
let statementIndex = 0;
|
||||
let sourceDatabase = database;
|
||||
let sqlServerUseDatabase: string | undefined;
|
||||
@@ -356,8 +357,8 @@ function clearLiveBatchSqlExecution(tab: QueryTab, executionId: string) {
|
||||
if (liveBatchSqlExecutions.get(tab)?.executionId === executionId) liveBatchSqlExecutions.delete(tab);
|
||||
}
|
||||
|
||||
function createBatchSqlExecution(executionId: string, editorSql: string, submittedSql: string, databaseType: DatabaseType | undefined, sourceOffset: number | undefined, executionTarget: MultiDbExecutionTarget): BatchSqlExecution | undefined {
|
||||
const statements = databaseType === "mongodb" ? splitMongoCommandRanges(submittedSql).map(({ from, to, text }) => ({ from, to, sql: text })) : splitSqlStatementRanges(submittedSql, databaseType);
|
||||
function createBatchSqlExecution(executionId: string, editorSql: string, submittedSql: string, databaseType: DatabaseType | undefined, sourceOffset: number | undefined, executionTarget: MultiDbExecutionTarget, parameterOptions?: SqlParameterOptions): BatchSqlExecution | undefined {
|
||||
const statements = databaseType === "mongodb" ? splitMongoCommandRanges(submittedSql).map(({ from, to, text }) => ({ from, to, sql: text })) : splitSqlStatementRanges(submittedSql, databaseType, parameterOptions);
|
||||
if (statements.length === 0) return undefined;
|
||||
if (statements.length > 1 && databaseType && NON_STREAMING_BATCH_DATABASE_TYPES.has(databaseType)) return undefined;
|
||||
const offset = sourceOffset ?? 0;
|
||||
@@ -5873,19 +5874,31 @@ export const useQueryStore = defineStore("query", () => {
|
||||
const executionCatalog = resumedExecutionTarget ? resumedExecutionTarget.catalog : targetContext ? (targetContext.scope === "catalog" ? targetContext.catalog : undefined) : (executionTarget?.catalog ?? (tab.mode === "data" ? tab.tableMeta?.catalog : tab.catalog));
|
||||
const contextDatabase = databaseTargetContext?.database;
|
||||
const targetDatabase = resumedExecutionTarget ? resumedExecutionTarget.database : targetContext?.scope === "connection" ? "" : (contextDatabase ?? executionTarget?.database ?? tab.database);
|
||||
if (effectiveDbType === "opengauss") {
|
||||
await connStore.ensureDatabaseCompatibilityMode(executionConnectionId, targetDatabase || conn?.database);
|
||||
}
|
||||
const targetSchema = resumedExecutionTarget ? resumedExecutionTarget.schema : targetContext?.scope === "connection" ? undefined : (databaseTargetContext?.schema ?? executionTarget?.schema ?? tab.schema);
|
||||
const executionDatabase = dataTabExecutionDatabase(conn, targetDatabase, executionCatalog);
|
||||
const sqlStatementParameterOptions = sqlStatementParameterOptionsForCompatibility(effectiveDbType, effectiveDbType === "opengauss" ? connStore.databaseCompatibilityMode(executionConnectionId, targetDatabase || conn?.database) : undefined);
|
||||
const useAgentCursor = usesAgentCursorForQuery(conn?.db_type, conn?.driver_profile);
|
||||
const queryTimeoutSecs = queryTimeoutSecsForConnection(conn, settingsStore.editorSettings.globalQueryTimeoutSecs);
|
||||
if (!batchResume) {
|
||||
const statementExecution =
|
||||
tab.mode === "query"
|
||||
? createBatchSqlExecution(executionId, tab.sql, sql, effectiveDbType, options?.sourceOffset, {
|
||||
connectionId: executionConnectionId,
|
||||
catalog: executionCatalog,
|
||||
database: targetDatabase,
|
||||
schema: targetSchema,
|
||||
})
|
||||
? createBatchSqlExecution(
|
||||
executionId,
|
||||
tab.sql,
|
||||
sql,
|
||||
effectiveDbType,
|
||||
options?.sourceOffset,
|
||||
{
|
||||
connectionId: executionConnectionId,
|
||||
catalog: executionCatalog,
|
||||
database: targetDatabase,
|
||||
schema: targetSchema,
|
||||
},
|
||||
sqlStatementParameterOptions,
|
||||
)
|
||||
: undefined;
|
||||
tab.batchSqlExecution = statementExecution && (tab.autoCommit !== false || statementExecution.total === 1) ? statementExecution : undefined;
|
||||
if (tab.batchSqlExecution) liveBatchSqlExecutions.set(tab, tab.batchSqlExecution);
|
||||
@@ -6536,7 +6549,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
// could not rewrite the SQL for these engines, so a plain execution
|
||||
// would return the first page again (#8993).
|
||||
const isOffsetJumpPage = typeof pageOffset === "number" && pageOffset > 0 && !options?.pagination?.sessionId;
|
||||
const frontendTimeoutSecs = frontendQueryTimeoutSecsForSql(sqlToExecute, effectiveDbType, queryTimeoutSecs);
|
||||
const frontendTimeoutSecs = frontendQueryTimeoutSecsForSql(sqlToExecute, effectiveDbType, queryTimeoutSecs, sqlStatementParameterOptions);
|
||||
const sourceLabelDatabase = targetDatabase || conn?.database;
|
||||
const executionClientSessionId = options?.pagination?.clientSessionId ?? (tab.mode === "query" || tab.mode === "data" ? tabClientSessionId(tab) : undefined);
|
||||
const currentBeforeDispatch = findExecutionTab(id);
|
||||
@@ -6695,7 +6708,14 @@ export const useQueryStore = defineStore("query", () => {
|
||||
} else {
|
||||
executionPromise = executeWithoutManualTransaction();
|
||||
}
|
||||
const annotatedResults = annotateQueryResultSources(markQueryResultsRowsRaw(await withFrontendQueryTimeout(executionPromise, frontendTimeoutSecs, t("editor.queryTimeoutError", { seconds: frontendTimeoutSecs }))), queryBaseSql, sourceLabelDatabase, effectiveDbType, options?.sourceOffset);
|
||||
const annotatedResults = annotateQueryResultSources(
|
||||
markQueryResultsRowsRaw(await withFrontendQueryTimeout(executionPromise, frontendTimeoutSecs, t("editor.queryTimeoutError", { seconds: frontendTimeoutSecs }))),
|
||||
queryBaseSql,
|
||||
sourceLabelDatabase,
|
||||
effectiveDbType,
|
||||
options?.sourceOffset,
|
||||
sqlStatementParameterOptions,
|
||||
);
|
||||
const results = offsetBatchQueryResultIndexes(annotatedResults.results, batchResume?.startStatementIndex ?? 0);
|
||||
reconcileBatchSqlResults(tab, executionId, results);
|
||||
// Oracle-only sticky state aggregation. Only the initial manual execution
|
||||
|
||||
Reference in new issue
Block a user