centralized system columns - BUG NEEDS FIX FOR SPECIFIC SYSTEM COLUMNS

This commit is contained in:
Priec
2026-08-08 18:58:24 +02:00
parent 8881041740
commit 7db328ca2b
4 changed files with 109 additions and 10 deletions

View File

@@ -4,6 +4,7 @@ pub mod search;
pub mod decimal;
pub mod grpc_error;
pub mod relationship;
pub mod system_column;
pub mod proto {
pub mod komp_ac {

104
common/src/system_column.rs Normal file
View File

@@ -0,0 +1,104 @@
// common/src/system_column.rs
//! The columns the server puts on every managed table.
//!
//! They are the one exception to the rule that a physical column name never
//! leaves the server. A user column is stored under its ordinal and shown only
//! under its alias, so an unmapped name at the public boundary is a leak; these
//! have no alias to hide behind, because the server -- not the user -- chose
//! their names. Anything asking "is this name public?" or "may a user claim
//! this name?" is asking about this list, so the list lives here once and the
//! `CREATE TABLE` fragments that make it true live next to it.
/// A column every managed table carries, named the same in Postgres and in the
/// public API.
pub struct SystemColumn {
pub name: &'static str,
/// The `CREATE TABLE` fragment that declares it.
pub definition: &'static str,
}
/// Declared ahead of the user columns.
pub const LEADING_SYSTEM_COLUMNS: [SystemColumn; 3] = [
SystemColumn {
name: "id",
definition: "id BIGSERIAL PRIMARY KEY",
},
SystemColumn {
name: "deleted",
definition: "deleted BOOLEAN NOT NULL DEFAULT FALSE",
},
SystemColumn {
name: "row_revision",
definition: "row_revision BIGINT NOT NULL DEFAULT 1",
},
];
/// Declared after the user columns.
pub const TRAILING_SYSTEM_COLUMNS: [SystemColumn; 1] = [SystemColumn {
name: "created_at",
definition: "created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP",
}];
/// Present only on a table that references its profile's chart of accounts.
/// Its declaration names that profile's schema, so it is built where the
/// profile is known rather than spelled out here.
pub const ACCOUNT_REFERENCE_COLUMN: &str = "account_id";
/// Every system column name, whether or not the column is on a given table.
///
/// A name is reserved for all tables even when only some tables carry the
/// column, so an alias means the same thing everywhere.
pub fn system_column_names() -> impl Iterator<Item = &'static str> {
LEADING_SYSTEM_COLUMNS
.iter()
.chain(TRAILING_SYSTEM_COLUMNS.iter())
.map(|column| column.name)
.chain(std::iter::once(ACCOUNT_REFERENCE_COLUMN))
}
/// Whether `name` is a system column: a name that is safe to show a client
/// as-is, and that a user may not claim for a column alias or a table.
pub fn is_system_column(name: &str) -> bool {
system_column_names().any(|system_name| system_name == name)
}
/// The system column names in a message, as `'id', 'deleted', ...`.
pub fn system_column_name_list() -> String {
system_column_names()
.map(|name| format!("'{name}'"))
.collect::<Vec<_>>()
.join(", ")
}
#[cfg(test)]
mod tests {
use super::{
ACCOUNT_REFERENCE_COLUMN, is_system_column, system_column_name_list, system_column_names,
};
#[test]
fn a_declared_column_is_a_system_column() {
for name in system_column_names() {
assert!(is_system_column(name), "{name} is declared but not public");
}
}
#[test]
fn a_conditional_column_is_reserved_on_every_table() {
assert!(is_system_column(ACCOUNT_REFERENCE_COLUMN));
}
#[test]
fn an_ordinal_is_not_a_system_column() {
assert!(!is_system_column("1"));
assert!(!is_system_column("column_1"));
}
#[test]
fn the_name_list_reads_as_a_sentence_fragment() {
assert_eq!(
system_column_name_list(),
"'id', 'deleted', 'row_revision', 'created_at', 'account_id'"
);
}
}