444 lines
16 KiB
Protocol Buffer
444 lines
16 KiB
Protocol Buffer
// common/proto/table_definition.proto
|
|
syntax = "proto3";
|
|
package komp_ac.table_definition;
|
|
|
|
import "common.proto";
|
|
|
|
// The TableDefinition service manages the entire lifecycle of user-defined
|
|
// tables (stored as both metadata and physical PostgreSQL tables) inside
|
|
// logical "profiles" (schemas). Each table has stored structure, links, and
|
|
// validation rules.
|
|
service TableDefinition {
|
|
// Creates a new table (and schema if missing) with system columns,
|
|
// linked-table foreign keys, user-defined columns, and optional indexes.
|
|
// Also inserts metadata and default validation rules. Entirely transactional.
|
|
rpc PostTableDefinition(PostTableDefinitionRequest) returns (TableDefinitionResponse);
|
|
|
|
// Creates a regular user-table bundle from the restricted komp_ac_fields
|
|
// contract embedded in a Typst invoice template. This is invoked explicitly
|
|
// by the user; templates do not provision tables merely by existing.
|
|
rpc CreateInvoiceTemplateTable(CreateInvoiceTemplateTableRequest) returns (CreateInvoiceTemplateTableResponse);
|
|
|
|
// Appends new user-defined columns to an existing table.
|
|
// Existing columns, links, and table logic are never changed by this call.
|
|
rpc AddTableColumns(AddTableColumnsRequest) returns (TableDefinitionResponse);
|
|
|
|
// Lists all profiles (schemas) and their tables with declared dependencies.
|
|
// This provides a tree-like overview of table relationships.
|
|
rpc GetProfileTree(komp_ac.common.Empty) returns (ProfileTreeResponse);
|
|
|
|
// Lists every column type a table may declare, along with the SQL type it
|
|
// maps to. Pure data retrieval - no business logic.
|
|
rpc ListColumnTypes(komp_ac.common.Empty) returns (ListColumnTypesResponse);
|
|
|
|
// Fetches all tables with their columns and scripts for a specific profile.
|
|
// Pure data retrieval - no business logic.
|
|
rpc GetProfileDetails(GetProfileDetailsRequest) returns (GetProfileDetailsResponse);
|
|
|
|
// Copies one complete profile into a new profile without copying table data.
|
|
rpc CopyProfile(CopyProfileRequest) returns (CopyProfileResponse);
|
|
|
|
// Returns the stored rename history for column aliases in one profile.
|
|
rpc GetColumnAliasRenameHistory(GetColumnAliasRenameHistoryRequest) returns (GetColumnAliasRenameHistoryResponse);
|
|
|
|
// Renames a user-visible column alias while keeping the physical column unchanged.
|
|
rpc RenameColumnAlias(RenameColumnAliasRequest) returns (RenameColumnAliasResponse);
|
|
|
|
// Drops a table and its metadata, then deletes the profile if it becomes empty.
|
|
rpc DeleteTable(DeleteTableRequest) returns (DeleteTableResponse);
|
|
}
|
|
|
|
// Defines the input for creating a new table definition.
|
|
message PostTableDefinitionRequest {
|
|
// Table name to create inside the target profile.
|
|
// Must be lowercase, alphanumeric with underscores,
|
|
// start with a letter, and be <= 63 chars.
|
|
// Forbidden names: "id", "deleted", "created_at", "row_revision" -- the
|
|
// system columns, which share one namespace with table names wherever the
|
|
// two are named side by side. The "_id" suffix is allowed.
|
|
string table_name = 1;
|
|
|
|
// The table's columns, including its links. System columns are added on top.
|
|
repeated ColumnDefinition columns = 3;
|
|
|
|
// Column names to index, matching names declared above. System columns
|
|
// ("id", "deleted", "created_at", "row_revision") already have indexes, and
|
|
// a LINK column is indexed when it is created. Requests naming either are
|
|
// rejected.
|
|
repeated string indexes = 4;
|
|
|
|
// Name of profile (Postgres schema) where the table will be created.
|
|
// Same naming rules as table_name; cannot collide with reserved schemas
|
|
// like "public", "information_schema", or ones starting with "pg_".
|
|
string profile_name = 5;
|
|
|
|
// Columns whose values identify a row to users in pickers, in the order
|
|
// they are shown. Each must name one of the user-defined columns above.
|
|
// Empty means the row is identified by its id alone.
|
|
repeated string row_display_columns = 7;
|
|
|
|
// ISO-4217 currency the profile keeps its accounting in. A profile is one
|
|
// accounting entity and keeps one set of books, so this is required only when
|
|
// the request creates the profile, and is ignored afterwards. It is unrelated
|
|
// to individual MONEY-column currencies: tables may hold money in any currency, and amounts convert
|
|
// to this one when they reach the ledger.
|
|
string accounting_currency = 8;
|
|
}
|
|
|
|
// Defines the input for explicitly creating tables backed by one invoice
|
|
// template. typst_source must contain exactly one field declaration:
|
|
//
|
|
// #let komp_ac_fields = (
|
|
// "sidlo.nazov",
|
|
// "people[].name",
|
|
// "people[].requested_quantity",
|
|
// )
|
|
//
|
|
// A path matching an existing profile table and column becomes a reference to
|
|
// that table. Otherwise its final segment becomes a local TEXT column for an
|
|
// administrator to refine later. Every [] creates a child table with a required
|
|
// FK to its immediate generated parent.
|
|
message CreateInvoiceTemplateTableRequest {
|
|
string profile_name = 1;
|
|
string table_name = 2;
|
|
string typst_source = 3;
|
|
repeated string row_display_columns = 5;
|
|
}
|
|
|
|
// One physical dynamic table created for an invoice template scope. The root
|
|
// has an empty collection_path and parent_table_name. Each [] scope names its
|
|
// collection and the generated table that owns those repeated rows.
|
|
message GeneratedInvoiceTemplateTable {
|
|
string table_name = 1;
|
|
string collection_path = 2;
|
|
string parent_table_name = 3;
|
|
string sql = 4;
|
|
}
|
|
|
|
// Reports the complete table bundle created from one invoice template.
|
|
message CreateInvoiceTemplateTableResponse {
|
|
bool success = 1;
|
|
repeated GeneratedInvoiceTemplateTable tables = 2;
|
|
}
|
|
|
|
// Defines append-only column additions for an existing table.
|
|
message AddTableColumnsRequest {
|
|
// Existing profile/schema name.
|
|
string profile_name = 1;
|
|
|
|
// Existing table name in the profile.
|
|
string table_name = 2;
|
|
|
|
// New user-defined columns only. Existing columns cannot be changed here.
|
|
repeated ColumnDefinition columns = 3;
|
|
|
|
// Optional indexes for the new columns only.
|
|
repeated string indexes = 4;
|
|
|
|
}
|
|
|
|
enum MoneyRounding {
|
|
MONEY_ROUNDING_NONE = 0;
|
|
MONEY_ROUNDING_HALF_UP = 1;
|
|
}
|
|
|
|
// Describes one user-defined column for a table.
|
|
message ColumnDefinition {
|
|
// Must be lowercase, start with a letter, and use only lowercase letters,
|
|
// digits and underscores.
|
|
//
|
|
// Cannot be "id", "deleted", "created_at" or "row_revision": those are
|
|
// system columns, and a data request names system and user columns in one
|
|
// namespace. Any other name is free, including one ending in "_id" -- no
|
|
// column name is derived from a table name, so nothing collides.
|
|
string name = 1;
|
|
|
|
// Logical column type. Supported values (case-insensitive):
|
|
// TEXT
|
|
// BOOLEAN
|
|
// INSTANT (local input resolved in the authenticated user's timezone
|
|
// and displayed in the viewer timezone)
|
|
// USER_DATETIME (local input resolved in the user's timezone)
|
|
// RAW_DATETIME (timezone-free civil datetime)
|
|
// PHONE (international or national phone number; generates extension/type/country/calling-code companions)
|
|
// TIME (timezone-free time of day)
|
|
// MONEY (= unconstrained NUMERIC; currency is declared below)
|
|
// ACCOUNTING (creates schema-managed name, account, debit, and credit fields;
|
|
// account always selects a row from the profile's managed accounts table;
|
|
// name is limited to 10 characters and one stored row contributes
|
|
// one line to the profile journal)
|
|
// ACCOUNTING_TRANSFER (creates automatic source-period/account/book/
|
|
// denomination fields and writable target equivalents; one
|
|
// table may contain only one accounting transfer definition)
|
|
// INT
|
|
// BIGINT
|
|
// DATE
|
|
// DURATION
|
|
// PERIOD
|
|
// DECIMAL(p,s) → NUMERIC(p,s)
|
|
// LINK(table) → BIGINT referencing that table in the same profile, indexed
|
|
// automatically. A table may hold several links to the same
|
|
// target as long as the columns are named differently.
|
|
// DECIMAL args must be integers (no sign, no dot, no leading zeros);
|
|
// s ≤ p and p ≥ 1.
|
|
string field_type = 2;
|
|
|
|
// MONEY rounding applied before a value is stored.
|
|
MoneyRounding rounding = 3;
|
|
|
|
// When true, this numeric column is server-owned and projected from the
|
|
// profile quantity ledger.
|
|
bool quantity_ledger = 4;
|
|
|
|
// Canonical uppercase ISO-4217 currency code. Required for MONEY and forbidden
|
|
// for every other field type.
|
|
string currency = 5;
|
|
|
|
// When true, the server rejects omitted or explicitly null values.
|
|
bool required = 6;
|
|
}
|
|
|
|
// Response after table creation (success + DDL preview).
|
|
message TableDefinitionResponse {
|
|
// True if all DB changes and metadata inserts succeeded.
|
|
bool success = 1;
|
|
|
|
// The actual SQL executed: CREATE TABLE + CREATE INDEX statements.
|
|
string sql = 2;
|
|
}
|
|
|
|
// Describes the tree of all profiles and their tables.
|
|
message ProfileTreeResponse {
|
|
// One link: the table it points at, and the column carrying it.
|
|
message Dependency {
|
|
// Table being referenced.
|
|
string table_name = 1;
|
|
|
|
// Column holding the reference, named by whoever declared the link. This
|
|
// is what identifies the link, since a table may point at one target
|
|
// several times.
|
|
string column_name = 2;
|
|
}
|
|
|
|
// Table entry in a profile.
|
|
message Table {
|
|
// Internal ID from table_definitions.id (metadata record).
|
|
int64 id = 1;
|
|
|
|
// Table name within the profile (schema).
|
|
string name = 2;
|
|
|
|
// Links this table declares. One entry per link, so a table that names the
|
|
// same target twice appears twice.
|
|
repeated Dependency depends_on = 3;
|
|
|
|
// Columns whose values make up the human-readable row label, in order.
|
|
repeated string row_display_columns = 4;
|
|
|
|
// "dynamic" for user-defined tables, "system" for backend-managed tables.
|
|
string table_kind = 5;
|
|
}
|
|
|
|
// Profile (schema) entry.
|
|
message Profile {
|
|
// Name of the schema/profile (as stored in `schemas.name`).
|
|
string name = 1;
|
|
|
|
// All tables in that schema and their dependencies.
|
|
repeated Table tables = 2;
|
|
}
|
|
|
|
// All profiles in the system.
|
|
repeated Profile profiles = 1;
|
|
}
|
|
|
|
// Request to fetch all tables, columns and scripts for a profile.
|
|
message GetProfileDetailsRequest {
|
|
// Profile (schema) name to fetch details for.
|
|
string profile_name = 1;
|
|
}
|
|
|
|
// Response with all tables, columns and scripts for a profile.
|
|
message GetProfileDetailsResponse {
|
|
string profile_name = 1;
|
|
repeated TableDetail tables = 2;
|
|
}
|
|
|
|
// Request to copy one full profile into a new profile.
|
|
message CopyProfileRequest {
|
|
string source_profile_name = 1;
|
|
string target_profile_name = 2;
|
|
repeated string table_names = 3;
|
|
}
|
|
|
|
// Response after copying a profile.
|
|
message CopyProfileResponse {
|
|
bool success = 1;
|
|
string message = 2;
|
|
int32 tables_copied = 3;
|
|
int32 scripts_copied = 4;
|
|
}
|
|
|
|
// Request to fetch recorded column alias rename history for one profile.
|
|
message GetColumnAliasRenameHistoryRequest {
|
|
string profile_name = 1;
|
|
|
|
// Filter. When omitted, returns all tables in the profile.
|
|
optional int64 table_definition_id = 2;
|
|
}
|
|
|
|
// One recorded column alias rename.
|
|
message ColumnAliasRenameHistoryEntry {
|
|
int64 id = 1;
|
|
string profile_name = 2;
|
|
int64 table_definition_id = 3;
|
|
string table_name = 4;
|
|
string old_column_name = 5;
|
|
string new_column_name = 6;
|
|
string created_at = 7;
|
|
}
|
|
|
|
// Response with stored column alias rename history rows.
|
|
message GetColumnAliasRenameHistoryResponse {
|
|
string profile_name = 1;
|
|
repeated ColumnAliasRenameHistoryEntry entries = 2;
|
|
}
|
|
|
|
// Describes a table with its columns and associated scripts.
|
|
message TableDetail {
|
|
string name = 1;
|
|
int64 id = 2;
|
|
repeated ColumnDefinition columns = 3;
|
|
repeated ScriptInfo scripts = 4;
|
|
repeated string row_display_columns = 6;
|
|
map<string, ColumnBehavior> column_behaviors = 7;
|
|
string table_kind = 8;
|
|
}
|
|
|
|
// Server-owned behavior for one logical column returned in table details.
|
|
message ColumnBehavior {
|
|
// True when the server created the column as a companion of another column.
|
|
bool generated = 1;
|
|
|
|
// True when clients must display but never submit edits for the column.
|
|
bool read_only = 2;
|
|
|
|
// Logical source column name, empty for ordinary user-defined columns.
|
|
string generated_from = 3;
|
|
}
|
|
|
|
// A script that targets a specific column in a table.
|
|
message ScriptInfo {
|
|
int64 script_id = 1;
|
|
string target_column = 2;
|
|
string target_column_type = 3;
|
|
string script = 4;
|
|
string description = 5;
|
|
}
|
|
|
|
// Request to rename one user-visible column alias in a table.
|
|
message RenameColumnAliasRequest {
|
|
string profile_name = 1;
|
|
string table_name = 2;
|
|
string old_column_name = 3;
|
|
string new_column_name = 4;
|
|
}
|
|
|
|
// Response after renaming one column alias.
|
|
message RenameColumnAliasResponse {
|
|
bool success = 1;
|
|
string message = 2;
|
|
}
|
|
|
|
// Request to delete one table definition entirely.
|
|
message DeleteTableRequest {
|
|
// Profile (schema) name owning the table (must exist).
|
|
string profile_name = 1;
|
|
|
|
// Table to drop (must exist in the profile).
|
|
// Executes DROP TABLE "profile"."table" CASCADE and then removes metadata.
|
|
string table_name = 2;
|
|
}
|
|
|
|
// Response after table deletion.
|
|
message DeleteTableResponse {
|
|
// True if table and metadata were successfully deleted in one transaction.
|
|
bool success = 1;
|
|
|
|
// Human-readable summary of what was removed.
|
|
string message = 2;
|
|
}
|
|
|
|
// How a column type is spelled in ColumnDefinition.field_type.
|
|
enum ColumnTypeSpelling {
|
|
// The name is the whole spelling: "text", "money", "gtin_13".
|
|
COLUMN_TYPE_SPELLING_BARE = 0;
|
|
|
|
// The name takes a precision and a scale: "decimal(12,3)". Precision must be
|
|
// at least 1 and scale no greater than precision; neither may carry a sign,
|
|
// a decimal point, or leading zeros.
|
|
COLUMN_TYPE_SPELLING_DECIMAL = 1;
|
|
|
|
// The name takes the name of another table in the same profile:
|
|
// "link(adresar)". The column holds that table's id, and the server creates
|
|
// the foreign key and its index. A picker offers the profile's other tables
|
|
// as the argument.
|
|
COLUMN_TYPE_SPELLING_LINK = 2;
|
|
}
|
|
|
|
// Response describing the whole column-type vocabulary.
|
|
//
|
|
// This is the authority a client builds its column-type picker from: every rule
|
|
// a client would otherwise hardcode about what a type means is a field here. The
|
|
// list covers types clients may NOT declare as well (see `declarable`), so that
|
|
// the same call also explains the types GetProfileDetails reports back for
|
|
// server-generated companion columns.
|
|
message ListColumnTypesResponse {
|
|
// One column type and everything a client needs to know to offer it.
|
|
message ColumnType {
|
|
// Logical column type (e.g. "money", "instant"). Passed to the server as
|
|
// ColumnDefinition.field_type — see `spelling`, which is what says whether
|
|
// this name is the whole spelling.
|
|
string name = 1;
|
|
|
|
// Underlying PostgreSQL type the logical type maps to (e.g. "NUMERIC").
|
|
// Empty when `compound` is true. For COLUMN_TYPE_SPELLING_DECIMAL this is
|
|
// the unparameterised type; the declared precision and scale are appended.
|
|
string sql_type = 2;
|
|
|
|
// False for types the server generates on its own and rejects when a client
|
|
// declares them: the phone and IBAN companions, and the accounting-transfer
|
|
// connectors. A column-type picker must offer only declarable types.
|
|
bool declarable = 3;
|
|
|
|
// True when the type is a definition row rather than a column: it expands
|
|
// into several schema-managed companion columns and no column of this name
|
|
// survives. A compound type can therefore never be indexed, never be a row
|
|
// display column, and has no single sql_type of its own.
|
|
bool compound = 4;
|
|
|
|
// Whether `name` is the whole spelling or takes arguments.
|
|
ColumnTypeSpelling spelling = 5;
|
|
|
|
// Whether ColumnDefinition.currency is required. Currency is rejected on
|
|
// every type where this is false.
|
|
bool requires_currency = 6;
|
|
|
|
// True when the type may only be chosen while the table is being created.
|
|
// AddTableColumns rejects these, because they bring schema-managed columns
|
|
// that cannot be bolted onto a table that already exists.
|
|
bool creation_only = 7;
|
|
|
|
// Whether ColumnDefinition.quantity_ledger may be set on this type.
|
|
bool allows_quantity_ledger = 8;
|
|
|
|
// Optional name grouping several types a client offers behind one choice —
|
|
// "temporal" for the date and time types, "gtin" for the GTIN lengths.
|
|
// Empty when the type stands on its own.
|
|
string group = 9;
|
|
}
|
|
|
|
// Every column type, declarable or not, ordered by name.
|
|
repeated ColumnType column_types = 1;
|
|
}
|