diff --git a/agents/common/src/main/java/com/dbx/agent/DdlBuilder.java b/agents/common/src/main/java/com/dbx/agent/DdlBuilder.java index a03b1647b3..8c3b518dd4 100644 --- a/agents/common/src/main/java/com/dbx/agent/DdlBuilder.java +++ b/agents/common/src/main/java/com/dbx/agent/DdlBuilder.java @@ -295,7 +295,9 @@ public final class DdlBuilder { private static boolean isCharacterType(String normalized) { return "character varying".equals(normalized) || "varchar".equals(normalized) + || "nvarchar".equals(normalized) || "char".equals(normalized) + || "nchar".equals(normalized) || "character".equals(normalized); } diff --git a/agents/common/src/test/java/com/dbx/agent/CommonJavaCompatibilityTest.java b/agents/common/src/test/java/com/dbx/agent/CommonJavaCompatibilityTest.java index 0e71c87ef1..8ca214d3f6 100644 --- a/agents/common/src/test/java/com/dbx/agent/CommonJavaCompatibilityTest.java +++ b/agents/common/src/test/java/com/dbx/agent/CommonJavaCompatibilityTest.java @@ -724,6 +724,30 @@ class CommonJavaCompatibilityTest { ); } + @Test + void buildsTableDdlWithNationalCharacterLength() { + String ddl = DdlBuilder.buildTableDdl( + "DBX_DEMO", + "CUSTOMERS", + Collections.singletonList(new ColumnInfo( + "NAME", + "NVARCHAR", + false, + null, + false, + null, + null, + null, + null, + 100 + )), + Collections.emptyList(), + Collections.emptyList() + ); + + assertTrue(ddl.contains("\"NAME\" NVARCHAR(100) NOT NULL")); + } + private static class MinimalAgent implements DatabaseAgent { @Override public void connect(ConnectParams params) { diff --git a/apps/desktop/src/components/grid/DataGrid.vue b/apps/desktop/src/components/grid/DataGrid.vue index 30e37229a9..d5279f811a 100644 --- a/apps/desktop/src/components/grid/DataGrid.vue +++ b/apps/desktop/src/components/grid/DataGrid.vue @@ -96,7 +96,7 @@ import type { BuildSingleColumnAlterSqlOptions } from "@/lib/table/tableStructur import { buildTableSelectSql, qualifyTableReferencesInSql, quoteTableDataIdentifier } from "@/lib/table/tableSelectSql"; import { uuid } from "@/lib/common/utils"; import { generateCellValues, type CellValueGenerationKind } from "@/lib/dataGrid/cellValueGeneration"; -import { compactHeaderColumnType, isNumericColumnType, resolveDataGridTypeVisualKind, resolveHeaderColumnType, resolveResultColumnType } from "@/lib/dataGrid/dataGridColumnType"; +import { compactHeaderColumnType, formatMetadataColumnTypeLabel, isNumericColumnType, resolveDataGridTypeVisualKind, resolveHeaderColumnType, resolveResultColumnType } from "@/lib/dataGrid/dataGridColumnType"; import { dataGridCellTextClass, dataGridTypeVisualClass } from "@/lib/dataGrid/dataGridCellTextVisual"; import { DATA_GRID_TYPE_COLOR_KEYS, resolveActiveDataGridTypeColors } from "@/lib/dataGrid/dataGridTypeColorScheme"; import { @@ -668,14 +668,15 @@ const columnTypeMap = computed(() => { const map = new Map(); if (props.tableMeta?.columns) { for (const col of props.tableMeta.columns) { - const typeName = shortTypeName(col.data_type); - // Add precision for numeric/decimal types - if (col.numeric_precision != null && ["numeric", "decimal"].includes(col.data_type.toLowerCase())) { - const scale = col.numeric_scale ?? 0; - map.set(col.name, `${typeName}(${col.numeric_precision},${scale})`); - } else { - map.set(col.name, typeName); - } + map.set( + col.name, + formatMetadataColumnTypeLabel({ + dataType: shortTypeName(col.data_type), + characterMaximumLength: col.character_maximum_length, + numericPrecision: col.numeric_precision, + numericScale: col.numeric_scale, + }), + ); } } return map; diff --git a/apps/desktop/src/lib/dataGrid/dataGridColumnType.ts b/apps/desktop/src/lib/dataGrid/dataGridColumnType.ts index 9569d757dc..33892fed06 100644 --- a/apps/desktop/src/lib/dataGrid/dataGridColumnType.ts +++ b/apps/desktop/src/lib/dataGrid/dataGridColumnType.ts @@ -34,6 +34,27 @@ export function compactHeaderColumnType(dataType: string): string { return /^enum\s*\(/i.test(dataType.trim()) ? "enum" : dataType; } +const CHARACTER_LENGTH_TYPE_PATTERN = /^(?:char|varchar|nchar|nvarchar|varchar2|nvarchar2|character|character\s+varying|national\s+character|national\s+character\s+varying)$/i; + +export interface MetadataColumnTypeLabel { + dataType: string; + characterMaximumLength?: number | null; + numericPrecision?: number | null; + numericScale?: number | null; +} + +export function formatMetadataColumnTypeLabel({ dataType, characterMaximumLength, numericPrecision, numericScale }: MetadataColumnTypeLabel): string { + const typeName = dataType.trim(); + if (!typeName || /\([^)]*\)\s*$/.test(typeName)) return typeName; + if (characterMaximumLength != null && characterMaximumLength > 0 && CHARACTER_LENGTH_TYPE_PATTERN.test(typeName)) { + return `${typeName}(${characterMaximumLength})`; + } + if (numericPrecision != null && /^(?:numeric|decimal)$/i.test(typeName)) { + return `${typeName}(${numericPrecision},${numericScale ?? 0})`; + } + return typeName; +} + /** * Resolve the data type used to drive per-column alignment and other * type-driven rendering in the query-result grid. diff --git a/issue-assets/saphana-character-length.jpg b/issue-assets/saphana-character-length.jpg new file mode 100644 index 0000000000..01fe86c100 Binary files /dev/null and b/issue-assets/saphana-character-length.jpg differ diff --git a/packages/app-tests/dataGridColumnType.test.ts b/packages/app-tests/dataGridColumnType.test.ts index 2f128e4246..d86222a346 100644 --- a/packages/app-tests/dataGridColumnType.test.ts +++ b/packages/app-tests/dataGridColumnType.test.ts @@ -1,6 +1,6 @@ import { strict as assert } from "node:assert"; import { test } from "vitest"; -import { compactHeaderColumnType, resolveHeaderColumnType } from "../../apps/desktop/src/lib/dataGrid/dataGridColumnType.ts"; +import { compactHeaderColumnType, formatMetadataColumnTypeLabel, resolveHeaderColumnType } from "../../apps/desktop/src/lib/dataGrid/dataGridColumnType.ts"; test("prefers table-metadata type over the result type", () => { const type = resolveHeaderColumnType({ @@ -59,3 +59,13 @@ test("hides MySQL enum values from the compact header type", () => { assert.equal(compactHeaderColumnType("ENUM('', 'normal')"), "enum"); assert.equal(compactHeaderColumnType("varchar(255)"), "varchar(255)"); }); + +test("adds character length from table metadata", () => { + assert.equal(formatMetadataColumnTypeLabel({ dataType: "NVARCHAR", characterMaximumLength: 100 }), "NVARCHAR(100)"); + assert.equal(formatMetadataColumnTypeLabel({ dataType: "character varying", characterMaximumLength: 64 }), "character varying(64)"); +}); + +test("does not append duplicate type parameters", () => { + assert.equal(formatMetadataColumnTypeLabel({ dataType: "nvarchar(100)", characterMaximumLength: 100 }), "nvarchar(100)"); + assert.equal(formatMetadataColumnTypeLabel({ dataType: "numeric(10,2)", numericPrecision: 10, numericScale: 2 }), "numeric(10,2)"); +});