411 lines
14 KiB
Protocol Buffer
411 lines
14 KiB
Protocol Buffer
// common/proto/tables_data.proto
|
||
syntax = "proto3";
|
||
package komp_ac.tables_data;
|
||
|
||
import "common.proto";
|
||
import "ecb.proto";
|
||
import "google/protobuf/struct.proto";
|
||
|
||
// Read and write row data for user-defined tables inside profiles (schemas).
|
||
// Operations are performed against the physical PostgreSQL table that
|
||
// corresponds to the logical table definition and are scoped by profile
|
||
// (schema). Deletions are soft (set deleted = true). Typed binding and
|
||
// script-based validation are enforced consistently.
|
||
service TablesData {
|
||
// Insert a new row into a table with strict type binding and script validation.
|
||
//
|
||
// Behavior:
|
||
// - Validates that profile (schema) exists and table is defined for it
|
||
// - Validates provided columns exist (user-defined or allowed system/FK columns)
|
||
// - For columns targeted by scripts in this table, the client MUST provide the
|
||
// value, and it MUST equal the script’s calculated value (compared type-safely)
|
||
// - Binds values with correct SQL types, rejects invalid formats/ranges
|
||
// - Inserts the row and returns the new id; queues search indexing (best effort)
|
||
// - If the physical table is missing but the definition exists, returns INTERNAL
|
||
rpc PostTableData(PostTableDataRequest) returns (PostTableDataResponse);
|
||
|
||
// Insert an ACCOUNTING-enabled source row with an explicit exchange-rate
|
||
// treatment for the journal line created from it.
|
||
rpc PostAccountingTableData(PostAccountingTableDataRequest)
|
||
returns (PostTableDataResponse);
|
||
|
||
// Insert multiple rows by applying PostTableData behavior to each row.
|
||
//
|
||
// Behavior:
|
||
// - Accepts 1..10,000 rows in one gRPC request
|
||
// - Processes rows in request order
|
||
// - Each row is inserted through the same validation, script execution,
|
||
// typed binding, database insert, and indexing path as PostTableData
|
||
// - Stops at the first failing row and returns that row's gRPC error code
|
||
// with row index context; rows inserted before the failure remain inserted
|
||
rpc PostTableDataBulk(PostTableDataBulkRequest) returns (PostTableDataBulkResponse);
|
||
|
||
// Update existing row data with strict type binding and script validation.
|
||
//
|
||
// Behavior:
|
||
// - Validates profile and table, and that the record exists
|
||
// - If request data is empty, returns success without changing the row
|
||
// - For columns targeted by scripts:
|
||
// • If included in update, provided value must equal the script result
|
||
// • If not included, update must not cause the script result to differ
|
||
// from the current stored value; otherwise FAILED_PRECONDITION is returned
|
||
// - Binds values with correct SQL types; rejects invalid formats/ranges
|
||
// - Updates the row and returns the id; queues search indexing (best effort)
|
||
rpc PutTableData(PutTableDataRequest) returns (PutTableDataResponse);
|
||
rpc PutAccountingTableData(PutAccountingTableDataRequest)
|
||
returns (PutTableDataResponse);
|
||
|
||
// Performs a PUT after the user explicitly accepted changing a referenced version.
|
||
rpc PutTableDataConfirmed(PutTableDataConfirmedRequest) returns (PutTableDataResponse);
|
||
|
||
// Lists profiles whose rows currently point at this row version.
|
||
rpc GetTableUpdateImpact(TableUpdateImpactRequest) returns (TableUpdateImpactResponse);
|
||
|
||
// Snapshots the current version of a row and advances its version.
|
||
// Existing references remain immutable; new references use the new version.
|
||
rpc ArchiveTableData(ArchiveTableDataRequest) returns (ArchiveTableDataResponse);
|
||
|
||
// Soft-delete a single record (sets deleted = true) if it exists and is not already deleted.
|
||
//
|
||
// Behavior:
|
||
// - Validates profile and table definition
|
||
// - Updates only rows with deleted = false
|
||
// - success = true means a row was actually changed; false means nothing to delete
|
||
// - If the physical table is missing but the definition exists, returns INTERNAL
|
||
rpc DeleteTableData(DeleteTableDataRequest) returns (DeleteTableDataResponse);
|
||
|
||
// Fetch a single non-deleted row by id as textified values.
|
||
//
|
||
// Behavior:
|
||
// - Validates profile and table definition
|
||
// - Returns all columns as strings (COALESCE(col::TEXT, '') AS col)
|
||
// including: id, deleted, row_revision, all user-defined columns, and FK columns
|
||
// named by whoever declared each link, plus "account" on
|
||
// ACCOUNTING-enabled tables
|
||
// - Fails with NOT_FOUND if record does not exist or is soft-deleted
|
||
// - If the physical table is missing but the definition exists, returns INTERNAL
|
||
rpc GetTableData(GetTableDataRequest) returns (GetTableDataResponse);
|
||
|
||
// Fetches one exact version of a row.
|
||
rpc GetTableDataVersion(GetTableDataVersionRequest) returns (GetTableDataResponse);
|
||
|
||
// Count non-deleted rows in a table.
|
||
//
|
||
// Behavior:
|
||
// - Validates profile and table definition
|
||
// - Returns komp_ac.common.CountResponse.count with rows where deleted = FALSE
|
||
// - If the physical table is missing but the definition exists, returns INTERNAL
|
||
rpc GetTableDataCount(GetTableDataCountRequest) returns (komp_ac.common.CountResponse);
|
||
|
||
// Fetch the N-th non-deleted row by id order (1-based), then return its full data.
|
||
//
|
||
// Behavior:
|
||
// - position is 1-based (position = 1 → first row by id ASC with deleted = FALSE)
|
||
// - Returns NOT_FOUND if position is out of bounds
|
||
// - Otherwise identical to GetTableData for the selected id
|
||
rpc GetTableDataByPosition(GetTableDataByPositionRequest) returns (GetTableDataResponse);
|
||
}
|
||
|
||
// Insert a new row.
|
||
message PostTableDataRequest {
|
||
// Required. Profile (PostgreSQL schema) name that owns the table.
|
||
// Must exist in the schemas table.
|
||
string profile_name = 1;
|
||
|
||
// Required. Logical table (definition) name within the profile.
|
||
// Must exist in table_definitions for the given profile.
|
||
string table_name = 2;
|
||
|
||
// Required. Key-value data for columns to insert.
|
||
//
|
||
// Allowed keys:
|
||
// - User-defined columns from the table definition
|
||
// - System/FK columns:
|
||
// • "deleted" (BOOLEAN), optional; default FALSE if not provided
|
||
// • one BIGINT per link, named by whoever declared it
|
||
// • "account" (required, non-null slash-delimited account string) on
|
||
// ACCOUNTING-enabled tables
|
||
//
|
||
// Type expectations by SQL type:
|
||
// - TEXT: string value; empty string is treated as NULL
|
||
// - BOOLEAN: bool value
|
||
// - TIMESTAMPTZ: ISO 8601/RFC 3339 string (parsed to TIMESTAMPTZ)
|
||
// - INTEGER: number with no fractional part and within i32 range
|
||
// - BIGINT: number with no fractional part and within i64 range
|
||
// - NUMERIC(p,s): string representation only; empty string becomes NULL
|
||
// (numbers for NUMERIC are rejected to avoid precision loss)
|
||
//
|
||
// Script validation rules:
|
||
// - If a script exists for a target column, that column MUST be present here,
|
||
// and its provided value MUST equal the script’s computed value (type-aware
|
||
// comparison, e.g., decimals are compared numerically).
|
||
//
|
||
// Notes:
|
||
// - Unknown/invalid column names are rejected
|
||
// - Some application-specific validations may apply (e.g., max length for
|
||
// certain fields like "telefon")
|
||
map<string, google.protobuf.Value> data = 3;
|
||
}
|
||
|
||
message PostAccountingTableDataRequest {
|
||
PostTableDataRequest row = 1;
|
||
komp_ac.ecb.ExchangeRateSelection exchange_rate_selection = 2;
|
||
}
|
||
|
||
// Insert response.
|
||
message PostTableDataResponse {
|
||
// True if the insert succeeded.
|
||
bool success = 1;
|
||
|
||
// Human-readable message.
|
||
string message = 2;
|
||
|
||
// The id of the inserted row.
|
||
int64 inserted_id = 3;
|
||
|
||
// Revision committed for the inserted row.
|
||
int64 row_revision = 4;
|
||
|
||
// Journal created or updated by automatic accounting, when applicable.
|
||
optional int64 journal_id = 5;
|
||
}
|
||
|
||
// One row in a bulk insert request.
|
||
message PostTableDataBulkRow {
|
||
// Required. Same data payload as PostTableDataRequest.data.
|
||
map<string, google.protobuf.Value> data = 1;
|
||
}
|
||
|
||
// Bulk insert request.
|
||
message PostTableDataBulkRequest {
|
||
// Required. Profile (PostgreSQL schema) name that owns the table.
|
||
string profile_name = 1;
|
||
|
||
// Required. Logical table (definition) name within the profile.
|
||
string table_name = 2;
|
||
|
||
// Required. Rows to insert. Must contain at least 1 and at most 10,000 rows.
|
||
repeated PostTableDataBulkRow rows = 3;
|
||
}
|
||
|
||
// Bulk insert response.
|
||
message PostTableDataBulkResponse {
|
||
// True if all rows were inserted successfully.
|
||
bool success = 1;
|
||
|
||
// Human-readable message.
|
||
string message = 2;
|
||
|
||
// Per-row responses from the underlying PostTableData logic, in request order.
|
||
repeated PostTableDataResponse responses = 3;
|
||
}
|
||
|
||
// Update an existing row.
|
||
message PutTableDataRequest {
|
||
// Required. Profile (schema) name.
|
||
string profile_name = 1;
|
||
|
||
// Required. Table name within the profile.
|
||
string table_name = 2;
|
||
|
||
// Required. Id of the row to update.
|
||
int64 id = 3;
|
||
|
||
// Required. Columns to update (same typing rules as PostTableDataRequest.data).
|
||
// "account" may be omitted, but cannot be null when supplied.
|
||
//
|
||
// Special script rules:
|
||
// - If a script targets column X and X is included here, the value for X must
|
||
// equal the script’s result (type-aware).
|
||
// - If X is not included here but the update would cause the script’s result
|
||
// to change compared to the current stored value, the update is rejected with
|
||
// FAILED_PRECONDITION, instructing the caller to include X explicitly.
|
||
//
|
||
// Passing an empty map results in a no-op success response.
|
||
map<string, google.protobuf.Value> data = 4;
|
||
|
||
// Required. Revision returned when this row was loaded or last saved.
|
||
int64 expected_revision = 5;
|
||
|
||
}
|
||
|
||
message PutAccountingTableDataRequest {
|
||
PutTableDataRequest update = 1;
|
||
komp_ac.ecb.ExchangeRateSelection exchange_rate_selection = 2;
|
||
bool confirmed = 3;
|
||
repeated string expected_affected_profiles = 4;
|
||
}
|
||
|
||
message PutTableDataConfirmedRequest {
|
||
PutTableDataRequest update = 1;
|
||
repeated string expected_affected_profiles = 2;
|
||
}
|
||
|
||
// Update response.
|
||
message PutTableDataResponse {
|
||
// True if the update succeeded (or no-op on empty data).
|
||
bool success = 1;
|
||
|
||
// Human-readable message.
|
||
string message = 2;
|
||
|
||
// The id of the updated row.
|
||
int64 updated_id = 3;
|
||
|
||
// Revision committed for the updated row.
|
||
int64 row_revision = 4;
|
||
|
||
// Journal updated by automatic accounting, when applicable.
|
||
optional int64 journal_id = 5;
|
||
}
|
||
|
||
message TableUpdateImpactRequest {
|
||
string profile_name = 1;
|
||
string table_name = 2;
|
||
int64 id = 3;
|
||
}
|
||
|
||
message TableUpdateImpactResponse {
|
||
repeated string affected_profiles = 1;
|
||
}
|
||
|
||
message ArchiveTableDataRequest {
|
||
string profile_name = 1;
|
||
string table_name = 2;
|
||
int64 id = 3;
|
||
int64 expected_revision = 4;
|
||
}
|
||
|
||
message ArchiveTableDataResponse {
|
||
bool success = 1;
|
||
int64 archived_version = 2;
|
||
string archived_at = 3;
|
||
repeated string affected_profiles = 4;
|
||
int64 current_version = 5;
|
||
int64 row_revision = 6;
|
||
}
|
||
|
||
// Soft-delete a single row.
|
||
message DeleteTableDataRequest {
|
||
// Required. Profile (schema) name.
|
||
string profile_name = 1;
|
||
|
||
// Required. Table name within the profile.
|
||
string table_name = 2;
|
||
|
||
// Required. Row id to soft-delete.
|
||
int64 record_id = 3;
|
||
|
||
// Required. Revision returned when this row was loaded or last saved.
|
||
int64 expected_revision = 4;
|
||
}
|
||
|
||
// Soft-delete response.
|
||
message DeleteTableDataResponse {
|
||
// True when the row was successfully marked deleted.
|
||
bool success = 1;
|
||
}
|
||
|
||
// Fetch a single non-deleted row by id.
|
||
message GetTableDataRequest {
|
||
// Required. Profile (schema) name.
|
||
string profile_name = 1;
|
||
|
||
// Required. Table name within the profile.
|
||
string table_name = 2;
|
||
|
||
// Required. Id of the row to fetch.
|
||
int64 id = 3;
|
||
|
||
}
|
||
|
||
message GetTableDataVersionRequest {
|
||
string profile_name = 1;
|
||
string table_name = 2;
|
||
int64 id = 3;
|
||
int64 version = 4;
|
||
}
|
||
|
||
// A link resolved exactly as the source row stored it, together with the
|
||
// target row's current state. The physical companion version column remains
|
||
// an internal implementation detail.
|
||
enum ResolvedTableLinkStatus {
|
||
CURRENT = 0;
|
||
SUPERSEDED = 1;
|
||
UNAVAILABLE = 2;
|
||
DELETED = 3;
|
||
// The source row may expose the FK and the target's configured display
|
||
// columns, but the caller may not read the target table itself.
|
||
RESTRICTED = 4;
|
||
}
|
||
|
||
message ResolvedTableLinkVersion {
|
||
int64 version = 1;
|
||
map<string, string> data = 2;
|
||
repeated string row_display_values = 3;
|
||
repeated string row_display_columns = 4;
|
||
}
|
||
|
||
message ResolvedTableLinkUpdate {
|
||
int64 version = 1;
|
||
map<string, string> data = 2;
|
||
repeated string row_display_values = 3;
|
||
repeated string changed_fields = 4;
|
||
}
|
||
|
||
message ResolvedTableLink {
|
||
int64 id = 1;
|
||
ResolvedTableLinkVersion saved = 2;
|
||
ResolvedTableLinkStatus status = 3;
|
||
ResolvedTableLinkUpdate newer = 4;
|
||
}
|
||
|
||
// Row payload: all columns returned as strings.
|
||
message GetTableDataResponse {
|
||
// Map of column_name → stringified value for:
|
||
// - id, deleted
|
||
// - all user-defined columns from the table definition
|
||
// - one column per link, named by whoever declared it
|
||
// - account for ACCOUNTING-enabled tables
|
||
//
|
||
// All values are returned as TEXT via col::TEXT and COALESCEed to empty string
|
||
// (NULL becomes ""). The row is returned only if deleted = FALSE.
|
||
map<string, string> data = 1;
|
||
|
||
// Configured human-readable values for this row, in display order. A value
|
||
// is empty when its column is NULL/empty; an empty list means the row is
|
||
// identified by its id alone.
|
||
repeated string row_display_values = 2;
|
||
repeated string row_display_columns = 3;
|
||
|
||
// Links keyed by their public column name. Each value contains the row as
|
||
// saved by this record and, when the caller may read the target table, its
|
||
// newest available state. Restricted links contain only the target table's
|
||
// configured display columns.
|
||
map<string, ResolvedTableLink> resolved_links = 4;
|
||
|
||
// The generation returned by this response. Zero is never returned.
|
||
int64 row_version = 5;
|
||
}
|
||
|
||
// Count non-deleted rows.
|
||
message GetTableDataCountRequest {
|
||
// Required. Profile (schema) name.
|
||
string profile_name = 1;
|
||
|
||
// Required. Table name within the profile.
|
||
string table_name = 2;
|
||
}
|
||
|
||
// Fetch by ordinal position among non-deleted rows (1-based).
|
||
message GetTableDataByPositionRequest {
|
||
// Required. Profile (schema) name.
|
||
string profile_name = 1;
|
||
|
||
// Required. Table name within the profile.
|
||
string table_name = 2;
|
||
|
||
// Required. 1-based position by id ascending among rows with deleted = FALSE.
|
||
int32 position = 3;
|
||
}
|