Skip to content

improvement(tables): allow any printable characters in column display names#5580

Open
TheodoreSpeaks wants to merge 1 commit into
stagingfrom
feat/table-column-name
Open

improvement(tables): allow any printable characters in column display names#5580
TheodoreSpeaks wants to merge 1 commit into
stagingfrom
feat/table-column-name

Conversation

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator

Summary

  • Column display names now accept spaces, digits-first, punctuation, and unicode — the old identifier rule (^[a-z_][a-z0-9_]*$) dated from when names were the JSONB storage keys; since the stable-column-id refactor names are metadata-only, so only ids/table names keep the strict pattern (still the SQL injection guard)
  • New COLUMN_NAME_PATTERN rejects control/invisible characters (\p{C}), leading/trailing whitespace, and leading $ (filter-grammar collision); one shared write-time validator (assertValidNewColumnName) covers pattern/length/ci-uniqueness/id-shadowing across every column-creating path, including updateWorkflowGroup.newOutputColumns which previously validated nothing
  • CSV/JSON imports preserve raw headers as display names ("First Name" stays "First Name"); shared sanitizeColumnName + claim-style uniqueColumnName replace three divergent dedupe loops; async create-mode import now validates the inferred schema (previously persisted invalid schemas unchecked)
  • Name→id wire translation is now case-insensitive, matching schema uniqueness — miscased row-data keys from API/agent callers no longer silently drop
  • Caller-supplied column ids are validated at the contract boundary; enrichment output naming preserves typed names (create previously slugged what edit preserved); CSV dialog skip/create sentinels moved to NUL-prefixed values since the old whitespace sentinels became valid names

Type of Change

  • Improvement

Testing

1236 tests pass across table/import/contract/copilot suites (new coverage for the pattern, sanitizers, ci translation, and id-shadow guard); bun run lint, check:api-validation:strict pass; tsc clean

Known limitation (pre-existing resolver behavior, now more visible): workflow tag references can't address column names containing spaces/dots — referencing the parent object in a function block works.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel

vercel Bot commented Jul 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment Jul 10, 2026 11:53pm

Request Review

@cursor

cursor Bot commented Jul 10, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Broad changes to table schema validation, import coercion, and API contracts affect every column create/rename/import path; behavior shifts (human-readable names, case-insensitive wire keys) could surprise existing integrations but storage keys remain id-based.

Overview
Column display names are no longer restricted to SQL-style identifiers. Names can include spaces, leading digits, punctuation, and unicode; stable column ids (and table names) still use the strict NAME_PATTERN for JSONB/SQL safety.

Validation is centralized on COLUMN_NAME_PATTERN (rejects control/invisible chars, edge whitespace, leading $) and assertValidNewColumnName (pattern, length, case-insensitive uniqueness, and id shadowing). That validator replaces scattered checks across column CRUD, workflow-group output columns (including updateWorkflowGroup.newOutputColumns, which previously skipped validation), and API columnIdSchema for optional caller-supplied ids.

Imports and naming: CSV/JSON use sanitizeColumnName + uniqueColumnName so headers like First Name stay as display names instead of being slugged; async create-mode import now runs validateTableSchema before persisting. Wire name→id maps and row/filter/sort translation are case-insensitive, matching schema uniqueness.

API/UI: OpenAPI drops the column-name regex; column routes map id conflict errors to 400; CSV import dialog skip/create sentinels use NUL-prefixed values; column rename inputs get maxLength. Enrichment and copilot paths use uniqueColumnName with lowercased taken sets; filter/sort SQL errors for unknown fields say "Unknown field" instead of identifier rules.

Reviewed by Cursor Bugbot for commit ee12cc3. Bugbot is set up for automated code reviews on this repo. Configure here.

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ee12cc3. Configure here.

assertNewOutputColumns(
(data.newOutputColumns ?? []).map((c) => c.name),
schema
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Group update blocks freed names

Medium Severity

assertNewOutputColumns runs against the full pre-update column list before the output diff removes columns. Saving a workflow group that drops an output and adds a new one whose derived display name matches the removed column fails with an “already exists” style error even though that name would be free after the same update.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ee12cc3. Configure here.

@greptile-apps

greptile-apps Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR broadens table column display names while keeping storage keys strict. The main changes are:

  • Printable display names for table columns.
  • Shared validation for new column names and caller-supplied ids.
  • CSV and JSON import naming that preserves raw headers.
  • Case-insensitive name-to-id translation for row data, filters, and sorts.
  • Workflow and enrichment output naming updates.

Confidence Score: 4/5

The changed table-name flow looks mergeable after fixing two contained edge cases.

  • Case-variant display names can still shadow storage ids in later name-based lookups.
  • Workflow group updates can reject a valid remove-and-reuse output-name edit.
  • The import and validation paths otherwise follow the new display-name contract.

apps/sim/lib/table/column-keys.ts and apps/sim/lib/table/workflow-groups/service.ts

Important Files Changed

Filename Overview
apps/sim/lib/table/column-keys.ts Adds shared display-name validation and case-insensitive name-to-id translation, with a remaining case-variant id-shadow edge case.
apps/sim/lib/table/import.ts Updates import sanitization and mapping to preserve printable headers while deduping names case-insensitively.
apps/sim/lib/table/workflow-groups/service.ts Centralizes workflow output column validation, but validates update additions before removed outputs are excluded.
apps/sim/lib/table/column-naming.ts Introduces shared unique-name claiming for imports, enrichments, and workflow output derivation.

Reviews (1): Last reviewed commit: "improvement(tables): allow any printable..." | Re-trigger Greptile

name: string,
excludeIndex = -1
): boolean {
return columns.some((c, i) => i !== excludeIndex && getColumnId(c) === name)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Case-Variant Id Shadow

A display name like COL_ABC passes this check when another column's storage id is col_abc, but later name-based resolvers compare display names case-insensitively. If an API caller uses COL_ABC as a filter, sort, or upsert conflict target, it can resolve to the column whose name lowercases to col_abc instead of the intended new column.

Suggested change
return columns.some((c, i) => i !== excludeIndex && getColumnId(c) === name)
const lower = name.toLowerCase()
return columns.some((c, i) => i !== excludeIndex && getColumnId(c).toLowerCase() === lower)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +307 to +309
assertNewOutputColumns(
(data.newOutputColumns ?? []).map((c) => c.name),
schema

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Removed Output Name Stays Taken

This validates new output column names against the pre-update schema, so replacing an output while keeping the same display name is rejected before the old output is removed. A workflow edit that removes output score and adds a new output named score throws Column "score" already exists, even though the final schema would contain only the new column.

@greptile-apps

greptile-apps Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR relaxes table column display names while keeping storage ids strict. The main changes are:

  • Printable display names are accepted across API contracts and services.
  • Shared validators now handle name format, length, duplicates, and id shadowing.
  • CSV and JSON import paths preserve display headers and share name deduping.
  • Name-to-id translation now matches display names case-insensitively.
  • Workflow and enrichment output naming now use shared naming helpers.

Confidence Score: 4/5

The workflow-group update path needs a fix before merging.

  • Replacing an output column with a new output using the same display name can be rejected before removals are applied.
  • The main validation, import, and name-to-id translation changes otherwise have matching guards in the changed code.

apps/sim/lib/table/workflow-groups/service.ts

Important Files Changed

Filename Overview
apps/sim/lib/table/workflow-groups/service.ts Adds shared validation for workflow output columns, but validates new names before same-request removals are applied.
apps/sim/lib/table/column-keys.ts Adds shared display-name validation, id-shadow checks, and case-insensitive name-to-id lookup.
apps/sim/lib/table/import.ts Updates CSV and JSON header sanitization to preserve printable display names and use shared deduping.
apps/sim/lib/table/column-naming.ts Adds shared unique-name claiming and updates workflow output name derivation to claim names in the passed set.
apps/sim/lib/api/contracts/tables.ts Updates table API schemas so display names are relaxed while column ids remain identifier-safe.

Reviews (2): Last reviewed commit: "improvement(tables): allow any printable..." | Re-trigger Greptile

Comment on lines +307 to +310
assertNewOutputColumns(
(data.newOutputColumns ?? []).map((c) => c.name),
schema
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Pre-Removal Name Check

When a group update removes an existing output column and adds a new output with the same display name in the same request, this validation still checks against the pre-update schema. The request is rejected as a duplicate even though the later removal would leave a valid final schema with only one column by that name.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant