feat(gaussdb): make count query parallelism configurable

This commit is contained in:
Andrew authored and GitHub committed 2026-08-17 13:11:57 +08:00
1 parent 4ce27f8ae5
commit f6b91e93dc
16 files changed
+235 -3

No files matched your search

@@ -121,13 +121,16 @@ import {
import {
GAUSSDB_M_JDBC_DRIVER_CLASS,
gaussdbConnectionMode,
gaussdbCountQueryDop,
gaussdbIdentifierQuoteStyle,
gaussdbTargetServerType,
setGaussdbConnectionMode,
setGaussdbCountQueryDop,
setGaussdbIdentifierQuoteStyle,
setGaussdbTargetServerType,
supportsGaussdbIdentifierQuoteStyle,
type GaussdbConnectionMode,
type GaussdbCountQueryDop,
type GaussdbIdentifierQuoteStyle,
type GaussdbTargetServerType,
} from "@/lib/database/jdbcDialect";
@@ -551,6 +554,14 @@ const gaussdbTargetServerTypeComputed = computed<GaussdbTargetServerType>({
},
});
const gaussdbCountQueryDopComputed = computed<GaussdbCountQueryDop>({
get: () => gaussdbCountQueryDop(form.value),
set: (value) => {
setGaussdbCountQueryDop(form.value, value);
resetTestState();
},
});
const gaussdbHostEntries = ref<GaussdbHostEntry[]>(parseGaussdbHosts(form.value.host, form.value.port));
watch(
@@ -8010,6 +8021,26 @@ function openExternalUrl(url: string) {
</p>
</div>
</div>
<div v-if="showGaussdbConnectionMode" class="grid grid-cols-4 items-start gap-4">
<Label :class="connectionLabelSmallPaddedClass">{{ t("connection.gaussdbCountQueryDop") }}</Label>
<div class="col-span-3 grid gap-1">
<Select v-model="gaussdbCountQueryDopComputed">
<SelectTrigger class="h-9">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem :value="1">1 ({{ t("common.disabled") }})</SelectItem>
<SelectItem :value="2">2</SelectItem>
<SelectItem :value="4">4</SelectItem>
<SelectItem :value="8">8</SelectItem>
<SelectItem :value="16">16</SelectItem>
</SelectContent>
</Select>
<p class="text-xs leading-5 text-muted-foreground">
{{ t("connection.gaussdbCountQueryDopHint") }}
</p>
</div>
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label :class="connectionLabelSmallClass">{{ t("connection.connectTimeout") }}</Label>
<div class="col-span-3 grid grid-cols-2 gap-2">
@@ -265,7 +265,7 @@ import { buildColumnIndexMap, columnIndexColorClass, columnIndexMetadataRequestC
import { supportsTableStructureEditing } from "@/lib/database/databaseCapabilities";
import { rememberDataGridConditionHistory } from "@/lib/dataGrid/dataGridConditionHistory";
import { restoreDataGridLocalColumnFilters, serializeDataGridLocalColumnFilters } from "@/lib/dataGrid/dataGridLocalColumnFilterState";
import { effectiveDatabaseTypeForConnection } from "@/lib/database/jdbcDialect";
import { effectiveDatabaseTypeForConnection, gaussdbCountQueryDopHint } from "@/lib/database/jdbcDialect";
import { mongoCollectionSupportsIndexes, supportsMongoIndexMutations } from "@/lib/mongo/mongoCapabilities";
import { refreshLoadedMongoIndexes } from "@/lib/mongo/mongoIndexMetadata";
import { isProtectedMongoIndex, mongoDropAllIndexesPreview, mongoDropIndexFailureCount, mongoDropIndexPreview } from "@/lib/sidebar/mongoCollectionMutation";
@@ -3286,6 +3286,7 @@ async function lastPage() {
async function buildCurrentCountTarget(): Promise<{ sql: string; schema?: string } | undefined> {
if (props.countSql) return { sql: props.countSql, schema: props.schema };
if (props.tableMeta) {
const countHint = resolvedDatabaseType.value === "gaussdb" && props.connectionId ? gaussdbCountQueryDopHint(connectionStore.getConfig(props.connectionId)) : undefined;
const sql = await buildDataGridCountSql({
databaseType: props.databaseType,
identifierQuote: connectionStore.connectionIdentifierQuote?.(props.connectionId),
@@ -3294,6 +3295,7 @@ async function buildCurrentCountTarget(): Promise<{ sql: string; schema?: string
schema: props.tableMeta.schema,
tableName: props.tableMeta.tableName,
whereInput: currentWhereInput(),
countHint,
});
return {
sql,
+3
View File
@@ -922,6 +922,8 @@ export default {
gaussdbTargetServerTypeSlave: "Slave (standby)",
gaussdbTargetServerTypeAny: "Any (no preference)",
gaussdbTargetServerTypeHint: "Controls which node in a GaussDB cluster the JDBC driver connects to. Master tries the primary, slave tries a standby, any connects to whichever is available. Only applies in M (JDBC) mode.",
gaussdbCountQueryDop: "COUNT(*) parallelism",
gaussdbCountQueryDopHint: "Sets the query_dop (degree of parallelism) for COUNT(*) queries on large tables. Higher values use more resources. Set to 1 (default) to disable the optimizer hint.",
saveAndConnect: "Save & Connect",
save: "Save",
editTitle: "Edit Connection",
@@ -2121,6 +2123,7 @@ export default {
noResults: "No results",
edit: "Edit",
delete: "Delete",
disabled: "Disabled",
back: "Back",
},
dateTimePicker: {
+3
View File
@@ -740,6 +740,8 @@ export default withEnglishFallback({
gaussdbTargetServerTypeSlave: "Réplica (en espera)",
gaussdbTargetServerTypeAny: "Cualquiera (sin preferencia)",
gaussdbTargetServerTypeHint: "Controla a qué nodo del clúster GaussDB se conecta el controlador JDBC. Se aplica solo al modo M (JDBC).",
gaussdbCountQueryDop: "Paralelismo de COUNT(*)",
gaussdbCountQueryDopHint: "Establece query_dop (grado de paralelismo) para consultas COUNT(*) en tablas grandes. Los valores más altos usan más recursos. Establezca 1 (predeterminado) para desactivar la sugerencia del optimizador.",
saveAndConnect: "Guardar y conectar",
save: "Guardar",
editTitle: "Editar conexión",
@@ -2056,6 +2058,7 @@ export default withEnglishFallback({
noResults: "Sin resultados",
edit: "Editar",
delete: "Eliminar",
disabled: "Desactivado",
back: "Volver",
},
dateTimePicker: {
+3
View File
@@ -739,6 +739,8 @@ export default withEnglishFallback({
gaussdbTargetServerTypeSlave: "Slave (standby)",
gaussdbTargetServerTypeAny: "Qualsiasi (nessuna preferenza)",
gaussdbTargetServerTypeHint: "Controlla il nodo del cluster GaussDB a cui si connette il driver JDBC. Si applica solo alla modalità M (JDBC).",
gaussdbCountQueryDop: "Parallelismo COUNT(*)",
gaussdbCountQueryDopHint: "Imposta query_dop (grado di parallelismo) per le query COUNT(*) su tabelle di grandi dimensioni. Valori più elevati usano più risorse. Imposta 1 (predefinito) per disabilitare l'hint dell'ottimizzatore.",
saveAndConnect: "Salva & Connetti",
save: "Salva",
editTitle: "Modifica Connessione",
@@ -2054,6 +2056,7 @@ export default withEnglishFallback({
noResults: "Nessun risultato",
edit: "Modifica",
delete: "Elimina",
disabled: "Disabilitato",
back: "Indietro",
},
dateTimePicker: {
+3
View File
@@ -705,6 +705,8 @@ export default withEnglishFallback({
gaussdbTargetServerTypeSlave: "スレーブ(スタンバイ)",
gaussdbTargetServerTypeAny: "任意(優先なし)",
gaussdbTargetServerTypeHint: "JDBC ドライバーが接続する GaussDB クラスターのノードを制御します。M モード(JDBC)にのみ適用されます。",
gaussdbCountQueryDop: "COUNT(*) の並列度",
gaussdbCountQueryDopHint: "大規模テーブルの COUNT(*) クエリに query_dop(並列度)を設定します。値を大きくするとより多くのリソースを使用します。オプティマイザーヒントを無効にするには 1(既定値)を設定します。",
saveAndConnect: "保存して接続",
save: "保存",
editTitle: "接続を編集",
@@ -2081,6 +2083,7 @@ export default withEnglishFallback({
noResults: "結果なし",
edit: "編集",
delete: "削除",
disabled: "無効",
back: "戻る",
},
dateTimePicker: {
+3
View File
@@ -814,6 +814,8 @@ export default withEnglishFallback({
gaussdbTargetServerTypeSlave: "슬레이브(대기)",
gaussdbTargetServerTypeAny: "임의(선호 없음)",
gaussdbTargetServerTypeHint: "JDBC 드라이버가 연결할 GaussDB 클러스터 노드를 제어합니다. M 모드(JDBC)에만 적용됩니다.",
gaussdbCountQueryDop: "COUNT(*) 병렬 처리 수준",
gaussdbCountQueryDopHint: "대형 테이블의 COUNT(*) 쿼리에 query_dop(병렬 처리 수준)를 설정합니다. 값이 클수록 더 많은 리소스를 사용합니다. 옵티마이저 힌트를 비활성화하려면 1(기본값)로 설정하세요.",
saveAndConnect: "저장 후 연결",
save: "저장",
editTitle: "연결 편집",
@@ -1974,6 +1976,7 @@ export default withEnglishFallback({
noResults: "결과 없음",
edit: "편집",
delete: "삭제",
disabled: "비활성화",
back: "뒤로",
},
dateTimePicker: {
+3
View File
@@ -740,6 +740,8 @@ export default withEnglishFallback({
gaussdbTargetServerTypeSlave: "Slave (standby)",
gaussdbTargetServerTypeAny: "Qualquer (sem preferência)",
gaussdbTargetServerTypeHint: "Controla a qual nó do cluster GaussDB o driver JDBC se conecta. Aplica-se apenas ao modo M (JDBC).",
gaussdbCountQueryDop: "Paralelismo de COUNT(*)",
gaussdbCountQueryDopHint: "Define query_dop (grau de paralelismo) para consultas COUNT(*) em tabelas grandes. Valores maiores usam mais recursos. Defina como 1 (padrão) para desativar a dica do otimizador.",
saveAndConnect: "Salvar e Conectar",
save: "Salvar",
editTitle: "Editar Conexão",
@@ -2056,6 +2058,7 @@ export default withEnglishFallback({
noResults: "Sem resultados",
edit: "Editar",
delete: "Excluir",
disabled: "Desativado",
back: "Voltar",
},
dateTimePicker: {
+3
View File
@@ -846,6 +846,8 @@ export default withEnglishFallback({
gaussdbTargetServerTypeSlave: "备节点(Standby)",
gaussdbTargetServerTypeAny: "任意(不指定)",
gaussdbTargetServerTypeHint: "控制 JDBC 驱动连接到 GaussDB 集群中的哪个节点。主节点优先连接主库,备节点连接备库,任意节点连接可用节点。仅适用于 M 模式(JDBC)。",
gaussdbCountQueryDop: "COUNT(*) 并行度",
gaussdbCountQueryDopHint: "设置 COUNT(*) 查询的 query_dop(并行度),用于大表行数统计。较高的值会消耗更多资源。设置为 1(默认)则不传入优化器提示。",
saveAndConnect: "保存并连接",
save: "保存",
editTitle: "编辑连接",
@@ -2045,6 +2047,7 @@ export default withEnglishFallback({
noResults: "无结果",
edit: "编辑",
delete: "删除",
disabled: "禁用",
back: "返回",
},
dateTimePicker: {
+3
View File
@@ -739,6 +739,8 @@ export default withEnglishFallback({
gaussdbTargetServerTypeSlave: "備節點(Standby)",
gaussdbTargetServerTypeAny: "任意(不指定)",
gaussdbTargetServerTypeHint: "控制 JDBC 驅動程式連線到 GaussDB 叢集中的哪個節點。僅適用於 M 模式(JDBC)。",
gaussdbCountQueryDop: "COUNT(*) 平行度",
gaussdbCountQueryDopHint: "設定大型資料表 COUNT(*) 查詢的 query_dop(平行度)。較高的值會使用更多資源。設定為 1(預設值)可停用最佳化器提示。",
saveAndConnect: "儲存並連線",
save: "儲存",
editTitle: "編輯連線",
@@ -2055,6 +2057,7 @@ export default withEnglishFallback({
noResults: "無結果",
edit: "編輯",
delete: "刪除",
disabled: "停用",
back: "返回",
},
dateTimePicker: {
@@ -11,11 +11,14 @@ import {
connectionUsesDatabaseObjectTreeMode,
effectiveDatabaseTypeForConnection,
gaussdbConnectionMode,
gaussdbCountQueryDop,
gaussdbCountQueryDopHint,
gaussdbIdentifierQuoteOverride,
gaussdbIdentifierQuoteStyle,
gaussdbTargetServerType,
inferJdbcDialect,
setGaussdbConnectionMode,
setGaussdbCountQueryDop,
setGaussdbIdentifierQuoteStyle,
setGaussdbTargetServerType,
supportsGaussdbIdentifierQuoteStyle,
@@ -236,6 +239,24 @@ describe("GaussDB connection mode", () => {
expect(gaussdbTargetServerType(connection)).toBe("any");
expect(connection.external_config).toEqual({ gaussdbTargetServerType: "any" });
});
it("keeps count query parallelism disabled until explicitly configured", () => {
const connection = { db_type: "gaussdb", external_config: { retained: true } } as ConnectionConfig;
expect(gaussdbCountQueryDop(connection)).toBe(1);
expect(gaussdbCountQueryDopHint(connection)).toBeUndefined();
setGaussdbCountQueryDop(connection, 8);
expect(connection.external_config).toEqual({ retained: true, gaussdbCountQueryDop: 8 });
expect(gaussdbCountQueryDopHint(connection)).toBe("/*+ set(query_dop 8) */");
connection.external_config = { retained: true, gaussdbCountQueryDop: 32 };
expect(gaussdbCountQueryDop(connection)).toBe(1);
expect(gaussdbCountQueryDopHint(connection)).toBeUndefined();
setGaussdbCountQueryDop(connection, 1);
expect(connection.external_config).toEqual({ retained: true });
});
});
describe("query execution schema", () => {
@@ -108,6 +108,9 @@ export interface DataGridCountSqlOptions {
schema?: string;
tableName: string;
whereInput?: string;
/** Optional optimizer hint injected between SELECT and the select list.
* Example: "/*+ set(query_dop 32) *​/" for GaussDB parallel COUNT(*). */
countHint?: string;
}
export interface HiveTablePropertiesSqlOptions {
@@ -7,9 +7,11 @@ type JdbcDialectConnection = Partial<Pick<ConnectionConfig, "db_type" | "driver_
export type GaussdbIdentifierQuoteStyle = "auto" | "double" | "backtick";
export type GaussdbConnectionMode = "native" | "m-jdbc";
export type GaussdbTargetServerType = "master" | "slave" | "any";
export type GaussdbCountQueryDop = 1 | 2 | 4 | 8 | 16;
const GAUSSDB_IDENTIFIER_QUOTE_STYLE_KEY = "gaussdbIdentifierQuoteStyle";
const GAUSSDB_TARGET_SERVER_TYPE_KEY = "gaussdbTargetServerType";
const GAUSSDB_COUNT_QUERY_DOP_KEY = "gaussdbCountQueryDop";
export const GAUSSDB_M_JDBC_DRIVER_PROFILE = "gaussdb-m";
export const GAUSSDB_M_JDBC_DRIVER_CLASS = "com.huawei.gaussdb.jdbc.Driver";
@@ -239,6 +241,27 @@ export function codeMirrorSqlDialectForConnection(connection?: JdbcDialectConnec
return codeMirrorSqlDialect(databaseType);
}
export function gaussdbCountQueryDop(connection: JdbcDialectConnection | undefined): GaussdbCountQueryDop {
const external = externalConfigRecord(connection?.external_config);
const value = external[GAUSSDB_COUNT_QUERY_DOP_KEY];
return value === 2 || value === 4 || value === 8 || value === 16 ? value : 1;
}
export function setGaussdbCountQueryDop(connection: Pick<ConnectionConfig, "db_type"> & Partial<Pick<ConnectionConfig, "external_config">>, value: GaussdbCountQueryDop) {
const external = externalConfigRecord(connection.external_config);
if (value === 1) {
delete external[GAUSSDB_COUNT_QUERY_DOP_KEY];
} else {
external[GAUSSDB_COUNT_QUERY_DOP_KEY] = value;
}
connection.external_config = Object.keys(external).length > 0 ? external : undefined;
}
export function gaussdbCountQueryDopHint(connection: JdbcDialectConnection | undefined): string | undefined {
const dop = gaussdbCountQueryDop(connection);
return dop > 1 ? `/*+ set(query_dop ${dop}) */` : undefined;
}
function isJdbcAseProfile(connection?: JdbcDialectConnection): boolean {
if (connection?.db_type !== "jdbc") return false;
const explicitIdentity = [connection.driver_profile, connection.driver_label, connection.database_info?.productName].filter(Boolean).join("\n");
@@ -1284,6 +1284,59 @@ describe("queryStore hidden primary key editing", () => {
expect(tab.resultTotalRowCountLoading).toBe(false);
});
it.each([
["disabled by default", undefined, undefined],
["explicitly enabled", { gaussdbCountQueryDop: 8 }, "/*+ set(query_dop 8) */"],
])("keeps GaussDB count parallelism %s", async (_label, externalConfig, expectedCountHint) => {
editorSettings.autoCalculateTotalRows = true;
getConnectionConfig.mockReturnValue({
id: "gaussdb-1",
name: "GaussDB",
db_type: "gaussdb",
database: "app",
query_timeout_secs: 30,
external_config: externalConfig,
});
executeMulti.mockResolvedValue([
{
columns: ["id", "name"],
rows: Array.from({ length: 100 }, (_, index) => [index + 1, `user-${index + 1}`]),
affected_rows: 0,
execution_time_ms: 1,
},
]);
const { useQueryStore } = await import("@/stores/queryStore");
const store = useQueryStore();
const tabId = store.createTab("gaussdb-1", "app", "users", "data", "public");
store.setTableMeta(tabId, {
schema: "public",
tableName: "users",
columns: [
{ name: "id", data_type: "int", is_nullable: false, is_primary_key: true, column_default: null, extra: null },
{ name: "name", data_type: "varchar", is_nullable: true, is_primary_key: false, column_default: null, extra: null },
],
primaryKeys: ["id"],
});
await store.executeTabSql(tabId, "SELECT id, name FROM users LIMIT 100", {
pagination: { limit: 100, offset: 0 },
});
await vi.waitFor(() =>
expect(buildDataGridCountSql).toHaveBeenCalledWith({
databaseType: "gaussdb",
identifierQuote: undefined,
catalog: undefined,
database: undefined,
schema: "public",
tableName: "users",
whereInput: undefined,
countHint: expectedCountHint,
}),
);
});
it("stops appending when a SQL Server query has no bounded next-page plan", async () => {
getConnectionConfig.mockReturnValue({ id: "sqlserver-1", name: "SQL Server", db_type: "sqlserver", database: "app", query_timeout_secs: 30 });
analyzeEditableQueryEditability.mockResolvedValue({ editable: false, reason: "complex-query" });
+2 -1
View File
@@ -43,7 +43,7 @@ import { dataTabExecutionDatabase } from "@/lib/table/dataTabExecutionDatabase";
import { tableOpenPageLimit } from "@/lib/table/tableOpenPageLimit";
import { getCachedTableMetadata, loadTableIndexes, loadTableMetadata, type TableMetadataRequest } from "@/lib/metadata/tableMetadataCache";
import { buildTableSelectSql, quoteTableDataIdentifier } from "@/lib/table/tableSelectSql";
import { connectionObjectTreeNodeSchema, connectionQueryExecutionSchema, connectionUsesDatabaseObjectTreeMode, effectiveDatabaseTypeForConnection, metadataSchemaForConnection } from "@/lib/database/jdbcDialect";
import { connectionObjectTreeNodeSchema, connectionQueryExecutionSchema, connectionUsesDatabaseObjectTreeMode, effectiveDatabaseTypeForConnection, gaussdbCountQueryDopHint, metadataSchemaForConnection } from "@/lib/database/jdbcDialect";
import { frontendQueryTimeoutDelayMs, frontendQueryTimeoutSecsForSql, queryTimeoutSecsForConnection } from "@/lib/sql/queryTimeout";
import { queryResultNameFromPreamble, queryResultSourceLabel } from "@/lib/sql/queryResultSource";
import { beginDataGridNativeSelectionBlock, finishDataGridNativeSelectionBlock } from "@/lib/dataGrid/dataGridNativeSelection";
@@ -4866,6 +4866,7 @@ export const useQueryStore = defineStore("query", () => {
schema: tableMeta.schema,
tableName: tableMeta.tableName,
whereInput: current.whereInput?.trim() || undefined,
countHint: effectiveDbType === "gaussdb" ? gaussdbCountQueryDopHint(useConnectionStore().getConfig(current.connectionId)) : undefined,
};
})()
: undefined;