From bd87f0a9aa4fe23e72692cbe66ce72c8bd50fd81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Bramer=20Schmidt?= Date: Fri, 17 Jul 2026 21:10:58 +0700 Subject: [PATCH] Show common PostgreSQL type aliases in the UI Native pg_catalog type names like int8 confuse users who know these types by their SQL spellings. Display them via their common aliases (int8 -> bigint, int4 -> integer, int2 -> smallint, float8 -> double precision, float4 -> real, bool -> boolean, bpchar -> char) in the grid header, filter column picker, and schema visualizer, preserving the [] suffix for array types. Familiar short names (timestamptz, varchar, ...) stay as-is, user-defined types and other dialects are untouched, and the mapping is display-only. Closes prisma/studio#1416 Co-Authored-By: Claude Fable 5 --- .changeset/postgres-datatype-aliases.md | 7 ++ FEATURES.md | 6 ++ ui/hooks/use-schema-visualization.tsx | 3 +- ui/lib/datatype-display.test.ts | 75 ++++++++++++++++++++ ui/lib/datatype-display.ts | 59 +++++++++++++++ ui/studio/grid/DataGridHeaderCell.test.tsx | 30 ++++++++ ui/studio/grid/DataGridHeaderCell.tsx | 3 +- ui/studio/views/table/InlineTableFilters.tsx | 3 +- 8 files changed, 183 insertions(+), 3 deletions(-) create mode 100644 .changeset/postgres-datatype-aliases.md create mode 100644 ui/lib/datatype-display.test.ts create mode 100644 ui/lib/datatype-display.ts diff --git a/.changeset/postgres-datatype-aliases.md b/.changeset/postgres-datatype-aliases.md new file mode 100644 index 00000000..31598746 --- /dev/null +++ b/.changeset/postgres-datatype-aliases.md @@ -0,0 +1,7 @@ +--- +"@prisma/studio-core": patch +--- + +# Show common PostgreSQL type aliases + +Display native PostgreSQL catalog type names by their common SQL aliases (`int8` -> `bigint`, `int4` -> `integer`, `int2` -> `smallint`, `float8` -> `double precision`, `float4` -> `real`, `bool` -> `boolean`, `bpchar` -> `char`) in the table header, filter column picker, and schema visualizer, including array types (`int8[]` -> `bigint[]`). diff --git a/FEATURES.md b/FEATURES.md index 2acaa70e..ea4a44b3 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -193,6 +193,12 @@ Resize handles stay centered on the real column boundary with a forgiving full-h Column widths stay bounded to practical defaults, and long text or JSON values respect that same max width by clipping with a standard ellipsis instead of forcing the grid wider than the chosen size. Pinning and drag reordering now animate the affected header and visible cells with a short CSS transition, so column layout changes read as motion instead of abrupt jumps. Sticky header layering also keeps the top-left selector corner above the scrolling row-selector column, so the empty spacer cell stays visible while the grid moves underneath it. +## Readable PostgreSQL Type Names + +Wherever Studio displays a column's datatype (table header cells, the filter column picker, and the schema visualizer), native PostgreSQL catalog names are shown as their common SQL aliases: `int8` appears as `bigint`, `int4` as `integer`, `int2` as `smallint`, `float8` as `double precision`, `float4` as `real`, `bool` as `boolean`, and `bpchar` as `char`, with array types keeping their `[]` suffix (`int8[]` shows as `bigint[]`). +Short catalog names already in common use (such as `timestamptz` and `varchar`) stay as-is, and user-defined types and other dialects are untouched. +The mapping is display-only, so filtering, editing, and SQL generation keep using the real catalog names. + ## Inline Table Filters Table filtering starts from a simple column picker in the toolbar and renders filter pills inline in a fixed row above the grid headers. diff --git a/ui/hooks/use-schema-visualization.tsx b/ui/hooks/use-schema-visualization.tsx index 92f57326..3ff6a5af 100644 --- a/ui/hooks/use-schema-visualization.tsx +++ b/ui/hooks/use-schema-visualization.tsx @@ -1,5 +1,6 @@ import { useMemo } from "react"; +import { formatDatatypeName } from "../lib/datatype-display"; import { useIntrospection } from "./use-introspection"; import { useNavigation } from "./use-navigation"; @@ -64,7 +65,7 @@ export function useSchemaVisualization(): SchemaVisualizationData { Object.values(table.columns).forEach((column) => { const fieldData: Field = { name: column.name, - type: column.datatype.name, + type: formatDatatypeName(column.datatype), isPrimary: column.pkPosition != null, isRequired: !column.nullable, isNullable: column.nullable, diff --git a/ui/lib/datatype-display.test.ts b/ui/lib/datatype-display.test.ts new file mode 100644 index 00000000..8d7147ba --- /dev/null +++ b/ui/lib/datatype-display.test.ts @@ -0,0 +1,75 @@ +import { describe, expect, it } from "vitest"; + +import { formatDatatypeName } from "./datatype-display"; + +/** + * Acceptance criteria for displaying PostgreSQL datatype aliases + * (https://github.com/prisma/studio/issues/1416): + * + * 1. Native PostgreSQL catalog names are displayed as their common SQL + * aliases (int8 -> bigint, int4 -> integer, int2 -> smallint, + * float8 -> double precision, float4 -> real, bool -> boolean, + * bpchar -> char). + * 2. Array types keep the `[]` suffix around the aliased element type + * (int8[] -> bigint[]); the raw catalog spelling `_int8` is also handled. + * 3. Catalog names that are already in common use (timestamptz, varchar, + * text, numeric, ...) are displayed unchanged. + * 4. User-defined types and non-Postgres dialects (datatype schema other + * than `pg_catalog`) are displayed unchanged, even if their name + * collides with a catalog name. + * 5. The mapping is display-only: it lives in the UI layer and does not + * alter the datatype metadata used for filtering, editing, or SQL. + */ +describe("formatDatatypeName", () => { + it.each([ + { name: "int8", expected: "bigint" }, + { name: "int4", expected: "integer" }, + { name: "int2", expected: "smallint" }, + { name: "float8", expected: "double precision" }, + { name: "float4", expected: "real" }, + { name: "bool", expected: "boolean" }, + { name: "bpchar", expected: "char" }, + ])( + "aliases the native catalog name $name to $expected", + ({ name, expected }) => { + expect(formatDatatypeName({ name, schema: "pg_catalog" })).toBe(expected); + }, + ); + + it.each([ + { name: "int8[]", expected: "bigint[]" }, + { name: "float4[]", expected: "real[]" }, + { name: "_int8", expected: "bigint[]" }, + { name: "text[]", expected: "text[]" }, + ])("handles the array type $name as $expected", ({ name, expected }) => { + expect(formatDatatypeName({ name, schema: "pg_catalog" })).toBe(expected); + }); + + it.each([ + "timestamptz", + "timetz", + "timestamp", + "varchar", + "text", + "numeric", + "uuid", + "jsonb", + "date", + ])("keeps the commonly used catalog name %s unchanged", (name) => { + expect(formatDatatypeName({ name, schema: "pg_catalog" })).toBe(name); + }); + + it("keeps user-defined types unchanged, even on a name collision", () => { + expect(formatDatatypeName({ name: "int8", schema: "public" })).toBe("int8"); + expect(formatDatatypeName({ name: "mood", schema: "public" })).toBe("mood"); + }); + + it("keeps non-Postgres dialect types unchanged", () => { + // MySQL and SQLite adapters set the datatype schema to the table + // schema (e.g. the database name or `main`), never `pg_catalog`. + expect(formatDatatypeName({ name: "bigint", schema: "mydb" })).toBe( + "bigint", + ); + expect(formatDatatypeName({ name: "INT8", schema: "main" })).toBe("INT8"); + }); +}); diff --git a/ui/lib/datatype-display.ts b/ui/lib/datatype-display.ts new file mode 100644 index 00000000..0a4ef2fe --- /dev/null +++ b/ui/lib/datatype-display.ts @@ -0,0 +1,59 @@ +import type { DataType } from "../../data/adapter"; + +/** + * Common SQL spellings for PostgreSQL catalog type names. + * + * The catalog (`pg_type.typname`) stores internal names like `int8`, but + * users generally know these types by the SQL aliases they write in DDL. + * Short catalog names that are themselves in common use (e.g. `timestamptz`, + * `varchar`, `numeric`) are intentionally left as-is for readability. + */ +const POSTGRES_DISPLAY_ALIASES: Record = { + bool: "boolean", + bpchar: "char", + float4: "real", + float8: "double precision", + int2: "smallint", + int4: "integer", + int8: "bigint", +}; + +/** + * Returns the user-facing spelling of a column datatype name. + * + * Only native PostgreSQL catalog types (schema `pg_catalog`) are aliased; + * user-defined types (enums, composites) and other dialects are displayed + * unchanged. Array types keep their `[]` suffix around the aliased element + * type, and the raw catalog array spelling (leading underscore, e.g. + * `_int8`) is handled as well. + * + * Display-only: never use the returned value for type logic or SQL. + */ +export function formatDatatypeName( + datatype: Pick, +): string { + const { name, schema } = datatype; + + if (schema !== "pg_catalog") { + return name; + } + + let base = name; + let isArray = false; + + if (base.endsWith("[]")) { + isArray = true; + base = base.slice(0, -2); + } else if (base.startsWith("_")) { + isArray = true; + base = base.slice(1); + } + + const alias = POSTGRES_DISPLAY_ALIASES[base]; + + if (!alias) { + return name; + } + + return isArray ? `${alias}[]` : alias; +} diff --git a/ui/studio/grid/DataGridHeaderCell.test.tsx b/ui/studio/grid/DataGridHeaderCell.test.tsx index f42d6b71..b3dcddd3 100644 --- a/ui/studio/grid/DataGridHeaderCell.test.tsx +++ b/ui/studio/grid/DataGridHeaderCell.test.tsx @@ -58,6 +58,36 @@ describe("DataGridHeaderCell", () => { container.remove(); }); + it("shows the common alias for native PostgreSQL catalog type names", () => { + const container = document.createElement("div"); + document.body.appendChild(container); + const root = createRoot(container); + + act(() => { + root.render( + , + ); + }); + + expect(container.textContent).toContain("bigint"); + expect(container.textContent).not.toContain("int8"); + + act(() => { + root.unmount(); + }); + container.remove(); + }); + it("wraps tooltip icons in trigger spans instead of rendering raw svg triggers", () => { const container = document.createElement("div"); document.body.appendChild(container); diff --git a/ui/studio/grid/DataGridHeaderCell.tsx b/ui/studio/grid/DataGridHeaderCell.tsx index 9c3c3a2f..583f3dc4 100644 --- a/ui/studio/grid/DataGridHeaderCell.tsx +++ b/ui/studio/grid/DataGridHeaderCell.tsx @@ -8,6 +8,7 @@ import { TooltipProvider, TooltipTrigger, } from "../../components/ui/tooltip"; +import { formatDatatypeName } from "../../lib/datatype-display"; function HeaderTooltipIcon(props: { children: ReactNode; tooltip: ReactNode }) { const { children, tooltip } = props; @@ -84,7 +85,7 @@ export function DataGridHeaderCell({ column }: { column: Column }) { )} {name} - {datatype.affinity || datatype.name} + {datatype.affinity || formatDatatypeName(datatype)} ); diff --git a/ui/studio/views/table/InlineTableFilters.tsx b/ui/studio/views/table/InlineTableFilters.tsx index b332cb51..735e853a 100644 --- a/ui/studio/views/table/InlineTableFilters.tsx +++ b/ui/studio/views/table/InlineTableFilters.tsx @@ -57,6 +57,7 @@ import { getSupportedFilterOperatorsForColumn, isFilterOperator, } from "../../../hooks/filter-utils"; +import { formatDatatypeName } from "../../../lib/datatype-display"; import { cn } from "../../../lib/utils"; import { buildSqlFilterLintStatement, @@ -132,7 +133,7 @@ interface SqlFilterLintSupport { } function getFilterColumnDatatypeLabel(tableColumn: Table["columns"][string]) { - return tableColumn.datatype.name; + return formatDatatypeName(tableColumn.datatype); } const SQL_FILTER_OPTION_LABEL = "SQL WHERE clause";