fix(greenplum): drop ctid from table-data SELECT and friendly-fail PK-less edits

Browsing any GP table threw "column ctid does not exist" because the
table-viewer's pagination query unconditionally appended `ctid::text`
to the SELECT. AO and AOCO tables (the dominant storage in GP)
genuinely have no ctid, and even on heap tables the value isn't
reliable across segments — so editing by ctid would be wrong even when
the column exists.

- get_table_data: skip the ctid column entirely on Greenplum (`SELECT *`
  instead of `SELECT *, ctid::text`); ctids field stays as an empty Vec.
- update_row / delete_rows: when the table has no PK on a GP connection,
  return an actionable error instead of falling through to the ctid
  path. Tells the user to add a PK or use raw SQL.

Vanilla Postgres behavior is unchanged.
This commit is contained in:
2026-05-07 00:27:24 +03:00
parent 95c9470411
commit a485cf7ee3

View File

@@ -136,9 +136,19 @@ pub async fn get_table_data(
let offset = (page.saturating_sub(1)) * page_size;
// Greenplum AO / AOCO tables don't expose `ctid` and even on heap tables
// it isn't a reliable per-row identifier across segments — so on GP we
// skip the ctid column entirely. Edits without a PK will be rejected by
// update_row/delete_rows below with a friendly error.
let include_ctid = !matches!(flavor, DbFlavor::Greenplum);
let select_clause = if include_ctid {
"*, ctid::text"
} else {
"*"
};
let data_sql = format!(
"SELECT *, ctid::text FROM {}{}{} LIMIT {} OFFSET {}",
qualified, where_clause, order_clause, page_size, offset
"SELECT {} FROM {}{}{} LIMIT {} OFFSET {}",
select_clause, qualified, where_clause, order_clause, page_size, offset
);
let count_sql = format!("SELECT COUNT(*) FROM {}{}", qualified, where_clause);
@@ -248,6 +258,13 @@ pub async fn update_row(
let set_clause = format!("{} = $1", escape_ident(&column));
if pk_columns.is_empty() {
if matches!(state.get_flavor(&connection_id).await, DbFlavor::Greenplum) {
return Err(TuskError::Custom(
"Greenplum doesn't expose a stable ctid (AO/AOCO tables have none, \
heap-table ctid isn't unique across segments). Inline edit requires \
a primary key — add one or use a SQL UPDATE.".into(),
));
}
// Fallback: use ctid for row identification
let ctid_val = ctid.ok_or_else(|| {
TuskError::Custom("Cannot update: no primary key and no ctid provided".into())
@@ -354,6 +371,13 @@ pub async fn delete_rows(
let mut tx = pool.begin().await.map_err(TuskError::Database)?;
if pk_columns.is_empty() {
if matches!(state.get_flavor(&connection_id).await, DbFlavor::Greenplum) {
return Err(TuskError::Custom(
"Greenplum doesn't expose a stable ctid (AO/AOCO tables have none, \
heap-table ctid isn't unique across segments). Inline delete requires \
a primary key — add one or use a SQL DELETE.".into(),
));
}
// Fallback: use ctids for row identification
let ctid_list = ctids.ok_or_else(|| {
TuskError::Custom("Cannot delete: no primary key and no ctids provided".into())