Tables & Schema
Table explorer, schema visualization, row browsing, column inspection, and safe data operations.
Tables & Schema
The AxiomDB dashboard provides a visual interface for exploring your branch's schema, browsing row data, inspecting columns and relationships, and performing safe data operations. No raw SQL required — though you can always drop to a SQL console when needed.
Table Explorer
The Table Explorer is the primary interface for navigating your database schema. It lists every table in the public schema, along with row counts, size estimates, and health indicators.
┌─────────────────────────────────────────────────────────────────┐
│ Table Explorer [Search tables...] │
│ │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ ● users 12,847 rows 4.2 MB ││
│ │ ● orders 89,203 rows 28.1 MB ││
│ │ ● order_items 234,102 rows 45.6 MB ││
│ │ ● products 342 rows 1.1 MB ││
│ │ ● payments 87,450 rows 18.3 MB ││
│ │ ● sessions 1,203,441 rows 98.7 MB ││
│ │ ○ _prisma_migrations 12 rows 0.1 MB ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
│ 7 tables · 196.1 MB total · 1,627,397 total rows │
└─────────────────────────────────────────────────────────────────┘Table Metadata
Each table entry shows:
| Field | Source | Description |
|---|---|---|
| Row count | pg_stat_user_tables.n_live_tup | Estimated row count. Updated by ANALYZE. |
| Table size | pg_relation_size() | Data pages only. |
| Total size | pg_total_relation_size() | Data + indexes + toast. |
| Last vacuum | pg_stat_user_tables.last_vacuum | Last manual VACUUM. |
| Last analyze | pg_stat_user_tables.last_analyze | Last ANALYZE. |
| Dead tuples | pg_stat_user_tables.n_dead_tup | Rows marked for deletion. |
Table Size Query
SELECT
c.relname AS table_name,
pg_size_pretty(pg_relation_size(c.oid)) AS table_size,
pg_size_pretty(pg_total_relation_size(c.oid)) AS total_size,
s.n_live_tup AS row_count,
s.n_dead_tup AS dead_tuples,
s.last_vacuum,
s.last_analyze
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
LEFT JOIN pg_stat_user_tables s ON s.relid = c.oid
WHERE n.nspname = 'public'
AND c.relkind = 'r'
ORDER BY pg_total_relation_size(c.oid) DESC;Schema Flow Visualizer
The Schema Flow Visualizer renders your database's entity-relationship diagram based on foreign key constraints. It automatically detects relationships and draws connections between tables.
┌──────────────────────────────────────────────────────────────────┐
│ Schema Flow [Zoom: 100%] │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ users │──────►│ orders │──────►│ order_items │ │
│ │ │ 1:N │ │ 1:N │ │ │
│ │ id (PK) │ │ id (PK) │ │ id (PK) │ │
│ │ email │ │ user_id │ │ order_id │ │
│ │ name │ │ status │ │ product_id │ │
│ └──────────┘ │ total │ │ quantity │ │
│ └─────┬────┘ │ price │ │
│ │ └──────┬───────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────┐ ┌──────────────┐ │
│ │ payments │ │ products │ │
│ │ │ │ │ │
│ │ id (PK) │ │ id (PK) │ │
│ │ order_id │ │ name │ │
│ │ amount │ │ price │ │
│ │ method │ │ stock │ │
│ └──────────┘ └──────────────┘ │
└──────────────────────────────────────────────────────────────────┘Relationship Detection
The visualizer queries foreign key constraints:
SELECT
tc.table_name AS source_table,
kcu.column_name AS source_column,
ccu.table_name AS target_table,
ccu.column_name AS target_column,
tc.constraint_name
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name
AND tc.table_schema = kcu.table_schema
JOIN information_schema.constraint_column_usage ccu
ON ccu.constraint_name = tc.constraint_name
AND ccu.table_schema = tc.table_schema
WHERE tc.constraint_type = 'FOREIGN KEY'
AND tc.table_schema = 'public'
ORDER BY tc.table_name;Cardinality Inference
Cardinality (1:1, 1:N, N:M) is inferred from:
- Unique constraints on the foreign key column → 1:1
- No unique constraint on the foreign key column → 1:N
- Junction table with two foreign keys → N:M
-- Check if foreign key column has a unique constraint
SELECT
kcu.column_name,
CASE WHEN tc.constraint_type = 'UNIQUE' THEN '1:1' ELSE '1:N' END AS cardinality
FROM information_schema.key_column_usage kcu
LEFT JOIN information_schema.table_constraints tc
ON kcu.constraint_name = tc.constraint_name
AND tc.constraint_type = 'UNIQUE'
WHERE kcu.table_name = 'orders'
AND kcu.column_name = 'user_id';Row Browser
The Row Browser lets you view, sort, filter, and paginate through table data without writing SQL.
┌─────────────────────────────────────────────────────────────────┐
│ orders · 89,203 rows [Filter] [Sort] [Export]│
│ │
│ ┌──────┬──────────┬────────┬─────────┬────────────┬──────────┐ │
│ │ id │ user_id │ status │ total │ created_at │ actions │ │
│ ├──────┼──────────┼────────┼─────────┼────────────┼──────────┤ │
│ │ 1001 │ usr_abc │ paid │ $49.99 │ 2025-03-20 │ [Edit] │ │
│ │ 1002 │ usr_def │ pending│ $129.50 │ 2025-03-20 │ [Edit] │ │
│ │ 1003 │ usr_ghi │ paid │ $19.99 │ 2025-03-19 │ [Edit] │ │
│ │ 1004 │ usr_jkl │ failed │ $89.00 │ 2025-03-19 │ [Edit] │ │
│ │ 1005 │ usr_mno │ paid │ $249.99 │ 2025-03-18 │ [Edit] │ │
│ └──────┴──────────┴────────┴─────────┴────────────┴──────────┘ │
│ │
│ Showing 1–5 of 89,203 [< Prev] Page 1 [Next >] │
└─────────────────────────────────────────────────────────────────┘Pagination
Rows are paginated using cursor-based pagination for performance:
-- First page
SELECT * FROM orders
ORDER BY id ASC
LIMIT 50;
-- Next page (cursor = last id from previous page)
SELECT * FROM orders
WHERE id > :cursor
ORDER BY id ASC
LIMIT 50;Why cursor pagination?
Offset-based pagination (OFFSET 10000 LIMIT 50) becomes slow on large tables because Postgres must scan and discard all preceding rows. Cursor pagination (WHERE id > :cursor) uses the index and is O(1) regardless of page position.
Sorting
Click any column header to sort. The browser generates:
-- Ascending
SELECT * FROM orders ORDER BY total ASC LIMIT 50;
-- Descending
SELECT * FROM orders ORDER BY total DESC LIMIT 50;Multi-column sorting is supported by clicking multiple headers while holding Shift.
Filtering
The filter builder generates WHERE clauses:
┌─────────────────────────────────────────┐
│ Filter │
│ │
│ [status] [equals] [paid] [AND] │
│ [total] [> ] [50.00] [Apply] │
│ │
└─────────────────────────────────────────┘Generated SQL:
SELECT * FROM orders
WHERE status = 'paid' AND total > 50.00
ORDER BY id ASC
LIMIT 50;Supported operators:
| Operator | Types | SQL |
|---|---|---|
equals | All | = $1 |
not equals | All | != $1 |
> | Numeric, Date | > $1 |
< | Numeric, Date | < $1 |
>= | Numeric, Date | >= $1 |
<= | Numeric, Date | <= $1 |
contains | Text | LIKE '%' || $1 || '%' |
starts with | Text | LIKE $1 || '%' |
is null | All | IS NULL |
is not null | All | IS NOT NULL |
in | All | IN ($1, $2, ...) |
Empty States
When a table has no rows, the Row Browser displays a helpful empty state:
┌─────────────────────────────────────────────────────────────────┐
│ orders · 0 rows │
│ │
│ ┌─────────────────┐ │
│ │ │ │
│ │ No rows yet │ │
│ │ │ │
│ │ Insert data │ │
│ │ via your app │ │
│ │ or run a │ │
│ │ migration. │ │
│ │ │ │
│ └─────────────────┘ │
│ │
│ [Open SQL Console] [View Schema] │
└─────────────────────────────────────────────────────────────────┘Empty Table Detection
SELECT
c.relname AS table_name,
s.n_live_tup AS row_count,
CASE WHEN s.n_live_tup = 0 THEN true ELSE false END AS is_empty
FROM pg_class c
JOIN pg_stat_user_tables s ON s.relid = c.oid
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname = 'public' AND c.relkind = 'r';Safe Data Operations
The dashboard provides safe, guarded data modification operations. Every mutation is wrapped in a transaction and requires confirmation.
Row Editing
Edit individual cells directly in the Row Browser:
-- Generated when you edit a cell
BEGIN;
UPDATE orders SET status = 'shipped' WHERE id = 1001;
COMMIT;Row Deletion
Delete individual rows with confirmation:
-- Generated when you delete a row
BEGIN;
DELETE FROM orders WHERE id = 1001;
COMMIT;Bulk Operations
Select multiple rows for batch operations:
┌─────────────────────────────────────────────────────────────────┐
│ orders · 3 rows selected [Delete] [Export]│
│ │
│ ☑ 1001 │ usr_abc │ paid │ $49.99 │ 2025-03-20 │
│ ☑ 1002 │ usr_def │ pending │ $129.50 │ 2025-03-20 │
│ ☑ 1003 │ usr_ghi │ paid │ $19.99 │ 2025-03-19 │
└─────────────────────────────────────────────────────────────────┘Generated SQL:
BEGIN;
DELETE FROM orders WHERE id IN (1001, 1002, 1003);
COMMIT;Write operations
All write operations through the dashboard use the owner role via DIRECT_URL. Changes are immediate and cannot be undone. Always review generated SQL before confirming.
Undo Support
The dashboard does not provide undo. However, every mutation is logged in the branch audit trail:
{
"event": "ROW_UPDATED",
"table": "orders",
"row_id": 1001,
"changes": {
"status": { "old": "pending", "new": "shipped" }
},
"actor": "usr_x9y8z7",
"timestamp": "2025-03-20T14:30:00Z"
}Column Inspection
Click on any table to view its column definitions:
┌─────────────────────────────────────────────────────────────────┐
│ orders · Columns │
│ │
│ ┌──────────────┬──────────────┬─────────┬─────────┬──────────┐ │
│ │ Column │ Type │ Nullable│ Default │ Key │ │
│ ├──────────────┼──────────────┼─────────┼─────────┼──────────┤ │
│ │ id │ bigint │ NO │ nextval │ PK │ │
│ │ user_id │ uuid │ NO │ — │ FK→users │ │
│ │ status │ varchar(20) │ NO │ 'pending│ — │ │
│ │ total │ numeric(10,2)│ NO │ — │ — │ │
│ │ created_at │ timestamptz │ NO │ now() │ — │ │
│ │ updated_at │ timestamptz │ YES │ — │ — │ │
│ │ notes │ text │ YES │ — │ — │ │
│ └──────────────┴──────────────┴─────────┴─────────┴──────────┘ │
│ │
│ Indexes │
│ ┌─────────────────────────────────┬──────────┬────────────────┐ │
│ │ Index Name │ Columns │ Unique │ │
│ ├─────────────────────────────────┼──────────┼────────────────┤ │
│ │ orders_pkey │ id │ Yes │ │
│ │ orders_user_id_idx │ user_id │ No │ │
│ │ orders_status_created_at_idx │ status, │ No │ │
│ │ │ created_at│ │ │
│ └─────────────────────────────────┴──────────┴────────────────┘ │
└─────────────────────────────────────────────────────────────────┘Column Metadata Query
SELECT
c.column_name,
c.data_type,
c.character_maximum_length,
c.numeric_precision,
c.is_nullable,
c.column_default,
CASE WHEN pk.column_name IS NOT NULL THEN 'PK' ELSE '' END AS is_primary_key,
CASE WHEN fk.column_name IS NOT NULL THEN 'FK' ELSE '' END AS is_foreign_key
FROM information_schema.columns c
LEFT JOIN (
SELECT kcu.column_name
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name
WHERE tc.constraint_type = 'PRIMARY KEY'
AND tc.table_name = 'orders'
AND tc.table_schema = 'public'
) pk ON pk.column_name = c.column_name
LEFT JOIN (
SELECT kcu.column_name
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name
WHERE tc.constraint_type = 'FOREIGN KEY'
AND tc.table_name = 'orders'
AND tc.table_schema = 'public'
) fk ON fk.column_name = c.column_name
WHERE c.table_name = 'orders'
AND c.table_schema = 'public'
ORDER BY c.ordinal_position;Foreign Key Relationships
The Column Inspector shows all foreign key relationships for the selected table:
SELECT
tc.constraint_name,
kcu.column_name AS source_column,
ccu.table_name AS referenced_table,
ccu.column_name AS referenced_column,
rc.update_rule,
rc.delete_rule
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name
JOIN information_schema.constraint_column_usage ccu
ON ccu.constraint_name = tc.constraint_name
JOIN information_schema.referential_constraints rc
ON rc.constraint_name = tc.constraint_name
WHERE tc.constraint_type = 'FOREIGN KEY'
AND tc.table_name = 'orders'
AND tc.table_schema = 'public';Cascade Rules
| Rule | Behavior on Parent Delete |
|---|---|
CASCADE | Child rows are automatically deleted. |
SET NULL | Foreign key column is set to NULL. |
SET DEFAULT | Foreign key column is set to its default value. |
RESTRICT | Delete is prevented if child rows exist. |
NO ACTION | Similar to RESTRICT; checked at end of transaction. |
Index Inspection
Each table's indexes are displayed with their size and usage statistics:
SELECT
i.relname AS index_name,
ix.indisunique AS is_unique,
ix.indisprimary AS is_primary,
pg_size_pretty(pg_relation_size(i.oid)) AS index_size,
s.idx_scan AS times_used,
pg_get_indexdef(ix.indexrelid) AS definition
FROM pg_index ix
JOIN pg_class i ON i.oid = ix.indexrelid
JOIN pg_class t ON t.oid = ix.indrelid
LEFT JOIN pg_stat_user_indexes s ON s.indexrelid = ix.indexrelid
WHERE t.relname = 'orders'
AND t.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'public')
ORDER BY pg_relation_size(i.oid) DESC;Unused Indexes
Identify indexes that have never been used:
SELECT
schemaname || '.' || relname AS table_name,
indexrelname AS index_name,
pg_size_pretty(pg_relation_size(indexrelid)) AS index_size,
idx_scan AS times_used
FROM pg_stat_user_indexes
WHERE idx_scan = 0
AND schemaname = 'public'
AND indexrelname NOT LIKE '%_pkey'
ORDER BY pg_relation_size(indexrelid) DESC;Index usage reset
The idx_scan counter resets when the server restarts or the index is dropped and recreated. Use pg_stat_reset() timing context to interpret accurately.
Data Export
Export table data directly from the Row Browser:
| Format | Use Case |
|---|---|
| CSV | Spreadsheet analysis, data interchange |
| JSON | Application integration, API payloads |
| SQL | Backup, migration scripts |
CSV Export Query
COPY (
SELECT * FROM orders WHERE status = 'paid'
) TO STDOUT WITH CSV HEADER;JSON Export Query
SELECT json_agg(row_to_json(t))
FROM (
SELECT * FROM orders WHERE status = 'paid'
) t;Quick Actions
From the Table Explorer, you can:
| Action | Description |
|---|---|
| View Data | Open the Row Browser for the selected table. |
| View Schema | See column definitions, types, and constraints. |
| View Indexes | List all indexes with size and usage stats. |
| View Relationships | See foreign keys pointing to and from the table. |
| Copy Table Name | Copy the fully qualified table name. |
| Generate SELECT | Copy a SELECT * FROM table query. |
| Export | Download table data as CSV, JSON, or SQL. |
| Open in SQL Console | Open a pre-filled query in the SQL console. |