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
@@ -294,7 +294,11 @@ const settingsStore = useSettingsStore();
|
||||
|
||||
function sqlStatementParameterOptions() {
|
||||
const toggles = resolveSqlVariableSyntaxToggles(settingsStore.editorSettings.sqlVariableSyntaxOverrides, props.databaseType, settingsStore.editorSettings.sqlVariableSubstitutionEnabled);
|
||||
return { databaseType: props.databaseType, enabledSyntaxes: enabledSqlParameterSyntaxes(toggles) };
|
||||
return {
|
||||
databaseType: props.databaseType,
|
||||
compatibilityMode: props.databaseType === "opengauss" ? connectionStore.databaseCompatibilityMode(props.connectionId, props.database) : undefined,
|
||||
enabledSyntaxes: enabledSqlParameterSyntaxes(toggles),
|
||||
};
|
||||
}
|
||||
const { isDark, themePalette, activeCustomUiColors } = useTheme();
|
||||
const { t } = useI18n();
|
||||
@@ -3473,7 +3477,7 @@ async function refreshSemanticDiagnostics(options: { preserveOutsideRanges?: boo
|
||||
if (props.databaseType !== "sqlserver") {
|
||||
executableStatementRangeCache = executableStatementRangeCacheForDoc(executableStatementRangeCache, currentView.state.doc, props.databaseType, sqlStatementParameterOptions());
|
||||
}
|
||||
const diagnosticRanges = sqlSemanticDiagnosticRangesForViewport(sql, visibleRanges, props.databaseType, props.databaseType === "sqlserver" ? undefined : executableStatementRangeCache?.ranges);
|
||||
const diagnosticRanges = sqlSemanticDiagnosticRangesForViewport(sql, visibleRanges, props.databaseType, props.databaseType === "sqlserver" ? undefined : executableStatementRangeCache?.ranges, sqlStatementParameterOptions());
|
||||
if (diagnosticRanges.length === 0) {
|
||||
if (!options.preserveOutsideRanges) setSemanticDiagnostics([]);
|
||||
return;
|
||||
@@ -5334,9 +5338,31 @@ function shouldLoadCompletionObjects(completionContext: ReturnType<typeof getSql
|
||||
return routineContext && !isReferencedTableQualifier(completionContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Databases whose qualified routine completion treats the first qualifier as a
|
||||
* package (Oracle) or as a package-or-schema in A compatibility mode
|
||||
* (openGauss). The backend re-validates: non-package parents fall through to
|
||||
* the ordinary routine search, so routing is safe even when the compatibility
|
||||
* map is not yet warm. openGauss keeps the package-aware routing while the
|
||||
* mode is unknown (cold start, so A-mode completion works immediately after
|
||||
* connect) and once the mode is known to be A; a known B/PG mode skips the
|
||||
* extra package-style queries entirely.
|
||||
*/
|
||||
function usesPackageAwareRoutineCompletion(): boolean {
|
||||
if (props.databaseType === "oracle") return true;
|
||||
if (props.databaseType !== "opengauss") return false;
|
||||
const mode = connectionStore.databaseCompatibilityMode(props.connectionId, props.database)?.trim().toUpperCase();
|
||||
return mode === undefined || mode === "A";
|
||||
}
|
||||
|
||||
function oracleRoutineCompletionTargets(completionContext: ReturnType<typeof getSqlCompletionContext>): RoutineCompletionTarget[] {
|
||||
const parts = (completionContext.qualifierParts?.length ? completionContext.qualifierParts : completionContext.qualifier?.split("."))?.filter(Boolean) ?? [];
|
||||
if (parts.length === 0) return [{ schema: props.schema, globalSearch: true }];
|
||||
if (parts.length === 0) {
|
||||
// Oracle resolves unqualified routines across all schemas (owner semantics).
|
||||
// openGauss keeps the PG search_path scope for the unqualified case.
|
||||
if (props.databaseType === "oracle") return [{ schema: props.schema, globalSearch: true }];
|
||||
return [{ schema: props.schema }];
|
||||
}
|
||||
if (parts.length === 1) {
|
||||
return [{ schema: props.schema, parentName: parts[0] }, { schema: parts[0] }];
|
||||
}
|
||||
@@ -5353,14 +5379,14 @@ function routineCompletionTargetForContext(completionContext: ReturnType<typeof
|
||||
}
|
||||
|
||||
function routineCompletionScopeForContext(completionContext: ReturnType<typeof getSqlCompletionContext>, scope: CompletionMetadataScope): CompletionMetadataScope {
|
||||
if (props.databaseType === "oracle") return scope;
|
||||
if (usesPackageAwareRoutineCompletion()) return scope;
|
||||
const target = routineCompletionTargetForContext(completionContext, scope);
|
||||
return { database: target.database, schema: target.schema };
|
||||
}
|
||||
|
||||
function lookupLocalCompletionObjectsForContext(completionContext: ReturnType<typeof getSqlCompletionContext>, scope: CompletionMetadataScope): SqlCompletionObject[] {
|
||||
if (!props.connectionId || props.database == null) return [];
|
||||
if (props.databaseType === "oracle") {
|
||||
if (usesPackageAwareRoutineCompletion()) {
|
||||
return connectionStore.lookupLocalCompletionObjects(props.connectionId, scope.database, completionContext.prefix, MAX_COMPLETION_TABLES);
|
||||
}
|
||||
const target = routineCompletionTargetForContext(completionContext, scope);
|
||||
@@ -5370,7 +5396,7 @@ function lookupLocalCompletionObjectsForContext(completionContext: ReturnType<ty
|
||||
async function listCompletionObjectsForContext(completionContext: ReturnType<typeof getSqlCompletionContext>, scope: CompletionMetadataScope): Promise<SqlCompletionObject[]> {
|
||||
if (!props.connectionId || props.database == null) return [];
|
||||
const objectKinds = completionObjectKindsForContext(completionContext);
|
||||
if (props.databaseType !== "oracle") {
|
||||
if (!usesPackageAwareRoutineCompletion()) {
|
||||
const target = routineCompletionTargetForContext(completionContext, scope);
|
||||
return connectionStore.listCompletionObjects(props.connectionId, target.database, target.mask, MAX_COMPLETION_TABLES, target.schema, undefined, false, scope.schema, objectKinds);
|
||||
}
|
||||
@@ -7134,6 +7160,23 @@ watch([() => props.databaseType, () => props.dialect, () => props.syntaxDialect,
|
||||
});
|
||||
});
|
||||
|
||||
// openGauss compatibility mode is loaded asynchronously from the backend into a
|
||||
// dedicated store map (not the sidebar tree). A restored tab may open before the
|
||||
// map is warm; when the mode arrives, re-derive statement boundaries and
|
||||
// diagnostics so package DDL is parsed with the correct PL/SQL rules.
|
||||
watch(
|
||||
() => (props.databaseType === "opengauss" ? connectionStore.databaseCompatibilityMode(props.connectionId, props.database) : undefined),
|
||||
(now, before) => {
|
||||
if (now === before) return;
|
||||
executableStatementRangeCache = null;
|
||||
if (props.databaseType !== "opengauss") return;
|
||||
if (!view.value) return;
|
||||
refreshCompletionCache();
|
||||
setSemanticDiagnostics([]);
|
||||
scheduleSemanticDiagnostics(0);
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.forceWordWrap,
|
||||
() => {
|
||||
|
||||
@@ -176,6 +176,7 @@ import type { QueryTab, TableInfoTab, TreeNode, VectorCollectionMeta } from "@/t
|
||||
import type { SqlObjectNavigationTarget } from "@/lib/sql/sqlNavigation";
|
||||
import { sqlFormatDialectForDbType, type SqlFormatDialect } from "@/lib/sql/sqlFormatter";
|
||||
import { productionContextForDatabase } from "@/lib/database/productionSafety";
|
||||
import { sqlStatementParameterOptionsForCompatibility } from "@/lib/sql/sqlStatementRanges";
|
||||
import { connectionIsEffectivelyReadOnly } from "@/lib/database/readOnlyWriteAccess";
|
||||
|
||||
type DataGridHandle = DataGridColumnLayoutHandle & {
|
||||
@@ -342,6 +343,9 @@ const activeEffectiveDatabaseType = computed(() => effectiveDatabaseTypeForConne
|
||||
const canOpenTableImport = computed(() => supportsTableImport(activeEffectiveDatabaseType.value));
|
||||
const activeVectorConnection = computed(() => connectionStore.getConfig(props.activeTab.connectionId) ?? props.activeConnection);
|
||||
const activeDataTabExecutionDatabase = computed(() => dataTabExecutionDatabase(props.activeConnection, props.activeTab.database, activeDataTabTableMeta.value?.catalog));
|
||||
const activeSqlStatementParameterOptions = computed(() =>
|
||||
sqlStatementParameterOptionsForCompatibility(activeEffectiveDatabaseType.value, activeEffectiveDatabaseType.value === "opengauss" ? connectionStore.databaseCompatibilityMode(activeResultConnectionId.value, activeResultDatabase.value) : undefined),
|
||||
);
|
||||
const activeProductionContext = computed(() => productionContextForDatabase(props.activeConnection, props.activeTab.database));
|
||||
const productionWatermarkText = computed(() => (locale.value.startsWith("zh") ? "生产环境" : "PROD"));
|
||||
const productionSessionDetail = computed(() => {
|
||||
@@ -474,6 +478,7 @@ const activeStatementExecutionMarkers = computed(() =>
|
||||
props.activeTab.resultBaseSql || props.activeTab.lastExecutedSql || props.activeTab.sql,
|
||||
props.activeTab.resultEditorFingerprint ?? "",
|
||||
props.activeTab.batchSqlExecution,
|
||||
activeSqlStatementParameterOptions.value,
|
||||
),
|
||||
);
|
||||
const activeElasticsearchJsonResponse = computed(() => elasticsearchJsonResponseForResult(activeEffectiveDatabaseType.value, activeResultSql.value, props.activeTab.result));
|
||||
@@ -1146,7 +1151,7 @@ function toggleResultAutoSave() {
|
||||
function selectResultItem(item: (typeof visibleResultItems.value)[number]) {
|
||||
queryStore.setActiveResultIndex(props.activeTab.id, item.index);
|
||||
emit("update:activeOutputView", props.activeTab.id, "result");
|
||||
const range = resultSourceRange(props.activeTab.sql, item.result, item.index, activeEffectiveDatabaseType.value) ?? null;
|
||||
const range = resultSourceRange(props.activeTab.sql, item.result, item.index, activeEffectiveDatabaseType.value, activeSqlStatementParameterOptions.value) ?? null;
|
||||
if (queryEditorRef.value) {
|
||||
nextTick(() => queryEditorRef.value?.previewStatementRange(range));
|
||||
} else {
|
||||
@@ -1158,7 +1163,7 @@ function executionSummaryItemRange(item: ExecutionSummaryItem) {
|
||||
if (typeof item.sourceFrom === "number" && typeof item.sourceTo === "number" && item.sql && props.activeTab.sql.slice(item.sourceFrom, item.sourceTo) === item.sql) {
|
||||
return { from: item.sourceFrom, to: item.sourceTo };
|
||||
}
|
||||
return item.result ? resultSourceRange(props.activeTab.sql, item.result, item.statementIndex, activeEffectiveDatabaseType.value) : undefined;
|
||||
return item.result ? resultSourceRange(props.activeTab.sql, item.result, item.statementIndex, activeEffectiveDatabaseType.value, activeSqlStatementParameterOptions.value) : undefined;
|
||||
}
|
||||
|
||||
function previewExecutionSummaryItem(item: ExecutionSummaryItem) {
|
||||
|
||||
@@ -903,7 +903,8 @@ async function toggle(requestId = beginNavigationRequest()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.type === "package" && node.connectionId && supportsPackageMemberExpansion(currentDatabaseType())) {
|
||||
const packageDatabaseType = currentDatabaseType();
|
||||
if (node.type === "package" && node.connectionId && supportsPackageMemberExpansion(packageDatabaseType, packageDatabaseType === "opengauss" ? connectionStore.databaseCompatibilityMode(node.connectionId, node.database) : undefined)) {
|
||||
await connectionStore.loadPackageMembers(node);
|
||||
emitNodeToggled(node, wasExpanded);
|
||||
return;
|
||||
|
||||
@@ -15,7 +15,7 @@ import { defaultViewForResult } from "@/lib/query/queryResultDefaultView";
|
||||
import { isQueryExecutionErrorResult } from "@/lib/query/queryResultError";
|
||||
import { classifyRedisCommandSafety } from "@/lib/redis/redisCommandSafety";
|
||||
import { isSqlExecutionSnapshot, resolveExecutableSql, type SqlExecutionOverride, type SqlExecutionSnapshot } from "@/lib/sql/sqlExecutionTarget";
|
||||
import { isElasticsearchRestRequestText, parseElasticsearchRestRequestTarget, splitSqlStatementRanges } from "@/lib/sql/sqlStatementRanges";
|
||||
import { isElasticsearchRestRequestText, parseElasticsearchRestRequestTarget, splitSqlStatementRanges, sqlStatementParameterOptionsForCompatibility } from "@/lib/sql/sqlStatementRanges";
|
||||
import { extractSqlParameterDescriptors, type SqlParameterDescriptor, type SqlParameterSyntax } from "@/lib/sql/sqlParameters";
|
||||
import { expandSqlVariables } from "@/lib/sql/sqlVariables";
|
||||
import { enabledSqlParameterSyntaxes, resolveSqlVariableSyntaxToggles } from "@/lib/sql/sqlVariableSyntax";
|
||||
@@ -393,7 +393,7 @@ export function useSqlExecution(deps: {
|
||||
cancelEditorViewportRequest(options.editorViewportRequestId);
|
||||
return;
|
||||
}
|
||||
const statementCount = splitSqlStatementRanges(sql, executionDatabaseType).length;
|
||||
const statementCount = splitSqlStatementRanges(sql, executionDatabaseType, sqlStatementParameterOptionsForCompatibility(executionDatabaseType, executionDatabaseType === "opengauss" ? connectionStore.databaseCompatibilityMode(tab.connectionId, tab.database) : undefined)).length;
|
||||
// Output-view switching belongs to the tab the user is looking at — both
|
||||
// when the query starts and when it finishes.
|
||||
if (deps.activeTab.value?.id === executionTabId) {
|
||||
|
||||
@@ -130,6 +130,15 @@ describe("databaseObjectCapabilities", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("exposes packages only for openGauss A compatibility mode", () => {
|
||||
expect(sidebarObjectKindsForDatabase("opengauss", "A")).toEqual(expect.arrayContaining(["PACKAGE", "PACKAGE_BODY"]));
|
||||
expect(sidebarObjectKindsForDatabase("opengauss", "a")).toContain("PACKAGE");
|
||||
for (const mode of ["B", "C", "PG", undefined]) {
|
||||
expect(sidebarObjectKindsForDatabase("opengauss", mode), String(mode)).not.toContain("PACKAGE");
|
||||
}
|
||||
expect(databaseObjectCapabilities("opengauss", "A").sourceReadable).toEqual(expect.arrayContaining(["PACKAGE", "PACKAGE_BODY"]));
|
||||
});
|
||||
|
||||
it("enables custom type details only for verified PG-family databases", () => {
|
||||
for (const dbType of ["postgres", "opengauss", "gaussdb", "kingbase", "vastbase"] as const) {
|
||||
expect(customTypeCapabilities(dbType), dbType).toEqual({ details: true, members: true, ddl: true });
|
||||
|
||||
@@ -17,6 +17,11 @@ describe("buildDatabaseTreeNodes", () => {
|
||||
expect(node.id).toBe("conn-1:analytics");
|
||||
});
|
||||
|
||||
it("carries database compatibility mode into database nodes", () => {
|
||||
const [node] = buildDatabaseTreeNodes("conn-1", [{ name: "oracle_mode", compatibility_mode: "A" }]);
|
||||
expect(node.compatibilityMode).toBe("A");
|
||||
});
|
||||
|
||||
it("shortens the Cloud Spanner label but keeps the resource path as the node identity", () => {
|
||||
const [node] = buildDatabaseTreeNodes("conn-1", databases(SPANNER_PATH), { displayLabel: spannerDisplayDatabase });
|
||||
|
||||
|
||||
@@ -25,6 +25,13 @@ describe("queryTimeout", () => {
|
||||
expect(frontendQueryTimeoutSecsForSql("UPDATE sample_records SET state = 'ready' RETURNING id", "postgres", 30)).toBe(0);
|
||||
});
|
||||
|
||||
it("uses conservative package splitting for an unresolved openGauss mode", () => {
|
||||
const packageSpec = `CREATE OR REPLACE PACKAGE pkg_utils AS
|
||||
FUNCTION get_version RETURN VARCHAR2;
|
||||
END pkg_utils;`;
|
||||
expect(frontendQueryTimeoutSecsForSql(`${packageSpec}\n/\nSELECT 1;`, "opengauss", 30)).toBe(120);
|
||||
});
|
||||
|
||||
it("keeps the frontend guard for non-row PostgreSQL statements", () => {
|
||||
expect(frontendQueryTimeoutSecsForSql("UPDATE sample_records SET state = 'ready'", "postgres", 30)).toBe(60);
|
||||
expect(frontendQueryTimeoutSecsForSql("INSERT INTO sample_records(note) VALUES ('RETURNING is text')", "postgres", 30)).toBe(60);
|
||||
|
||||
@@ -616,6 +616,27 @@ END pkg_utils_without_replace;`;
|
||||
expect(rangeSqlTexts(splitSqlStatementRanges(`${packageSpecWithoutReplace}\nSELECT 1;`, "xugu"))).toEqual([packageSpecWithoutReplace, "SELECT 1"]);
|
||||
});
|
||||
|
||||
it("keeps openGauss packages together while compatibility metadata is unknown", () => {
|
||||
const packageSpec = `CREATE OR REPLACE PACKAGE pkg_utils AS
|
||||
FUNCTION get_version RETURN VARCHAR2;
|
||||
PROCEDURE log_message(msg VARCHAR2);
|
||||
END pkg_utils;`;
|
||||
const script = `${packageSpec}\n/\nSELECT 1;`;
|
||||
|
||||
expect(rangeSqlTexts(splitSqlStatementRanges(script, "opengauss"))).toEqual([packageSpec, "SELECT 1"]);
|
||||
});
|
||||
|
||||
it("splits openGauss A-mode packages without changing PG mode", () => {
|
||||
const packageSpec = `CREATE OR REPLACE PACKAGE pkg_utils AS
|
||||
FUNCTION get_version RETURN VARCHAR2;
|
||||
PROCEDURE log_message(msg VARCHAR2);
|
||||
END pkg_utils;`;
|
||||
const script = `${packageSpec}\n/\nSELECT 1;`;
|
||||
|
||||
expect(rangeSqlTexts(splitSqlStatementRanges(script, "opengauss", { compatibilityMode: "A" }))).toEqual([packageSpec, "SELECT 1"]);
|
||||
expect(rangeSqlTexts(splitSqlStatementRanges(script, "opengauss", { compatibilityMode: "PG" }))).not.toEqual([packageSpec, "SELECT 1"]);
|
||||
});
|
||||
|
||||
it("splits a declaration-only Oracle package body before following DML", () => {
|
||||
const packageBody = `CREATE OR REPLACE PACKAGE BODY packageName IS
|
||||
null;
|
||||
|
||||
@@ -18,6 +18,7 @@ const MYSQL_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "PROCEDURE", "FUNCT
|
||||
// covers user-created types (enum/domain/composite/range/multirange/base);
|
||||
// relation auto-generated row types stay hidden.
|
||||
const POSTGRES_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "MATERIALIZED_VIEW", "PROCEDURE", "FUNCTION", "SEQUENCE", "TYPE"];
|
||||
const OPENGAUSS_A_OBJECTS: SidebarObjectKind[] = [...POSTGRES_OBJECTS, "PACKAGE", "PACKAGE_BODY"];
|
||||
|
||||
// KWDB is routed through the PostgreSQL pool but its pg_type catalog
|
||||
// compatibility is not verified yet, so it stays on the pre-TYPE object set.
|
||||
@@ -114,8 +115,8 @@ function isSourceReadableObjectKind(kind: SidebarObjectKind, dbType?: DatabaseTy
|
||||
return true;
|
||||
}
|
||||
|
||||
export function databaseObjectCapabilities(dbType?: DatabaseType): DatabaseObjectCapabilities {
|
||||
const sidebarObjects = sidebarObjectKindsForDatabase(dbType);
|
||||
export function databaseObjectCapabilities(dbType?: DatabaseType, compatibilityMode?: string): DatabaseObjectCapabilities {
|
||||
const sidebarObjects = sidebarObjectKindsForDatabase(dbType, compatibilityMode);
|
||||
return {
|
||||
sidebarObjects,
|
||||
sourceReadable: sidebarObjects.filter((kind) => isSourceReadableObjectKind(kind, dbType)),
|
||||
@@ -166,8 +167,9 @@ export function schemaDiffRoutineObjectTypesIntersection(sourceDbType?: Database
|
||||
return schemaDiffRoutineObjectTypes(targetDbType).filter((kind) => sourceTypes.has(kind));
|
||||
}
|
||||
|
||||
export function sidebarObjectKindsForDatabase(dbType?: DatabaseType): SidebarObjectKind[] {
|
||||
export function sidebarObjectKindsForDatabase(dbType?: DatabaseType, compatibilityMode?: string): SidebarObjectKind[] {
|
||||
if (!dbType) return [...TABLE_VIEW_OBJECTS];
|
||||
if (dbType === "opengauss" && compatibilityMode?.trim().toUpperCase() === "A") return [...OPENGAUSS_A_OBJECTS];
|
||||
return DATABASE_TYPE_OBJECTS.get(dbType) ?? [...ROUTINE_OBJECTS];
|
||||
}
|
||||
|
||||
@@ -202,8 +204,8 @@ export function customTypeCapabilities(dbType?: DatabaseType): CustomTypeCapabil
|
||||
return { details: supported, members: supported, ddl: supported };
|
||||
}
|
||||
|
||||
export function supportsPackageMemberExpansion(dbType?: DatabaseType): boolean {
|
||||
return !!dbType && PACKAGE_MEMBER_EXPANSION_DATABASES.has(dbType);
|
||||
export function supportsPackageMemberExpansion(dbType?: DatabaseType, compatibilityMode?: string): boolean {
|
||||
return !!dbType && (PACKAGE_MEMBER_EXPANSION_DATABASES.has(dbType) || (dbType === "opengauss" && compatibilityMode?.trim().toUpperCase() === "A"));
|
||||
}
|
||||
|
||||
export function normalizeSidebarObjectKind(type: string): SidebarObjectKind {
|
||||
|
||||
@@ -38,6 +38,7 @@ export function buildDatabaseTreeNodes(
|
||||
type: "database" as const,
|
||||
connectionId,
|
||||
database: name,
|
||||
compatibilityMode: db.compatibility_mode?.trim() || undefined,
|
||||
isExpanded: false,
|
||||
children: [],
|
||||
},
|
||||
@@ -87,6 +88,7 @@ export function buildDuckDbConnectionTreeNodes(connectionId: string, databases:
|
||||
type: "database" as const,
|
||||
connectionId,
|
||||
database: name,
|
||||
compatibilityMode: db.compatibility_mode?.trim() || undefined,
|
||||
isExpanded: false,
|
||||
children: [],
|
||||
},
|
||||
|
||||
@@ -25,7 +25,7 @@ export function executableStatementRangeCacheForDoc(
|
||||
): ExecutableStatementRangeCache {
|
||||
const parameterOptions = typeof parameterOptionsOrParse === "function" ? undefined : parameterOptionsOrParse;
|
||||
const parse = typeof parameterOptionsOrParse === "function" ? parameterOptionsOrParse : customParse;
|
||||
const parameterSyntaxKey = parameterOptions?.enabledSyntaxes ? parameterOptions.enabledSyntaxes.join(",") : "*";
|
||||
const parameterSyntaxKey = `${parameterOptions?.enabledSyntaxes ? parameterOptions.enabledSyntaxes.join(",") : "*"}|compat=${parameterOptions?.compatibilityMode?.trim().toUpperCase() ?? ""}`;
|
||||
if (cache?.doc === doc && cache.databaseType === databaseType && cache.parameterSyntaxKey === parameterSyntaxKey) return cache;
|
||||
|
||||
const byStart = new Map<number, SqlTextRange>();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { splitSqlStatementRanges } from "@/lib/sql/sqlStatementRanges";
|
||||
import { splitSqlStatementRanges, sqlStatementParameterOptionsForCompatibility } from "@/lib/sql/sqlStatementRanges";
|
||||
import type { SqlParameterOptions } from "@/lib/sql/sqlParameters";
|
||||
import { tokenizeSqlSemantic } from "@/lib/sql/semantic/tokens";
|
||||
import { DEFAULT_QUERY_TIMEOUT_SECS } from "@/lib/connection/timeoutLimits";
|
||||
import type { ConnectionConfig, DatabaseType } from "@/types/database";
|
||||
@@ -46,7 +47,7 @@ export function queryTimeoutSecsForConcurrentIndex(configuredTimeoutSecs: number
|
||||
return Math.max(configuredTimeoutSecs, CONCURRENT_INDEX_QUERY_TIMEOUT_SECS);
|
||||
}
|
||||
|
||||
export function frontendQueryTimeoutSecsForSql(sql: string, databaseType: DatabaseType | undefined, queryTimeoutSecs: number): number {
|
||||
export function frontendQueryTimeoutSecsForSql(sql: string, databaseType: DatabaseType | undefined, queryTimeoutSecs: number, parameterOptions?: SqlParameterOptions): number {
|
||||
if (queryTimeoutSecs === 0) return 0;
|
||||
|
||||
// PostgreSQL applies the configured timeout while the backend receives rows
|
||||
@@ -56,7 +57,7 @@ export function frontendQueryTimeoutSecsForSql(sql: string, databaseType: Databa
|
||||
if (databaseType === "postgres" && postgresQueryMayReturnRows(sql, databaseType)) return 0;
|
||||
|
||||
const baseTimeoutSecs = Math.max(queryTimeoutSecs * 2, 60);
|
||||
const statementCount = Math.max(splitSqlStatementRanges(sql, databaseType).length, 1);
|
||||
const statementCount = Math.max(splitSqlStatementRanges(sql, databaseType, parameterOptions ?? sqlStatementParameterOptionsForCompatibility(databaseType)).length, 1);
|
||||
return baseTimeoutSecs * statementCount;
|
||||
}
|
||||
|
||||
@@ -67,7 +68,7 @@ export function frontendQueryTimeoutDelayMs(timeoutSecs: number): number | undef
|
||||
}
|
||||
|
||||
function postgresQueryMayReturnRows(sql: string, databaseType: DatabaseType): boolean {
|
||||
return splitSqlStatementRanges(sql, databaseType).some(({ sql: statement }) => {
|
||||
return splitSqlStatementRanges(sql, databaseType, sqlStatementParameterOptionsForCompatibility(databaseType)).some(({ sql: statement }) => {
|
||||
const tokens = tokenizeSqlSemantic(statement, "postgres");
|
||||
const firstKeyword = tokens.find((token) => token.kind === "word" && token.depth === 0)?.normalized;
|
||||
if (!firstKeyword) return false;
|
||||
|
||||
@@ -24,14 +24,14 @@ export interface SqlSemanticDiagnosticVisibleRange {
|
||||
to: number;
|
||||
}
|
||||
|
||||
export function sqlSemanticDiagnosticRangesForViewport(sql: string, visibleRanges: readonly SqlSemanticDiagnosticVisibleRange[], databaseType?: DatabaseType, cachedStatements?: readonly SqlTextRange[]): SqlTextRange[] {
|
||||
const statements = databaseType === "sqlserver" ? sqlServerSemanticDiagnosticRanges(sql) : (cachedStatements ?? executableStatementRanges(sql, databaseType));
|
||||
export function sqlSemanticDiagnosticRangesForViewport(sql: string, visibleRanges: readonly SqlSemanticDiagnosticVisibleRange[], databaseType?: DatabaseType, cachedStatements?: readonly SqlTextRange[], parameterOptions?: { compatibilityMode?: string }): SqlTextRange[] {
|
||||
const statements = databaseType === "sqlserver" ? sqlServerSemanticDiagnosticRanges(sql) : (cachedStatements ?? executableStatementRanges(sql, databaseType, parameterOptions));
|
||||
if (statements.length === 0 || visibleRanges.length === 0) return [];
|
||||
|
||||
const selected: SqlTextRange[] = [];
|
||||
const seen = new Set<string>();
|
||||
for (const statement of statements) {
|
||||
if (isOraclePlSqlStatement(statement.sql, databaseType)) continue;
|
||||
if (isOraclePlSqlStatement(statement.sql, databaseType, parameterOptions)) continue;
|
||||
if (!visibleRanges.some((visibleRange) => rangesIntersect(statement, visibleRange))) continue;
|
||||
const key = `${statement.from}:${statement.to}`;
|
||||
if (seen.has(key)) continue;
|
||||
|
||||
@@ -55,6 +55,8 @@ type TriggerPseudoRecordName = "new" | "old" | "parent" | "eventinfo";
|
||||
|
||||
export interface SqlParameterOptions {
|
||||
databaseType?: DatabaseType;
|
||||
/** Database-level compatibility mode, for example openGauss A/B/C/PG. */
|
||||
compatibilityMode?: string;
|
||||
// Which placeholder syntaxes are recognized. Undefined enables all of them.
|
||||
enabledSyntaxes?: readonly SqlParameterSyntax[];
|
||||
}
|
||||
|
||||
@@ -499,7 +499,7 @@ export function splitSqlStatementRanges(sql: string, databaseType?: DatabaseType
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (isOracleLikeDatabase(databaseType) && isAtLineStart(sql, i) && isSlashLine(sql, i)) {
|
||||
if (isOracleLikeDatabase(databaseType, parameterOptions) && isAtLineStart(sql, i) && isSlashLine(sql, i)) {
|
||||
const lineEnd = findLineEnd(sql, i);
|
||||
flush(i);
|
||||
i = nextLineStart(sql, lineEnd);
|
||||
@@ -579,7 +579,7 @@ export function splitSqlStatementRanges(sql: string, databaseType?: DatabaseType
|
||||
const tagMatch = /^\$[A-Za-z_0-9]*\$/.exec(sql.slice(i));
|
||||
if (tagMatch) {
|
||||
markContent(i);
|
||||
if (databaseType === "gaussdb" && statementStart !== -1 && startsWithPostgresDollarQuotedRoutinePrefix(sql.slice(statementStart, i))) {
|
||||
if ((databaseType === "gaussdb" || isOpenGaussOracleCompatibility(databaseType, parameterOptions)) && statementStart !== -1 && startsWithPostgresDollarQuotedRoutinePrefix(sql.slice(statementStart, i))) {
|
||||
postgresDollarQuotedRoutine = true;
|
||||
}
|
||||
dollarTag = tagMatch[0].slice(1, -1);
|
||||
@@ -609,7 +609,7 @@ export function splitSqlStatementRanges(sql: string, databaseType?: DatabaseType
|
||||
// Internal semicolons remain part of the routine body.
|
||||
flush();
|
||||
} else {
|
||||
if (oraclePlSqlStatementEnd === undefined && isOracleLikeDatabase(databaseType) && !postgresDollarQuotedRoutine && statementStart !== -1) {
|
||||
if (oraclePlSqlStatementEnd === undefined && isOracleLikeDatabase(databaseType, parameterOptions) && !postgresDollarQuotedRoutine && statementStart !== -1) {
|
||||
const statementSoFar = sql.slice(statementStart, i);
|
||||
oraclePlSqlStatementEnd = startsWithOraclePlSqlBlock(statementSoFar) ? statementStart + (oraclePlSqlBlockEnd(sql.slice(statementStart)) ?? sql.length - statementStart) : null;
|
||||
}
|
||||
@@ -740,7 +740,7 @@ function rangeForCursorInSoftRanges(sql: string, ranges: RawStatement[], pos: nu
|
||||
}
|
||||
|
||||
function splitStatementRangeAtSoftStarts(sql: string, statement: RawStatement, databaseType?: DatabaseType, parameterOptions?: SqlParameterOptions): RawStatement[] {
|
||||
if (isOraclePlSqlStatement(statement.sql, databaseType)) return [statement];
|
||||
if (isOraclePlSqlStatement(statement.sql, databaseType, parameterOptions)) return [statement];
|
||||
if (isSapHanaScriptBlockStatement(statement.sql, databaseType)) return [statement];
|
||||
// Routine bodies contain top-level-looking SET/INSERT/SELECT lines that are not independent statements.
|
||||
if (isMysqlRoutineBlockDatabase(databaseType) && startsWithMysqlRoutineBlock(statement.sql, parameterOptions)) return [statement];
|
||||
@@ -1575,12 +1575,27 @@ function isSqlWhitespace(ch: string): boolean {
|
||||
return ch === " " || ch === "\t" || ch === "\r" || ch === "\n";
|
||||
}
|
||||
|
||||
export function isOracleLikeDatabase(databaseType?: DatabaseType): boolean {
|
||||
return !!databaseType && ORACLE_LIKE_PL_SQL_DATABASES.has(databaseType);
|
||||
function isOpenGaussOracleCompatibility(databaseType?: DatabaseType, options?: SqlParameterOptions): boolean {
|
||||
if (databaseType !== "opengauss") return false;
|
||||
const mode = options?.compatibilityMode?.trim().toUpperCase();
|
||||
// Unknown mode is handled conservatively so an A-mode package is never
|
||||
// split into executable fragments before metadata loading finishes.
|
||||
return mode === undefined || mode === "A";
|
||||
}
|
||||
|
||||
export function isOraclePlSqlStatement(sql: string, databaseType?: DatabaseType): boolean {
|
||||
return isOracleLikeDatabase(databaseType) && startsWithOraclePlSqlBlock(sql);
|
||||
export function sqlStatementParameterOptionsForCompatibility(databaseType?: DatabaseType, compatibilityMode?: string): SqlParameterOptions | undefined {
|
||||
if (databaseType !== "opengauss") return undefined;
|
||||
// Keep the browser-side splitter aligned with the backend's conservative
|
||||
// behavior while the compatibility probe is still cold or unavailable.
|
||||
return { compatibilityMode: compatibilityMode?.trim() || "A" };
|
||||
}
|
||||
|
||||
export function isOracleLikeDatabase(databaseType?: DatabaseType, options?: SqlParameterOptions): boolean {
|
||||
return !!databaseType && (ORACLE_LIKE_PL_SQL_DATABASES.has(databaseType) || isOpenGaussOracleCompatibility(databaseType, options));
|
||||
}
|
||||
|
||||
export function isOraclePlSqlStatement(sql: string, databaseType?: DatabaseType, options?: SqlParameterOptions): boolean {
|
||||
return isOracleLikeDatabase(databaseType, options) && startsWithOraclePlSqlBlock(sql);
|
||||
}
|
||||
|
||||
function isSapHanaScriptBlockDatabase(databaseType?: DatabaseType): boolean {
|
||||
|
||||
@@ -4,7 +4,8 @@ import { hexToRgba } from "@/lib/common/color";
|
||||
import type { CSSProperties } from "vue";
|
||||
import { findConnectionGroupPath } from "@/lib/sidebar/sidebarLayout";
|
||||
import { splitMongoCommandRanges } from "@/lib/mongo/mongoShellCommand";
|
||||
import { executableStatementRanges, splitSqlStatementRanges, type SqlTextRange } from "@/lib/sql/sqlStatementRanges";
|
||||
import { executableStatementRanges, splitSqlStatementRanges, sqlStatementParameterOptionsForCompatibility, type SqlTextRange } from "@/lib/sql/sqlStatementRanges";
|
||||
import type { SqlParameterOptions } from "@/lib/sql/sqlParameters";
|
||||
import { sqlTextFingerprint } from "@/lib/sql/sqlTextFingerprint";
|
||||
import { isQueryExecutionErrorResult } from "@/lib/query/queryResultError";
|
||||
import type { BatchSqlExecution, ConnectionConfig, DatabaseType, QueryResult, QueryTab } from "@/types/database";
|
||||
@@ -240,14 +241,14 @@ export function resultSqlForGrid(tab: Pick<QueryTab, "result" | "resultBaseSql"
|
||||
* A stale or ambiguous source is ignored instead of highlighting a different
|
||||
* statement that happens to have the same text.
|
||||
*/
|
||||
export function resultSourceRange(editorSql: string, result: Pick<QueryResult, "sourceStatement" | "sourceFrom" | "sourceTo"> | undefined, resultIndex: number | undefined, databaseType?: DatabaseType): SqlTextRange | undefined {
|
||||
export function resultSourceRange(editorSql: string, result: Pick<QueryResult, "sourceStatement" | "sourceFrom" | "sourceTo"> | undefined, resultIndex: number | undefined, databaseType?: DatabaseType, parameterOptions?: SqlParameterOptions): SqlTextRange | undefined {
|
||||
const sourceStatement = result?.sourceStatement;
|
||||
if (!sourceStatement) return undefined;
|
||||
if (typeof result.sourceFrom === "number" && typeof result.sourceTo === "number" && editorSql.slice(result.sourceFrom, result.sourceTo) === sourceStatement) {
|
||||
return { from: result.sourceFrom, to: result.sourceTo, sql: sourceStatement };
|
||||
}
|
||||
|
||||
const statements = statementRanges(editorSql, databaseType);
|
||||
const statements = statementRanges(editorSql, databaseType, parameterOptions);
|
||||
const indexed = typeof resultIndex === "number" ? statements[resultIndex] : undefined;
|
||||
if (indexed?.sql === sourceStatement) {
|
||||
return { from: indexed.from, to: indexed.to, sql: indexed.sql };
|
||||
@@ -273,10 +274,10 @@ function lineStartOffset(sql: string, from: number): number {
|
||||
return sql.lastIndexOf("\n", Math.max(0, from - 1)) + 1;
|
||||
}
|
||||
|
||||
function statementRanges(sql: string, databaseType?: DatabaseType): SqlTextRange[] {
|
||||
function statementRanges(sql: string, databaseType?: DatabaseType, parameterOptions?: SqlParameterOptions): SqlTextRange[] {
|
||||
if (databaseType === "redis") return executableStatementRanges(sql, databaseType);
|
||||
if (databaseType === "mongodb") return splitMongoCommandRanges(sql).map(({ from, to, text }) => ({ from, to, sql: text }));
|
||||
return splitSqlStatementRanges(sql, databaseType);
|
||||
return splitSqlStatementRanges(sql, databaseType, parameterOptions ?? sqlStatementParameterOptionsForCompatibility(databaseType));
|
||||
}
|
||||
|
||||
function liveStatementExecutionMarkers(editorSql: string, batch: BatchSqlExecution): StatementExecutionMarker[] {
|
||||
@@ -301,12 +302,20 @@ function liveStatementExecutionMarkers(editorSql: string, batch: BatchSqlExecuti
|
||||
}));
|
||||
}
|
||||
|
||||
export function statementExecutionMarkers(editorSql: string, results: QueryResult[] | undefined, databaseType?: DatabaseType, submittedSql = editorSql, executionEditorFingerprint = sqlTextFingerprint(editorSql), batch?: BatchSqlExecution): StatementExecutionMarker[] {
|
||||
export function statementExecutionMarkers(
|
||||
editorSql: string,
|
||||
results: QueryResult[] | undefined,
|
||||
databaseType?: DatabaseType,
|
||||
submittedSql = editorSql,
|
||||
executionEditorFingerprint = sqlTextFingerprint(editorSql),
|
||||
batch?: BatchSqlExecution,
|
||||
parameterOptions?: SqlParameterOptions,
|
||||
): StatementExecutionMarker[] {
|
||||
if (batch?.items.length) return liveStatementExecutionMarkers(editorSql, batch);
|
||||
if (!results?.length || sqlTextFingerprint(editorSql) !== executionEditorFingerprint) return [];
|
||||
const submittedStatements = statementRanges(submittedSql, databaseType);
|
||||
const submittedStatements = statementRanges(submittedSql, databaseType, parameterOptions);
|
||||
if (submittedStatements.length <= 1) return [];
|
||||
const editorStatements = submittedSql === editorSql ? submittedStatements : statementRanges(editorSql, databaseType);
|
||||
const editorStatements = submittedSql === editorSql ? submittedStatements : statementRanges(editorSql, databaseType, parameterOptions);
|
||||
|
||||
const byLine = new Map<number, { success: number; error: number }>();
|
||||
for (const result of results) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -652,6 +652,8 @@ export interface DatabaseInfo {
|
||||
comment?: string | null;
|
||||
default_charset?: string | null;
|
||||
default_collation?: string | null;
|
||||
/** Database-level compatibility mode, for example openGauss A/B/C/PG. */
|
||||
compatibility_mode?: string | null;
|
||||
}
|
||||
|
||||
export interface DatabaseStorageInfo {
|
||||
@@ -1336,6 +1338,8 @@ export interface TreeNode {
|
||||
pinned?: boolean;
|
||||
connectionId?: string;
|
||||
database?: string;
|
||||
/** Database-level compatibility mode, for example openGauss A/B/C/PG. */
|
||||
compatibilityMode?: string;
|
||||
catalog?: string;
|
||||
catalogType?: string;
|
||||
linkedServer?: string;
|
||||
|
||||
Reference in new issue
Block a user