Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/postgres-datatype-aliases.md
Original file line number Diff line number Diff line change
@@ -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[]`).
6 changes: 6 additions & 0 deletions FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 2 additions & 1 deletion ui/hooks/use-schema-visualization.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useMemo } from "react";

import { formatDatatypeName } from "../lib/datatype-display";
import { useIntrospection } from "./use-introspection";
import { useNavigation } from "./use-navigation";

Expand Down Expand Up @@ -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,
Expand Down
75 changes: 75 additions & 0 deletions ui/lib/datatype-display.test.ts
Original file line number Diff line number Diff line change
@@ -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");
});
});
59 changes: 59 additions & 0 deletions ui/lib/datatype-display.ts
Original file line number Diff line number Diff line change
@@ -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<string, string> = {
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<DataType, "name" | "schema">,
): 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;
}
30 changes: 30 additions & 0 deletions ui/studio/grid/DataGridHeaderCell.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<DataGridHeaderCell
column={
{
datatype: {
name: "int8",
schema: "pg_catalog",
},
name: "id",
} as Column
}
/>,
);
});

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);
Expand Down
3 changes: 2 additions & 1 deletion ui/studio/grid/DataGridHeaderCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -84,7 +85,7 @@ export function DataGridHeaderCell({ column }: { column: Column }) {
)}
<span className="min-w-0 truncate font-medium">{name}</span>
<span className="min-w-0 truncate lowercase text-muted-foreground/70">
{datatype.affinity || datatype.name}
{datatype.affinity || formatDatatypeName(datatype)}
</span>
</div>
);
Expand Down
3 changes: 2 additions & 1 deletion ui/studio/views/table/InlineTableFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import {
getSupportedFilterOperatorsForColumn,
isFilterOperator,
} from "../../../hooks/filter-utils";
import { formatDatatypeName } from "../../../lib/datatype-display";
import { cn } from "../../../lib/utils";
import {
buildSqlFilterLintStatement,
Expand Down Expand Up @@ -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";
Expand Down