Platform

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:

FieldSourceDescription
Row countpg_stat_user_tables.n_live_tupEstimated row count. Updated by ANALYZE.
Table sizepg_relation_size()Data pages only.
Total sizepg_total_relation_size()Data + indexes + toast.
Last vacuumpg_stat_user_tables.last_vacuumLast manual VACUUM.
Last analyzepg_stat_user_tables.last_analyzeLast ANALYZE.
Dead tuplespg_stat_user_tables.n_dead_tupRows 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:

  1. Unique constraints on the foreign key column → 1:1
  2. No unique constraint on the foreign key column → 1:N
  3. 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:

OperatorTypesSQL
equalsAll= $1
not equalsAll!= $1
>Numeric, Date> $1
<Numeric, Date< $1
>=Numeric, Date>= $1
<=Numeric, Date<= $1
containsTextLIKE '%' || $1 || '%'
starts withTextLIKE $1 || '%'
is nullAllIS NULL
is not nullAllIS NOT NULL
inAllIN ($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

RuleBehavior on Parent Delete
CASCADEChild rows are automatically deleted.
SET NULLForeign key column is set to NULL.
SET DEFAULTForeign key column is set to its default value.
RESTRICTDelete is prevented if child rows exist.
NO ACTIONSimilar 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:

FormatUse Case
CSVSpreadsheet analysis, data interchange
JSONApplication integration, API payloads
SQLBackup, 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:

ActionDescription
View DataOpen the Row Browser for the selected table.
View SchemaSee column definitions, types, and constraints.
View IndexesList all indexes with size and usage stats.
View RelationshipsSee foreign keys pointing to and from the table.
Copy Table NameCopy the fully qualified table name.
Generate SELECTCopy a SELECT * FROM table query.
ExportDownload table data as CSV, JSON, or SQL.
Open in SQL ConsoleOpen a pre-filled query in the SQL console.

On this page