v0.5.0 — Grid Interactivity, Home Polish, Deeper PostgreSQL (#8)
* chore: bump version to 0.5.0 (Task 1) * feat(store): v7 migration — favorites + recent_connections (Task 2) * feat(models): add favorite to Connection + Store CRUD (Task 3) * feat(types): ColumnInfo editability + IndexInfo/ConstraintInfo/RecentConnection (Task 4) * chore: bump version to 0.5.0 (Task 1) — lockfile * feat(commands): typed wrappers for favorites/recents/indexes/constraints (Task 5) * feat(db): PG indexes/constraints queries + matview UNION in tables (Task 6) * feat(db): get_table_data editability flags + ctid/rowid locator (Task 7) * feat(db): execute_change no-PK locator guard + affected-count check (Task 8) * feat(db): preserve bigint precision as string on PG read path (Task 9) * feat(db): get_indexes / get_constraints commands (Task 10) * feat(store): favorites + recents Store methods (Task 11) * feat(commands): favorites/recents IPC + register indexes/constraints (Task 12) * feat(store): connectionStore favorites/recents/move-selection (Task 13) * feat(lib): recent-connections pure helpers (Task 14) * feat(store): dbViewerStore indexes/constraints + stageCellEdit (Task 15) * feat(grid): pure editability + filter-operator + cell transform (Task 16) * feat(grid): pure keyboard-nav helper (Task 17) * feat(grid): CellEditor inline editor (Task 18) * feat(grid): CellContextMenu + RowDetailDrawer (Task 19) * feat(grid): focus model + keyboard nav + inline edit + copy + context menu (Task 20) * feat(db-viewer): FilterBuilder drag-and-drop + type-aware operators (Task 21) * feat(db-viewer): ObjectExplorer indexes/constraints/procedures + matview icon (Task 22) * feat(home): ConnectionCard favorite star + on-demand StatusDot (Task 23) * feat(home): move-to-folder + recents strip + status wiring (Task 24) * feat(db-viewer): grid wiring + matview read-only + post-commit refetch (Task 25) * docs: v0.5.0 status + roadmap updates (Task 26) * polish: empty/error/loading states for v0.5.0 surfaces (Task 28) * feat(home): duplicateConnection + useConnectionStatus hook, drop StatusDot (FEAT-A) * feat(home): connection card kebab menu — favorite/test/manage (FEAT-B) * fix(home): populate server_version/latency_ms in test_connection + clean online display * style(home): swap grab handle and kebab positions on connection card * style(home): nudge kebab menu to right-1 * style(home): nudge kebab menu to right-0.5 * feat(home): Escape clears + exits focused search * feat(grid): context-menu View/Select Row, outside-click close, Esc cancels edit, FK reference (GRID-A) * feat(grid): smart CellEditor — enum select, FK searchable dropdown, textarea height (GRID-B) * feat(grid): enums + FK options fed into CellEditor (GRID-C) * feat(grid): FK dropdown display-column labels + placeholder + empty state * fix(grid): FK dropdown renders as fixed overlay to avoid clipping * fix(grid): portal FK dropdown to body + FK reference icon instead of click-to-open * style(grid): move FK reference icon to the start of the cell * feat(grid): optimistic staged cell values + pending dot, cleared on refetch * fix(grid): queue is source of truth for staged values — value diff, Clear All clears dots, same-cell edits replace * fix(db-viewer): type getLocator for staged-value matching * test(db-viewer): unit-test deriveStagedValues; fix activeTab null guard * fix(db-viewer): pass table prop to VirtualDataGrid — staged edits now carry the table name * test(db-viewer): use index access instead of .at() for TS lib target * feat(grid): FK dropdown options as one-row column cells (FK-reference style, cap 5) * style(grid): FK dropdown — values only, fixed 360px width, FK-viewer surface styling * style(grid): harden FK dropdown minWidth to 360px * style(grid): cap FK dropdown cells at 3 * style(grid): cap FK dropdown cells at 4 * fix(grid): pending dot clears on commit — values stay until refetch * fix(db): deserialize pg_attribute char columns as i8 — no more panic on get_table_data * docs: reflect grid interactivity, smart editors, optimistic queue, FK reference, kebab status
This commit is contained in:
@@ -26,12 +26,19 @@ pub fn pg_tables_query(schema: Option<&str>) -> String {
|
||||
match schema {
|
||||
Some(s) => format!(
|
||||
"SELECT table_name, table_type FROM information_schema.tables \
|
||||
WHERE table_schema = '{}' ORDER BY table_name",
|
||||
s
|
||||
WHERE table_schema = '{}' \
|
||||
UNION ALL \
|
||||
SELECT matviewname AS table_name, 'MATERIALIZED VIEW' AS table_type \
|
||||
FROM pg_matviews WHERE schemaname = '{}' \
|
||||
ORDER BY table_name",
|
||||
s, s
|
||||
),
|
||||
None => {
|
||||
"SELECT table_name, table_type, table_schema FROM information_schema.tables \
|
||||
WHERE table_schema NOT IN ('pg_catalog', 'information_schema') \
|
||||
UNION ALL \
|
||||
SELECT matviewname AS table_name, 'MATERIALIZED VIEW' AS table_type, schemaname AS table_schema \
|
||||
FROM pg_matviews WHERE schemaname NOT IN ('pg_catalog', 'information_schema') \
|
||||
ORDER BY table_schema, table_name"
|
||||
.to_string()
|
||||
}
|
||||
@@ -285,6 +292,61 @@ pub fn pg_extensions_query() -> String {
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// Query indexes in a schema.
|
||||
///
|
||||
/// Returns index name, schema, table, definition (`pg_get_indexdef`),
|
||||
/// uniqueness, access method, columns CSV, size in bytes, and tablespace.
|
||||
pub fn pg_indexes_query(_schema: &str) -> String {
|
||||
format!(
|
||||
"SELECT \
|
||||
i.relname AS index_name, \
|
||||
ns.nspname AS schema, \
|
||||
t.relname AS table_name, \
|
||||
pg_get_indexdef(ix.indexrelid) AS definition, \
|
||||
ix.indisunique AS is_unique, \
|
||||
am.amname AS method, \
|
||||
(SELECT string_agg(a.attname, ', ' ORDER BY ord.ord) \
|
||||
FROM unnest(ix.indkey) WITH ORDINALITY AS ord(attnum, ord) \
|
||||
JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ord.attnum) AS columns, \
|
||||
pg_relation_size(i.oid) AS size_bytes, \
|
||||
ts.spcname AS tablespace \
|
||||
FROM pg_index ix \
|
||||
JOIN pg_class i ON i.oid = ix.indexrelid \
|
||||
JOIN pg_class t ON t.oid = ix.indrelid \
|
||||
JOIN pg_namespace ns ON t.relnamespace = ns.oid \
|
||||
JOIN pg_am am ON i.relam = am.oid \
|
||||
LEFT JOIN pg_tablespace ts ON i.reltablespace = ts.oid \
|
||||
WHERE ns.nspname = $1 \
|
||||
ORDER BY i.relname"
|
||||
)
|
||||
}
|
||||
|
||||
/// Query CHECK / UNIQUE / EXCLUSION constraints in a schema.
|
||||
///
|
||||
/// Primary and foreign keys are intentionally excluded — they surface in the
|
||||
/// table grid. Returns name, schema, table, contype, definition
|
||||
/// (`pg_get_constraintdef`), deferrability, validation, and columns CSV.
|
||||
pub fn pg_constraints_query(_schema: &str) -> String {
|
||||
format!(
|
||||
"SELECT \
|
||||
c.conname AS name, \
|
||||
ns.nspname AS schema, \
|
||||
cl.relname AS table_name, \
|
||||
c.contype::text, \
|
||||
pg_get_constraintdef(c.oid) AS definition, \
|
||||
c.condeferrable, \
|
||||
c.convalidated, \
|
||||
(SELECT string_agg(a.attname, ', ' ORDER BY ord.ord) \
|
||||
FROM unnest(c.conkey) WITH ORDINALITY AS ord(attnum, ord) \
|
||||
JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = ord.attnum) AS columns \
|
||||
FROM pg_constraint c \
|
||||
JOIN pg_class cl ON c.conrelid = cl.oid \
|
||||
JOIN pg_namespace ns ON cl.relnamespace = ns.oid \
|
||||
WHERE ns.nspname = $1 AND c.contype IN ('c', 'u', 'x') \
|
||||
ORDER BY c.conname"
|
||||
)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tests
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -365,6 +427,49 @@ mod tests {
|
||||
assert!(sql.contains("datistemplate"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pg_indexes_query_is_parameterized_and_joins() {
|
||||
let sql = pg_indexes_query("public");
|
||||
assert!(sql.contains("$1"), "schema must be parameterized; got: {}", sql);
|
||||
assert!(
|
||||
sql.contains("pg_indexes") || sql.contains("pg_index"),
|
||||
"should query pg_index; got: {}",
|
||||
sql
|
||||
);
|
||||
assert!(sql.contains("pg_get_indexdef"), "should include index definition");
|
||||
assert!(sql.contains("indisunique"), "should include uniqueness");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pg_constraints_query_filters_check_unique_exclusion() {
|
||||
let sql = pg_constraints_query("public");
|
||||
assert!(sql.contains("$1"), "schema must be parameterized; got: {}", sql);
|
||||
assert!(
|
||||
sql.contains("pg_constraint"),
|
||||
"should query pg_constraint; got: {}",
|
||||
sql
|
||||
);
|
||||
assert!(sql.contains("contype"), "should select contype");
|
||||
assert!(sql.contains("'c'"), "should filter CHECK ('c')");
|
||||
assert!(sql.contains("'u'"), "should filter UNIQUE ('u')");
|
||||
assert!(sql.contains("'x'"), "should filter EXCLUSION ('x')");
|
||||
assert!(sql.contains("pg_get_constraintdef"), "should include definition");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pg_tables_query_includes_materialized_views() {
|
||||
let sql = pg_tables_query(Some("public"));
|
||||
assert!(
|
||||
sql.contains("pg_matviews"),
|
||||
"matview UNION must source pg_matviews; got: {}",
|
||||
sql,
|
||||
);
|
||||
assert!(
|
||||
sql.contains("MATERIALIZED VIEW"),
|
||||
"should label materialized views"
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// MySQL
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user