aliasing2

This commit is contained in:
Priec
2026-08-13 14:24:34 +02:00
parent 27d3e9af34
commit 5fe630fff7
6 changed files with 53 additions and 37 deletions

View File

@@ -108,14 +108,18 @@ message PostTableDefinitionRequest {
// column, though, so it is free to be anything: this is where that choice is // column, though, so it is free to be anything: this is where that choice is
// made, instead of a RenameColumnAlias call afterwards. // made, instead of a RenameColumnAlias call afterwards.
// //
// The ACCOUNTING_TRANSFER connectors are the exception. They are refused here // Only ACCOUNTING's columns may be renamed. Renaming a generated column
// exactly as RenameColumnAlias refuses them. // requires its relationship to whatever generated it to be recorded, so the
// rest of the system can find it without knowing its name; ACCOUNTING has that
// in table_accounting_definitions, and nothing else does. The ACCOUNTING_
// TRANSFER connectors and the PHONE and IBAN companions are refused, the
// latter because a row write finds them by rebuilding their names from their
// parent column's name.
message GeneratedColumnAlias { message GeneratedColumnAlias {
// The name the backend would otherwise give the column: one of ACCOUNTING's // The name the backend would otherwise give the column: one of ACCOUNTING's
// "name", "tax_point_date", "debit", "credit" or "account", or a companion // "name", "tax_point_date", "debit", "credit" or "account". Must name a
// such as "work_phone_extension". Must name a column the request really // column the request really generates -- an alias for anything else is
// generates -- an alias for anything else is rejected rather than ignored, // rejected rather than ignored, so a typo cannot pass silently.
// so a typo cannot pass silently.
string generated_name = 1; string generated_name = 1;
// What the column should be called instead. Same rules as any column name. // What the column should be called instead. Same rules as any column name.

Binary file not shown.

View File

@@ -56,16 +56,20 @@ pub struct PostTableDefinitionRequest {
/// column, though, so it is free to be anything: this is where that choice is /// column, though, so it is free to be anything: this is where that choice is
/// made, instead of a RenameColumnAlias call afterwards. /// made, instead of a RenameColumnAlias call afterwards.
/// ///
/// The ACCOUNTING_TRANSFER connectors are the exception. They are refused here /// Only ACCOUNTING's columns may be renamed. Renaming a generated column
/// exactly as RenameColumnAlias refuses them. /// requires its relationship to whatever generated it to be recorded, so the
/// rest of the system can find it without knowing its name; ACCOUNTING has that
/// in table_accounting_definitions, and nothing else does. The ACCOUNTING\_
/// TRANSFER connectors and the PHONE and IBAN companions are refused, the
/// latter because a row write finds them by rebuilding their names from their
/// parent column's name.
#[derive(serde::Serialize, serde::Deserialize)] #[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct GeneratedColumnAlias { pub struct GeneratedColumnAlias {
/// The name the backend would otherwise give the column: one of ACCOUNTING's /// The name the backend would otherwise give the column: one of ACCOUNTING's
/// "name", "tax_point_date", "debit", "credit" or "account", or a companion /// "name", "tax_point_date", "debit", "credit" or "account". Must name a
/// such as "work_phone_extension". Must name a column the request really /// column the request really generates -- an alias for anything else is
/// generates -- an alias for anything else is rejected rather than ignored, /// rejected rather than ignored, so a typo cannot pass silently.
/// so a typo cannot pass silently.
#[prost(string, tag = "1")] #[prost(string, tag = "1")]
pub generated_name: ::prost::alloc::string::String, pub generated_name: ::prost::alloc::string::String,
/// What the column should be called instead. Same rules as any column name. /// What the column should be called instead. Same rules as any column name.

2
server

Submodule server updated: de424f245d...9984e3b272

View File

@@ -21,10 +21,6 @@ use crate::{
/// them, and [`TableDraft::preview_rows`] is where that is explained. /// them, and [`TableDraft::preview_rows`] is where that is explained.
pub(crate) const ACCOUNTING_FIELD_TYPE: &str = "accounting"; pub(crate) const ACCOUNTING_FIELD_TYPE: &str = "accounting";
/// The compound type whose companions the backend refuses to rename: they are
/// the connectors a transfer is posted through, and are looked up by name.
pub(crate) const ACCOUNTING_TRANSFER_FIELD_TYPE: &str = "accounting_transfer";
/// The virtual field the ACCOUNTING foreign key is written as. It is a column /// The virtual field the ACCOUNTING foreign key is written as. It is a column
/// like any other once created, so it can be aliased too. /// like any other once created, so it can be aliased too.
pub(crate) const ACCOUNT_API_COLUMN: &str = "account"; pub(crate) const ACCOUNT_API_COLUMN: &str = "account";
@@ -229,13 +225,18 @@ impl TableDraft {
/// The generated columns this draft would let the user rename, in the order /// The generated columns this draft would let the user rename, in the order
/// they appear in the column list. /// they appear in the column list.
/// ///
/// ACCOUNTING_TRANSFER's companions are left out: the backend refuses to /// ACCOUNTING's, and only ACCOUNTING's. A generated column can carry a name
/// name them anything else. Everything else a definition row or a companion /// of its own once the rest of the system can find it without that name --
/// type generates is a display name over a physical column, and is free. /// which for ACCOUNTING is `table_accounting_definitions`, recording its
/// columns by physical name. The ACCOUNTING_TRANSFER connectors and the
/// PHONE and IBAN companions have no such record: a companion is found on
/// write by rebuilding its name from its parent's, so renaming one would
/// hide it from the write path. The backend refuses those, and this does
/// not offer what the backend refuses.
pub(crate) fn aliasable_generated_columns(&self) -> Vec<String> { pub(crate) fn aliasable_generated_columns(&self) -> Vec<String> {
let mut names = Vec::new(); let mut names = Vec::new();
for (index, column) in self.columns.added.iter().enumerate() { for (index, column) in self.columns.added.iter().enumerate() {
if column.data_type == ACCOUNTING_TRANSFER_FIELD_TYPE { if column.data_type != ACCOUNTING_FIELD_TYPE {
continue; continue;
} }
names.extend( names.extend(
@@ -244,10 +245,8 @@ impl TableDraft {
.iter() .iter()
.map(|generated| generated.name.clone()), .map(|generated| generated.name.clone()),
); );
if column.data_type == ACCOUNTING_FIELD_TYPE {
names.push(ACCOUNT_API_COLUMN.to_string()); names.push(ACCOUNT_API_COLUMN.to_string());
} }
}
names names
} }
@@ -556,13 +555,24 @@ mod tests {
); );
} }
/// The transfer connectors are resolved by name by the posting engine, and /// Only ACCOUNTING's columns are offered. The transfer connectors are
/// the backend refuses to rename them, so they are never offered. /// resolved by name by the posting engine, and a phone or IBAN companion is
/// found on write by rebuilding its name from its parent's -- the backend
/// refuses both, so neither is offered here.
#[test] #[test]
fn accounting_transfer_connectors_are_not_aliasable() { fn only_accounting_columns_are_aliasable() {
let draft = draft_with_column("accounting_transfer", "accounting_transfer"); for (name, data_type) in [
("accounting_transfer", "accounting_transfer"),
("work_phone", "phone"),
("bank_account", "iban"),
] {
let draft = draft_with_column(name, data_type);
assert!(draft.aliasable_generated_columns().is_empty()); assert!(
draft.aliasable_generated_columns().is_empty(),
"`{data_type}` should offer no aliases"
);
}
} }
#[test] #[test]

View File

@@ -13,10 +13,7 @@
use crate::schema::{ColumnCatalog, ColumnDraft, columns_from_rows}; use crate::schema::{ColumnCatalog, ColumnDraft, columns_from_rows};
use super::draft::{ use super::draft::{ACCOUNT_API_COLUMN, ACCOUNTING_FIELD_TYPE, GeneratedAlias, TableDraft};
ACCOUNT_API_COLUMN, ACCOUNTING_FIELD_TYPE, ACCOUNTING_TRANSFER_FIELD_TYPE, GeneratedAlias,
TableDraft,
};
/// The `profile_name` option meaning "create a new profile too". /// The `profile_name` option meaning "create a new profile too".
pub(crate) const NEW_PROFILE: &str = "__new__"; pub(crate) const NEW_PROFILE: &str = "__new__";
@@ -227,10 +224,11 @@ impl AddTablePageState {
}); });
// A generated column is named by the backend, but the name is a // A generated column is named by the backend, but the name is a
// display name: it can be aliased, and the alias is applied as a // display name, and ACCOUNTING's columns can be given one of the
// rename the moment the table exists. The connectors of an // user's own in the request that creates them. Only ACCOUNTING's:
// ACCOUNTING_TRANSFER are the exception the backend protects. // see `TableDraft::aliasable_generated_columns` for why the other
let aliasable = column.data_type != ACCOUNTING_TRANSFER_FIELD_TYPE; // generated columns cannot be renamed at all.
let aliasable = column.data_type == ACCOUNTING_FIELD_TYPE;
for generated in columns.generated_columns_of(index) { for generated in columns.generated_columns_of(index) {
let mut tags = vec![format!("generated by {}", column.data_type)]; let mut tags = vec![format!("generated by {}", column.data_type)];