Skip to content

Indexes & FK Cascade Rules — Frontend Integration

Backend branch: refactor/remove-recover-with-item-antipattern


What's New

FeatureStatusFE Work Needed
Database indexes (create/list/delete)Backend completeYes — new UI panel
FK cascade rules (ON DELETE / ON UPDATE)Backend + Frontend completeDropdowns in column properties panel
Schema import (indexes + cascades)Backend completeNo — auto-detected on import

1. Indexes API

Endpoints

MethodPathAuthPermission
GET/api/indexes/view/{viewUuid}JWTVIEW_DATA
POST/api/indexes/view/{viewUuid}JWTCONFIGURE_VIEW
DELETE/api/indexes/{indexUuid}JWTCONFIGURE_VIEW

TypeScript Types

typescript
interface IndexDTO {
  uuid?: string;          // Read-only — assigned by backend on creation
  name: string;           // Required — index name, e.g. "idx_orders_customer_date"
  columnNames: string[];  // Required — column names in the index
  isUnique?: boolean;     // Optional — default false
}

Create Index

typescript
// POST /api/indexes/view/{viewUuid}  →  201 Created
const index = await api.post<IndexDTO>(`/api/indexes/view/${viewUuid}`, {
  name: 'idx_orders_customer_date',
  columnNames: ['customer_id', 'order_date'],
  isUnique: false,
}, {
  headers: { 'X-Client-Id': clientTabId },
});
// index.uuid is now set

Error codes:

  • 400name or columnNames missing/empty
  • 403 — user lacks CONFIGURE_VIEW permission
  • 404 — view not found

Async note: The 201 response means the metadata record exists. The actual CREATE INDEX DDL runs asynchronously via the processor. No SSE event is fired specifically for index operations yet — the index is usable immediately from the metadata side, and the DB-side index appears within seconds.

List Indexes

typescript
// GET /api/indexes/view/{viewUuid}  →  200 OK
const indexes = await api.get<IndexDTO[]>(`/api/indexes/view/${viewUuid}`);
// Returns [] when no indexes exist

Delete Index

typescript
// DELETE /api/indexes/{indexUuid}  →  204 No Content
await api.delete(`/api/indexes/${index.uuid}`, {
  headers: { 'X-Client-Id': clientTabId },
});

UI Suggestion

An "Indexes" tab or section in the View Settings/Configuration panel (alongside entity constraints). Minimal UI:

  • Table with columns: Name, Columns (chip list), Unique (badge), Delete (icon button)
  • "Add Index" form: name input, multi-select for columns (populated from view's column list), unique checkbox
  • Column names in the dropdown should use the database column names (from advancedOptions.name or the column's fieldName), not display names

2. FK Cascade Rules (ON DELETE / ON UPDATE)

What It Is

Foreign key columns can specify what happens when the referenced parent row is deleted or updated. This maps directly to PostgreSQL's ON DELETE and ON UPDATE clauses.

Status: Implemented in both backend and frontend. The column properties panel shows "On Delete" and "On Update" dropdowns in the Referential Actions section for FK columns.

Allowed Values

ValueLabel for UION DELETE MeaningON UPDATE Meaning
CASCADECascadeDelete child rowsUpdate FK to new key
SET_NULLSet NullSet FK to NULLSet FK to NULL
RESTRICTRestrictBlock delete if children existBlock update if referenced
NO_ACTIONNo ActionSame as Restrict (DB default)Same as Restrict (DB default)
SET_DEFAULTSet DefaultSet FK to default valueSet FK to default value
null(not set)DB default (No Action)DB default (No Action)

How to Read It

Both values are returned on column fetch:

typescript
// GET /api/columns/view/{viewUuid}
column.advancedOptions?.onDeleteAction  // "CASCADE" | "SET_NULL" | ... | null
column.advancedOptions?.onUpdateAction  // "CASCADE" | "SET_NULL" | ... | null

How to Set It

Use the existing column update endpoint:

typescript
// PATCH /api/columns/item/{columnUuid}  →  200 OK (sync) or 202 (async)
await api.patch(`/api/columns/item/${columnUuid}`, {
  advancedOptions: {
    onDeleteAction: 'CASCADE',
    onUpdateAction: 'CASCADE',
  },
}, {
  headers: { 'X-Client-Id': clientTabId },
});

Async migration: Changing cascade actions is a schema change — the backend drops and recreates the FK constraint with the new actions. This follows the same async migration flow as changing nullable/unique/type.

Schema Import

When importing an existing database, both DELETE_RULE and UPDATE_RULE are read from JDBC metadata and stored on the FK column. The referenced_table_name is also stored for DDL generation.

Validation

  • SET_NULL requires the FK column to be nullable — the frontend shows a warning if the column is NOT NULL
  • Both actions are included in the migration impact analysis and dry-run preview

3. Schema Import — No FE Changes

When users import an existing database schema, the backend now automatically:

  • Detects and stores ON DELETE rules on FK columns
  • Detects and stores database indexes (composite, non-unique)
  • Correctly handles composite unique indexes (no longer flags individual columns as unique)

This data is immediately available via the Indexes API and column advancedOptions.onDeleteAction after import. No frontend changes needed for the import flow itself.


Not Included / Future

  • SSE events for index changes — not implemented yet. If we want real-time index updates across tabs, we'd need a new workspace SSE event type. For now, refreshing the indexes list after create/delete is sufficient.
  • Index rename — not supported. Delete and recreate instead.
  • Partial index / expression index — not supported yet (only simple column-based indexes).

SchemaStack Internal Developer Documentation