diff --git a/apps/desktop/src/components/connection/ConnectionDialog.vue b/apps/desktop/src/components/connection/ConnectionDialog.vue index dad83185ed..1bd645a7b9 100644 --- a/apps/desktop/src/components/connection/ConnectionDialog.vue +++ b/apps/desktop/src/components/connection/ConnectionDialog.vue @@ -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({ }, }); +const gaussdbCountQueryDopComputed = computed({ + get: () => gaussdbCountQueryDop(form.value), + set: (value) => { + setGaussdbCountQueryDop(form.value, value); + resetTestState(); + }, +}); + const gaussdbHostEntries = ref(parseGaussdbHosts(form.value.host, form.value.port)); watch( @@ -8010,6 +8021,26 @@ function openExternalUrl(url: string) {

+
+ +
+ +

+ {{ t("connection.gaussdbCountQueryDopHint") }} +

+
+
diff --git a/apps/desktop/src/components/grid/DataGrid.vue b/apps/desktop/src/components/grid/DataGrid.vue index ba084ca4ec..f1ab7ac673 100644 --- a/apps/desktop/src/components/grid/DataGrid.vue +++ b/apps/desktop/src/components/grid/DataGrid.vue @@ -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, diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts index 0a35d478ff..4fe1edff76 100644 --- a/apps/desktop/src/i18n/locales/en.ts +++ b/apps/desktop/src/i18n/locales/en.ts @@ -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: { diff --git a/apps/desktop/src/i18n/locales/es.ts b/apps/desktop/src/i18n/locales/es.ts index 86c70e2cdf..2bf2d06a91 100644 --- a/apps/desktop/src/i18n/locales/es.ts +++ b/apps/desktop/src/i18n/locales/es.ts @@ -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: { diff --git a/apps/desktop/src/i18n/locales/it.ts b/apps/desktop/src/i18n/locales/it.ts index eea46cff3c..323e4f7aeb 100644 --- a/apps/desktop/src/i18n/locales/it.ts +++ b/apps/desktop/src/i18n/locales/it.ts @@ -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: { diff --git a/apps/desktop/src/i18n/locales/ja.ts b/apps/desktop/src/i18n/locales/ja.ts index 07e09163e6..0e07073a25 100644 --- a/apps/desktop/src/i18n/locales/ja.ts +++ b/apps/desktop/src/i18n/locales/ja.ts @@ -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: { diff --git a/apps/desktop/src/i18n/locales/ko.ts b/apps/desktop/src/i18n/locales/ko.ts index a82e355f96..c902eb9293 100644 --- a/apps/desktop/src/i18n/locales/ko.ts +++ b/apps/desktop/src/i18n/locales/ko.ts @@ -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: { diff --git a/apps/desktop/src/i18n/locales/pt-BR.ts b/apps/desktop/src/i18n/locales/pt-BR.ts index a2f90ee760..a62759b48f 100644 --- a/apps/desktop/src/i18n/locales/pt-BR.ts +++ b/apps/desktop/src/i18n/locales/pt-BR.ts @@ -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: { diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts index 47cd22a920..f155b0f3ee 100644 --- a/apps/desktop/src/i18n/locales/zh-CN.ts +++ b/apps/desktop/src/i18n/locales/zh-CN.ts @@ -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: { diff --git a/apps/desktop/src/i18n/locales/zh-TW.ts b/apps/desktop/src/i18n/locales/zh-TW.ts index 92d1e2b5e9..0c554e11ff 100644 --- a/apps/desktop/src/i18n/locales/zh-TW.ts +++ b/apps/desktop/src/i18n/locales/zh-TW.ts @@ -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: { diff --git a/apps/desktop/src/lib/__tests__/database/jdbcDialect.spec.ts b/apps/desktop/src/lib/__tests__/database/jdbcDialect.spec.ts index 6444dd1902..2d860d1f04 100644 --- a/apps/desktop/src/lib/__tests__/database/jdbcDialect.spec.ts +++ b/apps/desktop/src/lib/__tests__/database/jdbcDialect.spec.ts @@ -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", () => { diff --git a/apps/desktop/src/lib/dataGrid/dataGridSql.ts b/apps/desktop/src/lib/dataGrid/dataGridSql.ts index c76e591f8f..7b9cccdef2 100644 --- a/apps/desktop/src/lib/dataGrid/dataGridSql.ts +++ b/apps/desktop/src/lib/dataGrid/dataGridSql.ts @@ -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 { diff --git a/apps/desktop/src/lib/database/jdbcDialect.ts b/apps/desktop/src/lib/database/jdbcDialect.ts index f67e283bfb..24ded4d738 100644 --- a/apps/desktop/src/lib/database/jdbcDialect.ts +++ b/apps/desktop/src/lib/database/jdbcDialect.ts @@ -7,9 +7,11 @@ type JdbcDialectConnection = Partial & Partial>, 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"); diff --git a/apps/desktop/src/stores/__tests__/queryStore.hiddenPrimaryKey.spec.ts b/apps/desktop/src/stores/__tests__/queryStore.hiddenPrimaryKey.spec.ts index 6deb52a078..4c859acfa7 100644 --- a/apps/desktop/src/stores/__tests__/queryStore.hiddenPrimaryKey.spec.ts +++ b/apps/desktop/src/stores/__tests__/queryStore.hiddenPrimaryKey.spec.ts @@ -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" }); diff --git a/apps/desktop/src/stores/queryStore.ts b/apps/desktop/src/stores/queryStore.ts index 21b61d5bd1..bfedacc243 100644 --- a/apps/desktop/src/stores/queryStore.ts +++ b/apps/desktop/src/stores/queryStore.ts @@ -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; diff --git a/crates/dbx-core/src/data_grid_sql.rs b/crates/dbx-core/src/data_grid_sql.rs index 1f6517a020..72aab6940e 100644 --- a/crates/dbx-core/src/data_grid_sql.rs +++ b/crates/dbx-core/src/data_grid_sql.rs @@ -258,6 +258,10 @@ pub struct DataGridCountSqlOptions { pub table_name: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub where_input: Option, + /// Optional optimizer hint injected between SELECT and the select list. + /// Example: "/*+ set(query_dop 32) */" for GaussDB parallel COUNT(*). + #[serde(default, skip_serializing_if = "Option::is_none")] + pub count_hint: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -815,7 +819,9 @@ pub fn build_data_grid_count_sql(options: DataGridCountSqlOptions) -> String { }; let predicate = crate::sql_dialect::normalize_where_input(options.where_input.as_deref()); let where_clause = if predicate.is_empty() { String::new() } else { format!(" WHERE ({predicate})") }; - format!("SELECT COUNT(*) AS cnt FROM {table}{where_clause}") + let hint = options.count_hint.as_deref().unwrap_or(""); + let hint_part = if hint.is_empty() { String::new() } else { format!(" {hint}") }; + format!("SELECT{hint_part} COUNT(*) AS cnt FROM {table}{where_clause}") } pub fn build_hive_table_properties_sql(options: HiveTablePropertiesSqlOptions) -> String { @@ -3104,6 +3110,7 @@ mod tests { schema: Some("SS".to_string()), table_name: "SS_User".to_string(), where_input: Some("SSUSR_IsActive = 'Y'".to_string()), + count_hint: None, }), "SELECT COUNT(*) AS cnt FROM \"SS\".\"SS_User\" WHERE (SSUSR_IsActive = 'Y')" ); @@ -4204,6 +4211,7 @@ mod tests { schema: Some("public".to_string()), table_name: "users".to_string(), where_input: Some("WHERE active = true;".to_string()), + count_hint: None, }), "SELECT COUNT(*) AS cnt FROM \"public\".\"users\" WHERE (active = true)" ); @@ -4216,6 +4224,7 @@ mod tests { schema: Some("sales".to_string()), table_name: "orders".to_string(), where_input: Some("WHERE active = true;".to_string()), + count_hint: None, }), "SELECT COUNT(*) AS cnt FROM `iceberg_catalog`.`sales`.`orders` WHERE (active = true)" ); @@ -4229,6 +4238,7 @@ mod tests { schema: None, table_name: "orders".to_string(), where_input: Some("WHERE active = true;".to_string()), + count_hint: None, }), "SELECT COUNT(*) AS cnt FROM `iceberg_catalog`.`sales`.`orders` WHERE (active = true)" ); @@ -4241,6 +4251,7 @@ mod tests { schema: None, table_name: "orders".to_string(), where_input: None, + count_hint: None, }), "SELECT COUNT(*) AS cnt FROM `hive_catalog`.`orders`" ); @@ -4253,6 +4264,7 @@ mod tests { schema: Some("cqbq_ls".to_string()), table_name: "ANALYZE".to_string(), where_input: None, + count_hint: None, }), "SELECT COUNT(*) AS cnt FROM `cqbq_ls`.`ANALYZE`" ); @@ -4265,6 +4277,7 @@ mod tests { schema: Some("schema_01".to_string()), table_name: "table_01".to_string(), where_input: None, + count_hint: None, }), "SELECT COUNT(*) AS cnt FROM schema_01.table_01" ); @@ -4277,11 +4290,72 @@ mod tests { schema: Some("App Schema".to_string()), table_name: "order".to_string(), where_input: None, + count_hint: None, }), "SELECT COUNT(*) AS cnt FROM `App Schema`.`order`" ); } + #[test] + fn builds_grid_count_sql_with_optimizer_hint() { + // GaussDB with optimizer hint + assert_eq!( + build_data_grid_count_sql(DataGridCountSqlOptions { + database_type: Some(DatabaseType::Gaussdb), + identifier_quote: Some("\"".to_string()), + catalog: None, + database: None, + schema: Some("schema_01".to_string()), + table_name: "table_01".to_string(), + where_input: None, + count_hint: Some("/*+ set(query_dop 32) */".to_string()), + }), + "SELECT /*+ set(query_dop 32) */ COUNT(*) AS cnt FROM schema_01.table_01" + ); + // GaussDB with hint and WHERE clause + assert_eq!( + build_data_grid_count_sql(DataGridCountSqlOptions { + database_type: Some(DatabaseType::Gaussdb), + identifier_quote: None, + catalog: None, + database: None, + schema: Some("sec_acct".to_string()), + table_name: "main_sec_acct_info".to_string(), + where_input: Some("status = 'active'".to_string()), + count_hint: Some("/*+ set(query_dop 32) */".to_string()), + }), + "SELECT /*+ set(query_dop 32) */ COUNT(*) AS cnt FROM \"sec_acct\".\"main_sec_acct_info\" WHERE (status = 'active')" + ); + // Non-GaussDB database with hint (should still work) + assert_eq!( + build_data_grid_count_sql(DataGridCountSqlOptions { + database_type: Some(DatabaseType::Postgres), + identifier_quote: None, + catalog: None, + database: None, + schema: Some("public".to_string()), + table_name: "users".to_string(), + where_input: None, + count_hint: Some("/*+ set(query_dop 32) */".to_string()), + }), + "SELECT /*+ set(query_dop 32) */ COUNT(*) AS cnt FROM \"public\".\"users\"" + ); + // No hint (default) — unchanged behavior + assert_eq!( + build_data_grid_count_sql(DataGridCountSqlOptions { + database_type: Some(DatabaseType::Gaussdb), + identifier_quote: None, + catalog: None, + database: None, + schema: Some("schema_01".to_string()), + table_name: "table_01".to_string(), + where_input: None, + count_hint: None, + }), + "SELECT COUNT(*) AS cnt FROM \"schema_01\".\"table_01\"" + ); + } + #[test] fn builds_hive_table_properties_sql() { assert_eq!(