multirow reference in the client

This commit is contained in:
Priec
2026-07-29 11:34:08 +02:00
parent 33a5f47b42
commit 76a5f76194
17 changed files with 105 additions and 90 deletions

View File

@@ -504,14 +504,14 @@ async fn table_physical_to_display_map(
Ok(mapping)
}
async fn table_row_display_column(
async fn table_row_display_columns(
pool: &PgPool,
profile_name: &str,
table_name: &str,
) -> Result<String, Status> {
) -> Result<Vec<String>, Status> {
sqlx::query_scalar(
r#"
SELECT td.row_display_column
SELECT td.row_display_columns
FROM schemas s
JOIN table_definitions td ON td.schema_id = s.id
WHERE s.name = $1 AND td.table_name = $2
@@ -521,7 +521,7 @@ async fn table_row_display_column(
.bind(table_name)
.fetch_one(pool)
.await
.map_err(|e| Status::internal(format!("Row display column lookup failed: {}", e)))
.map_err(|e| Status::internal(format!("Row display columns lookup failed: {}", e)))
}
fn remap_json_to_display_names(
@@ -541,13 +541,18 @@ fn remap_json_to_display_names(
}
}
fn row_display_value(value: &serde_json::Value, column: &str) -> String {
match value.get(column) {
Some(serde_json::Value::String(value)) => value.clone(),
Some(serde_json::Value::Number(value)) => value.to_string(),
Some(serde_json::Value::Bool(value)) => value.to_string(),
_ => String::new(),
}
/// One value per display column, positionally aligned with them, so a column
/// that is NULL for this row stays visible as an empty slot.
fn row_display_values(value: &serde_json::Value, columns: &[String]) -> Vec<String> {
columns
.iter()
.map(|column| match value.get(column) {
Some(serde_json::Value::String(value)) => value.clone(),
Some(serde_json::Value::Number(value)) => value.to_string(),
Some(serde_json::Value::Bool(value)) => value.to_string(),
_ => String::new(),
})
.collect()
}
enum ResolvedOrderColumn {
@@ -573,8 +578,14 @@ async fn resolve_order_column(
return Ok(ResolvedOrderColumn::Position);
}
let requested_column = if requested_column.eq_ignore_ascii_case("row_display_column") {
table_row_display_column(pool, profile_name, table_name).await?
// Sorting by "the display column" means the first one: it is the part
// callers read left to right. A table with none sorts by id instead.
let requested_column = if requested_column.eq_ignore_ascii_case("row_display_columns") {
table_row_display_columns(pool, profile_name, table_name)
.await?
.into_iter()
.next()
.unwrap_or_else(|| "id".to_string())
} else {
requested_column.to_string()
};
@@ -661,7 +672,7 @@ async fn fetch_ordered_rows(
offset: usize,
) -> Result<Vec<Hit>, Status> {
let physical_to_display = table_physical_to_display_map(pool, profile_name, table_name).await?;
let display_column = table_row_display_column(pool, profile_name, table_name).await?;
let display_columns = table_row_display_columns(pool, profile_name, table_name).await?;
let (resolved_order, direction) = match order {
Some(order) => (
resolve_order_column(pool, profile_name, table_name, &order.column).await?,
@@ -694,14 +705,14 @@ async fn fetch_ordered_rows(
let json_data: serde_json::Value = row.try_get("data").unwrap_or_default();
let position: i64 = row.try_get("picker_position").unwrap_or_default();
let json_data = remap_json_to_display_names(json_data, &physical_to_display);
let row_display_value = row_display_value(&json_data, &display_column);
let row_display_values = row_display_values(&json_data, &display_columns);
Hit {
id,
score: 0.0,
content_json: json_data.to_string(),
table_name: table_name.to_string(),
row_display_value,
row_display_column: display_column.clone(),
row_display_values,
row_display_columns: display_columns.clone(),
position: u64::try_from(position).ok(),
}
})
@@ -810,12 +821,13 @@ async fn run_search(
.push(*pg_id);
}
let mut content_map: HashMap<(String, i64), (String, String, String)> = HashMap::new();
let mut content_map: HashMap<(String, i64), (String, Vec<String>, Vec<String>)> =
HashMap::new();
for (table_name, pg_ids) in ids_by_table {
validate_identifier(&table_name, "table_name")?;
let physical_to_display =
table_physical_to_display_map(pool, profile_name, &table_name).await?;
let display_column = table_row_display_column(pool, profile_name, &table_name).await?;
let display_columns = table_row_display_columns(pool, profile_name, &table_name).await?;
let sql = format!(
"SELECT id, to_jsonb(t) AS data FROM {} t WHERE deleted = FALSE AND id = ANY($1)",
qualify_profile_table(profile_name, &table_name)
@@ -830,10 +842,10 @@ async fn run_search(
let id: i64 = row.try_get("id").unwrap_or_default();
let json_data: serde_json::Value = row.try_get("data").unwrap_or_default();
let json_data = remap_json_to_display_names(json_data, &physical_to_display);
let display_value = row_display_value(&json_data, &display_column);
let display_values = row_display_values(&json_data, &display_columns);
content_map.insert(
(table_name.clone(), id),
(json_data.to_string(), display_value, display_column.clone()),
(json_data.to_string(), display_values, display_columns.clone()),
);
}
}
@@ -843,13 +855,13 @@ async fn run_search(
.filter_map(|(score, pg_id, table_name)| {
content_map
.get(&(table_name.clone(), pg_id))
.map(|(content_json, row_display_value, row_display_column)| Hit {
.map(|(content_json, row_display_values, row_display_columns)| Hit {
id: pg_id,
score,
content_json: content_json.clone(),
table_name,
row_display_value: row_display_value.clone(),
row_display_column: row_display_column.clone(),
row_display_values: row_display_values.clone(),
row_display_columns: row_display_columns.clone(),
position: None,
})
})
@@ -866,7 +878,7 @@ async fn fetch_ordered_candidate_rows(
offset: usize,
) -> Result<Vec<Hit>, Status> {
let physical_to_display = table_physical_to_display_map(pool, profile_name, table_name).await?;
let display_column = table_row_display_column(pool, profile_name, table_name).await?;
let display_columns = table_row_display_columns(pool, profile_name, table_name).await?;
let resolved_order =
resolve_order_column(pool, profile_name, table_name, &order.column).await?;
let sql = format!(
@@ -919,14 +931,14 @@ async fn fetch_ordered_candidate_rows(
let position: i64 = row.try_get("picker_position").unwrap_or_default();
let score: f32 = row.try_get("candidate_score").unwrap_or_default();
let json_data = remap_json_to_display_names(json_data, &physical_to_display);
let display_value = row_display_value(&json_data, &display_column);
let display_values = row_display_values(&json_data, &display_columns);
Hit {
id,
score,
content_json: json_data.to_string(),
table_name: table_name.to_string(),
row_display_value: display_value,
row_display_column: display_column.clone(),
row_display_values: display_values,
row_display_columns: display_columns.clone(),
position: u64::try_from(position).ok(),
}
})