mirror of
https://github.com/Rohithgilla12/data-peek
synced 2026-04-21 12:57:16 +00:00
19 KiB
19 KiB
Table Designer Feature - Implementation Plan
Overview
This plan outlines the implementation of a comprehensive Table Designer feature for data-peek, enabling users to create new tables and modify existing tables through a dedicated tab interface with full PostgreSQL feature support.
Scope
- Create Table: Full-featured table creation with columns, constraints, indexes
- Edit Table (ALTER TABLE): Add/drop/rename columns, modify types, manage constraints and indexes
- PostgreSQL Full Features: Sequences, partitioning, inheritance, custom types, CHECK constraints, UNIQUE constraints
Phase 1: Shared Types & Data Models
File: packages/shared/src/index.ts
Add new types for DDL operations:
// PostgreSQL data types for dropdown
export type PostgresDataType =
| 'smallint' | 'integer' | 'bigint' | 'serial' | 'bigserial'
| 'numeric' | 'real' | 'double precision' | 'money'
| 'char' | 'varchar' | 'text'
| 'bytea'
| 'timestamp' | 'timestamptz' | 'date' | 'time' | 'timetz' | 'interval'
| 'boolean'
| 'uuid'
| 'json' | 'jsonb'
| 'xml'
| 'point' | 'line' | 'lseg' | 'box' | 'path' | 'polygon' | 'circle'
| 'cidr' | 'inet' | 'macaddr'
| 'int4range' | 'int8range' | 'numrange' | 'tsrange' | 'tstzrange' | 'daterange'
| 'custom' // For custom/enum types
// Column definition for table designer
export interface ColumnDefinition {
id: string // Client-side tracking
name: string
dataType: PostgresDataType | string
length?: number // For varchar(n), char(n)
precision?: number // For numeric(p,s)
scale?: number
isNullable: boolean
isPrimaryKey: boolean
isUnique: boolean
defaultValue?: string
defaultType?: 'value' | 'expression' | 'sequence'
sequenceName?: string // For nextval('sequence')
checkConstraint?: string
comment?: string
collation?: string
isArray?: boolean // For array types like text[]
}
// Constraint types
export type ConstraintType = 'primary_key' | 'foreign_key' | 'unique' | 'check' | 'exclude'
export interface ConstraintDefinition {
id: string
name?: string // Optional, auto-generated if not provided
type: ConstraintType
columns: string[]
// Foreign key specific
referencedSchema?: string
referencedTable?: string
referencedColumns?: string[]
onUpdate?: 'NO ACTION' | 'RESTRICT' | 'CASCADE' | 'SET NULL' | 'SET DEFAULT'
onDelete?: 'NO ACTION' | 'RESTRICT' | 'CASCADE' | 'SET NULL' | 'SET DEFAULT'
// Check constraint specific
checkExpression?: string
// Exclude constraint specific (advanced)
excludeElements?: Array<{ column: string; operator: string }>
excludeUsing?: 'btree' | 'gist' | 'gin' | 'hash'
}
// Index definition
export interface IndexDefinition {
id: string
name?: string
columns: Array<{
name: string
order?: 'ASC' | 'DESC'
nullsPosition?: 'FIRST' | 'LAST'
}>
isUnique: boolean
method?: 'btree' | 'hash' | 'gist' | 'gin' | 'spgist' | 'brin'
where?: string // Partial index condition
include?: string[] // INCLUDE columns (covering index)
concurrent?: boolean
}
// Table partitioning
export interface PartitionDefinition {
type: 'RANGE' | 'LIST' | 'HASH'
columns: string[]
}
// Full table definition
export interface TableDefinition {
schema: string
name: string
columns: ColumnDefinition[]
constraints: ConstraintDefinition[]
indexes: IndexDefinition[]
partition?: PartitionDefinition
inherits?: string[] // Parent tables
tablespace?: string
comment?: string
withOids?: boolean
unlogged?: boolean
}
// ALTER TABLE operations
export type AlterColumnOperation =
| { type: 'add'; column: ColumnDefinition }
| { type: 'drop'; columnName: string; cascade?: boolean }
| { type: 'rename'; oldName: string; newName: string }
| { type: 'set_type'; columnName: string; newType: string; using?: string }
| { type: 'set_nullable'; columnName: string; nullable: boolean }
| { type: 'set_default'; columnName: string; defaultValue: string | null }
| { type: 'set_comment'; columnName: string; comment: string | null }
export type AlterConstraintOperation =
| { type: 'add_constraint'; constraint: ConstraintDefinition }
| { type: 'drop_constraint'; name: string; cascade?: boolean }
| { type: 'rename_constraint'; oldName: string; newName: string }
export type AlterIndexOperation =
| { type: 'create_index'; index: IndexDefinition }
| { type: 'drop_index'; name: string; cascade?: boolean; concurrent?: boolean }
| { type: 'rename_index'; oldName: string; newName: string }
| { type: 'reindex'; name: string; concurrent?: boolean }
export interface AlterTableBatch {
schema: string
table: string
columnOperations: AlterColumnOperation[]
constraintOperations: AlterConstraintOperation[]
indexOperations: AlterIndexOperation[]
renameTable?: string
setSchema?: string
comment?: string | null
}
// Result types
export interface DDLResult {
success: boolean
executedSql: string[]
errors?: string[]
}
// Metadata for UI
export interface SequenceInfo {
schema: string
name: string
dataType: string
startValue: string
increment: string
}
export interface CustomTypeInfo {
schema: string
name: string
type: 'enum' | 'composite' | 'range' | 'domain'
values?: string[] // For enums
}
Phase 2: DDL SQL Builder
New File: apps/desktop/src/main/ddl-builder.ts
Create a dedicated builder for DDL statements:
// Key functions to implement:
export function buildCreateTable(definition: TableDefinition): ParameterizedQuery
export function buildAlterTable(batch: AlterTableBatch): ParameterizedQuery[]
export function buildDropTable(schema: string, table: string, cascade?: boolean): ParameterizedQuery
export function buildCreateIndex(schema: string, table: string, index: IndexDefinition): ParameterizedQuery
export function buildPreviewDDL(definition: TableDefinition): string // For UI preview
export function buildAlterPreviewDDL(batch: AlterTableBatch): string[]
Key considerations:
- Quote all identifiers properly
- Handle reserved words
- Escape string values in defaults
- Support all PostgreSQL constraint syntax
- Handle partial indexes with WHERE
- Support expression indexes
Phase 3: IPC Handlers
File: apps/desktop/src/main/index.ts
Add new IPC handlers:
// Create table
ipcMain.handle('db:create-table', async (_, { config, definition }) => {
// Execute CREATE TABLE statement
// Return DDLResult
})
// Alter table
ipcMain.handle('db:alter-table', async (_, { config, batch }) => {
// Execute ALTER TABLE statements in transaction
// Return DDLResult
})
// Drop table
ipcMain.handle('db:drop-table', async (_, { config, schema, table, cascade }) => {
// Execute DROP TABLE
// Return DDLResult
})
// Get table DDL (reverse engineer)
ipcMain.handle('db:get-table-ddl', async (_, { config, schema, table }) => {
// Query pg_catalog to reconstruct CREATE TABLE
// Return TableDefinition
})
// Get available sequences
ipcMain.handle('db:get-sequences', async (_, config) => {
// Query pg_sequences
// Return SequenceInfo[]
})
// Get custom types
ipcMain.handle('db:get-types', async (_, config) => {
// Query pg_type for enums, composites, etc.
// Return CustomTypeInfo[]
})
// Preview DDL without executing
ipcMain.handle('db:preview-ddl', async (_, { definition }) => {
// Return generated SQL string
})
File: apps/desktop/src/preload/index.ts
Update API exposure:
const api = {
// ... existing
ddl: {
createTable: (config, definition) => ipcRenderer.invoke('db:create-table', { config, definition }),
alterTable: (config, batch) => ipcRenderer.invoke('db:alter-table', { config, batch }),
dropTable: (config, schema, table, cascade) =>
ipcRenderer.invoke('db:drop-table', { config, schema, table, cascade }),
getTableDDL: (config, schema, table) =>
ipcRenderer.invoke('db:get-table-ddl', { config, schema, table }),
getSequences: (config) => ipcRenderer.invoke('db:get-sequences', config),
getTypes: (config) => ipcRenderer.invoke('db:get-types', config),
previewDDL: (definition) => ipcRenderer.invoke('db:preview-ddl', { definition })
}
}
Phase 4: Tab Store Extension
File: apps/desktop/src/renderer/src/stores/tab-store.ts
Add new tab type:
export type TabType = 'query' | 'table-preview' | 'erd' | 'table-designer'
export interface TableDesignerTab extends BaseTab {
type: 'table-designer'
schemaName: string
tableName?: string // undefined for new table
mode: 'create' | 'edit'
isDirty: boolean
}
// New actions
createTableDesignerTab: (connectionId: string, schemaName: string, tableName?: string) => string
Phase 5: DDL Store
New File: apps/desktop/src/renderer/src/stores/ddl-store.ts
Create Zustand store for table designer state:
interface DDLState {
// Per-tab state (keyed by tabId)
tabState: Map<string, TableDesignerState>
// Actions
initializeTab: (tabId: string, mode: 'create' | 'edit', definition?: TableDefinition) => void
// Column operations
addColumn: (tabId: string, column?: Partial<ColumnDefinition>) => void
updateColumn: (tabId: string, columnId: string, updates: Partial<ColumnDefinition>) => void
removeColumn: (tabId: string, columnId: string) => void
reorderColumns: (tabId: string, startIndex: number, endIndex: number) => void
// Constraint operations
addConstraint: (tabId: string, constraint: ConstraintDefinition) => void
updateConstraint: (tabId: string, constraintId: string, updates: Partial<ConstraintDefinition>) => void
removeConstraint: (tabId: string, constraintId: string) => void
// Index operations
addIndex: (tabId: string, index: IndexDefinition) => void
updateIndex: (tabId: string, indexId: string, updates: Partial<IndexDefinition>) => void
removeIndex: (tabId: string, indexId: string) => void
// Table properties
updateTableProperties: (tabId: string, updates: Partial<TableDefinition>) => void
// Diff tracking for edit mode
getAlterTableBatch: (tabId: string) => AlterTableBatch | null
// Validation
validateDefinition: (tabId: string) => { valid: boolean; errors: string[] }
// Cleanup
clearTabState: (tabId: string) => void
}
interface TableDesignerState {
original?: TableDefinition // For edit mode - track original state
current: TableDefinition
isDirty: boolean
validationErrors: string[]
}
Phase 6: UI Components
New Components Structure:
src/renderer/src/components/
├── table-designer/
│ ├── table-designer.tsx # Main container
│ ├── column-list.tsx # Column grid with drag-drop
│ ├── column-editor-row.tsx # Inline column editing
│ ├── column-editor-dialog.tsx # Full column editor modal
│ ├── constraint-list.tsx # Constraint management
│ ├── constraint-editor-dialog.tsx # Add/edit constraint
│ ├── index-list.tsx # Index management
│ ├── index-editor-dialog.tsx # Add/edit index
│ ├── table-properties.tsx # Table-level settings
│ ├── ddl-preview-panel.tsx # Live SQL preview
│ ├── data-type-select.tsx # Type picker with search
│ └── sequence-select.tsx # Sequence picker
Main Component: table-designer.tsx
Layout:
┌─────────────────────────────────────────────────────────────────┐
│ Toolbar: [Save] [Preview SQL] [Cancel] | Table: schema.name │
├─────────────────────────────────────────────────────────────────┤
│ Tabs: [Columns] [Constraints] [Indexes] [Properties] │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Column Grid (primary view) │ │
│ │ ┌─────┬──────────┬──────────┬────┬────┬────┬─────────┐ │ │
│ │ │ ⋮⋮ │ Name │ Type │ PK │ NN │ UQ │ Default │ │ │
│ │ ├─────┼──────────┼──────────┼────┼────┼────┼─────────┤ │ │
│ │ │ ⋮⋮ │ id │ uuid │ ✓ │ ✓ │ │ gen_... │ │ │
│ │ │ ⋮⋮ │ name │ varchar │ │ ✓ │ │ │ │ │
│ │ │ ⋮⋮ │ email │ varchar │ │ ✓ │ ✓ │ │ │ │
│ │ │ ⋮⋮ │ created │ timestamp│ │ ✓ │ │ now() │ │ │
│ │ └─────┴──────────┴──────────┴────┴────┴────┴─────────┘ │ │
│ │ [+ Add Column] │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ DDL Preview (collapsible) │ │
│ │ CREATE TABLE schema.table ( │ │
│ │ id uuid PRIMARY KEY DEFAULT gen_random_uuid(), │ │
│ │ ... │ │
│ │ ); │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
Features by Tab:
Columns Tab:
- Drag-drop reordering
- Inline editing for quick changes
- Click to expand for advanced options
- Visual indicators: PK (key icon), NOT NULL (*), UNIQUE (U)
- Type suggestions as you type
- Array type toggle
- Collation selector for text types
Constraints Tab:
- List view with constraint type badges
- Add constraint button with type selector
- Foreign key builder with table/column pickers
- Check constraint with SQL editor
- Unique constraint with column multi-select
Indexes Tab:
- List of indexes with method badges
- Column selector with order (ASC/DESC)
- Partial index condition editor
- INCLUDE column selector
- CONCURRENT option toggle
Properties Tab:
- Table comment
- Schema selector (for changing schema)
- Tablespace selector
- Partition configuration
- Inheritance configuration
- UNLOGGED toggle
Phase 7: Integration Points
Schema Explorer Updates
Add to schema-explorer.tsx:
- "Create Table" button in schema header:
<Button onClick={() => createTableDesignerTab(connectionId, schema.name)}>
<Plus className="size-3.5" />
</Button>
- Context menu on tables:
<ContextMenu>
<ContextMenuItem onClick={() => createTableDesignerTab(connectionId, schema.name, table.name)}>
Edit Table Structure
</ContextMenuItem>
<ContextMenuItem onClick={() => handleDropTable(schema.name, table.name)}>
Drop Table...
</ContextMenuItem>
</ContextMenu>
Tab Container Updates
Add to tab-container.tsx:
case 'table-designer':
return <TableDesigner tabId={tab.id} />
Phase 8: Implementation Order
Step 1: Foundation (Day 1-2)
- Add all shared types to
packages/shared/src/index.ts - Create
ddl-builder.tswith CREATE TABLE generation - Add basic IPC handlers for create-table and preview-ddl
Step 2: Basic UI (Day 2-3)
- Create DDL store
- Add new tab type to tab-store
- Create basic TableDesigner component with column list
- Add column editing (inline + dialog)
- Add DDL preview panel
Step 3: Column Features (Day 3-4)
- Implement all column properties
- Add data type selector with all PostgreSQL types
- Add sequence picker for defaults
- Implement column validation
Step 4: Constraints (Day 4-5)
- Add constraint list UI
- Implement PRIMARY KEY constraint
- Implement FOREIGN KEY with table/column picker
- Implement UNIQUE and CHECK constraints
Step 5: Indexes (Day 5)
- Add index list UI
- Implement index creation with all options
- Add partial index support
Step 6: ALTER TABLE (Day 6-7)
- Implement
db:get-table-ddlhandler (reverse engineer table) - Implement diff tracking in DDL store
- Generate ALTER TABLE statements
- Handle column type changes with USING
Step 7: Advanced Features (Day 7-8)
- Add partitioning support
- Add inheritance support
- Add custom type support
- Add schema explorer integration (create button, context menu)
Step 8: Polish (Day 8)
- Validation and error handling
- Keyboard shortcuts
- Undo/redo support
- Test with various edge cases
Keyboard Shortcuts
| Shortcut | Action |
|---|---|
Cmd+S |
Save/Execute DDL |
Cmd+Enter |
Preview SQL |
Cmd+N |
Add new column |
Delete |
Remove selected column |
Cmd+Z |
Undo last change |
Cmd+Shift+Z |
Redo |
Tab |
Move to next column field |
Shift+Tab |
Move to previous field |
Validation Rules
- Table name: Required, valid identifier, not reserved word
- Column names: Required, unique within table, valid identifier
- Primary key: At most one (can be composite)
- Foreign keys: Referenced table must exist, column types must match
- Check constraints: Valid SQL expression
- Default values: Type-compatible with column
Error Handling
- Pre-execution validation: Show errors inline before attempting execution
- Execution errors: Display PostgreSQL error message clearly
- Partial success: If ALTER TABLE fails mid-batch, show which operations succeeded
- Recovery: Provide option to rollback or continue
Testing Scenarios
- Create simple table with various column types
- Create table with composite primary key
- Create table with foreign keys to multiple tables
- Create table with CHECK constraints
- Create table with partial indexes
- Edit existing table - add column
- Edit existing table - change column type
- Edit existing table - add/remove constraints
- Drop table with cascade
- Create table with partitioning