aliasing3
This commit is contained in:
2
client
2
client
Submodule client updated: c923d0de22...c15c62e88b
@@ -108,18 +108,17 @@ 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.
|
||||||
//
|
//
|
||||||
// Only ACCOUNTING's columns may be renamed. Renaming a generated column
|
// ACCOUNTING, PHONE and IBAN generated columns may be renamed: their
|
||||||
// requires its relationship to whatever generated it to be recorded, so the
|
// relationships are recorded by physical column, so the rest of the system can
|
||||||
// rest of the system can find it without knowing its name; ACCOUNTING has that
|
// find them without knowing their display names. ACCOUNTING_TRANSFER connectors
|
||||||
// in table_accounting_definitions, and nothing else does. The ACCOUNTING_
|
// are the exception and are refused here exactly as RenameColumnAlias refuses
|
||||||
// TRANSFER connectors and the PHONE and IBAN companions are refused, the
|
// them.
|
||||||
// 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". Must name a
|
// "name", "tax_point_date", "debit", "credit" or "account", or a PHONE or
|
||||||
// column the request really generates -- an alias for anything else is
|
// IBAN companion such as "work_phone_ext". Must name a column the request
|
||||||
// rejected rather than ignored, so a typo cannot pass silently.
|
// really generates -- an alias for anything else is rejected rather than
|
||||||
|
// ignored, 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.
@@ -56,20 +56,19 @@ 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.
|
||||||
///
|
///
|
||||||
/// Only ACCOUNTING's columns may be renamed. Renaming a generated column
|
/// ACCOUNTING, PHONE and IBAN generated columns may be renamed: their
|
||||||
/// requires its relationship to whatever generated it to be recorded, so the
|
/// relationships are recorded by physical column, so the rest of the system can
|
||||||
/// rest of the system can find it without knowing its name; ACCOUNTING has that
|
/// find them without knowing their display names. ACCOUNTING_TRANSFER connectors
|
||||||
/// in table_accounting_definitions, and nothing else does. The ACCOUNTING\_
|
/// are the exception and are refused here exactly as RenameColumnAlias refuses
|
||||||
/// TRANSFER connectors and the PHONE and IBAN companions are refused, the
|
/// them.
|
||||||
/// 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". Must name a
|
/// "name", "tax_point_date", "debit", "credit" or "account", or a PHONE or
|
||||||
/// column the request really generates -- an alias for anything else is
|
/// IBAN companion such as "work_phone_ext". Must name a column the request
|
||||||
/// rejected rather than ignored, so a typo cannot pass silently.
|
/// really generates -- an alias for anything else is rejected rather than
|
||||||
|
/// ignored, 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
2
server
Submodule server updated: 9984e3b272...6e8ac32389
@@ -21,6 +21,9 @@ 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 connectors remain tied to their backend names.
|
||||||
|
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";
|
||||||
@@ -225,18 +228,13 @@ 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's, and only ACCOUNTING's. A generated column can carry a name
|
/// Every generated column whose relationship is recorded independently of
|
||||||
/// of its own once the rest of the system can find it without that name --
|
/// its display name. ACCOUNTING_TRANSFER's connectors are the exception:
|
||||||
/// which for ACCOUNTING is `table_accounting_definitions`, recording its
|
/// the backend still resolves those by their fixed names.
|
||||||
/// 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_FIELD_TYPE {
|
if column.data_type == ACCOUNTING_TRANSFER_FIELD_TYPE {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
names.extend(
|
names.extend(
|
||||||
@@ -245,7 +243,9 @@ impl TableDraft {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|generated| generated.name.clone()),
|
.map(|generated| generated.name.clone()),
|
||||||
);
|
);
|
||||||
names.push(ACCOUNT_API_COLUMN.to_string());
|
if column.data_type == ACCOUNTING_FIELD_TYPE {
|
||||||
|
names.push(ACCOUNT_API_COLUMN.to_string());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
names
|
names
|
||||||
}
|
}
|
||||||
@@ -555,24 +555,34 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Only ACCOUNTING's columns are offered. The transfer connectors are
|
/// ACCOUNTING, PHONE and IBAN companions may be aliased. Transfer
|
||||||
/// resolved by name by the posting engine, and a phone or IBAN companion is
|
/// connectors remain fixed because the posting engine resolves their names.
|
||||||
/// found on write by rebuilding its name from its parent's -- the backend
|
|
||||||
/// refuses both, so neither is offered here.
|
|
||||||
#[test]
|
#[test]
|
||||||
fn only_accounting_columns_are_aliasable() {
|
fn every_recorded_generated_column_is_aliasable() {
|
||||||
for (name, data_type) in [
|
let phone = draft_with_column("work_phone", "phone");
|
||||||
("accounting_transfer", "accounting_transfer"),
|
assert_eq!(
|
||||||
("work_phone", "phone"),
|
phone.aliasable_generated_columns(),
|
||||||
("bank_account", "iban"),
|
[
|
||||||
] {
|
"work_phone_ext",
|
||||||
let draft = draft_with_column(name, data_type);
|
"work_phone_type",
|
||||||
|
"work_phone_country",
|
||||||
|
"work_phone_calling_code",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
assert!(
|
let iban = draft_with_column("bank_account", "iban");
|
||||||
draft.aliasable_generated_columns().is_empty(),
|
assert_eq!(
|
||||||
"`{data_type}` should offer no aliases"
|
iban.aliasable_generated_columns(),
|
||||||
);
|
[
|
||||||
}
|
"bank_account_country",
|
||||||
|
"bank_account_bban",
|
||||||
|
"bank_account_bank_identifier",
|
||||||
|
"bank_account_branch_identifier",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
let transfer = draft_with_column("accounting_transfer", "accounting_transfer");
|
||||||
|
assert!(transfer.aliasable_generated_columns().is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -13,7 +13,10 @@
|
|||||||
|
|
||||||
use crate::schema::{ColumnCatalog, ColumnDraft, columns_from_rows};
|
use crate::schema::{ColumnCatalog, ColumnDraft, columns_from_rows};
|
||||||
|
|
||||||
use super::draft::{ACCOUNT_API_COLUMN, ACCOUNTING_FIELD_TYPE, GeneratedAlias, TableDraft};
|
use super::draft::{
|
||||||
|
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__";
|
||||||
@@ -223,12 +226,10 @@ impl AddTablePageState {
|
|||||||
alias: String::new(),
|
alias: String::new(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// A generated column is named by the backend, but the name is a
|
// Every generated column with persisted provenance may be named by
|
||||||
// display name, and ACCOUNTING's columns can be given one of the
|
// the request. Transfer connectors are still resolved by fixed
|
||||||
// user's own in the request that creates them. Only ACCOUNTING's:
|
// backend names and remain the exception.
|
||||||
// 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)];
|
||||||
|
|||||||
@@ -664,11 +664,22 @@ impl ColumnDraft {
|
|||||||
/// compound one. This is what the column list shows underneath it, so the
|
/// compound one. This is what the column list shows underneath it, so the
|
||||||
/// columns a definition row brings are visible while the table is still
|
/// columns a definition row brings are visible while the table is still
|
||||||
/// being described.
|
/// being described.
|
||||||
pub(crate) fn generated_columns_of(&self, index: usize) -> &[GeneratedColumn] {
|
pub(crate) fn generated_columns_of(&self, index: usize) -> Vec<GeneratedColumn> {
|
||||||
self.added
|
let Some(column) = self.added.get(index) else {
|
||||||
.get(index)
|
return Vec::new();
|
||||||
.map(|column| self.catalog.generated_columns(&column.data_type))
|
};
|
||||||
.unwrap_or_default()
|
let generated = self.catalog.generated_columns(&column.data_type);
|
||||||
|
let default_prefix = format!("{}_", column.data_type);
|
||||||
|
generated
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.map(|mut companion| {
|
||||||
|
if let Some(suffix) = companion.name.strip_prefix(&default_prefix) {
|
||||||
|
companion.name = format!("{}_{}", column.name, suffix);
|
||||||
|
}
|
||||||
|
companion
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether a column can be indexed or identify a row. A compound column
|
/// Whether a column can be indexed or identify a row. A compound column
|
||||||
@@ -1115,7 +1126,23 @@ pub(crate) mod tests {
|
|||||||
grouped("gtin_12", "gtin"),
|
grouped("gtin_12", "gtin"),
|
||||||
grouped("gtin_13", "gtin"),
|
grouped("gtin_13", "gtin"),
|
||||||
grouped("gtin_14", "gtin"),
|
grouped("gtin_14", "gtin"),
|
||||||
declarable("iban"),
|
ColumnType {
|
||||||
|
generated_columns: vec![
|
||||||
|
generated_column("iban_country", "iban_country", false),
|
||||||
|
generated_column("iban_bban", "iban_bban", false),
|
||||||
|
generated_column(
|
||||||
|
"iban_bank_identifier",
|
||||||
|
"iban_bank_identifier",
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
generated_column(
|
||||||
|
"iban_branch_identifier",
|
||||||
|
"iban_branch_identifier",
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
..declarable("iban")
|
||||||
|
},
|
||||||
ColumnType {
|
ColumnType {
|
||||||
declarable: false,
|
declarable: false,
|
||||||
..declarable("iban_bban")
|
..declarable("iban_bban")
|
||||||
@@ -1135,7 +1162,19 @@ pub(crate) mod tests {
|
|||||||
..declarable("numeric")
|
..declarable("numeric")
|
||||||
},
|
},
|
||||||
declarable("period"),
|
declarable("period"),
|
||||||
declarable("phone"),
|
ColumnType {
|
||||||
|
generated_columns: vec![
|
||||||
|
generated_column("phone_ext", "phone_extension", false),
|
||||||
|
generated_column("phone_type", "phone_type", false),
|
||||||
|
generated_column("phone_country", "phone_country", false),
|
||||||
|
generated_column(
|
||||||
|
"phone_calling_code",
|
||||||
|
"phone_calling_code",
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
..declarable("phone")
|
||||||
|
},
|
||||||
ColumnType {
|
ColumnType {
|
||||||
declarable: false,
|
declarable: false,
|
||||||
sql_type: "INTEGER".to_string(),
|
sql_type: "INTEGER".to_string(),
|
||||||
|
|||||||
Reference in New Issue
Block a user