// common/proto/tables_data.proto syntax = "proto3"; package komp_ac.tables_data; import "common.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); // 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); // 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, all user-defined columns, and FK columns // named "_id" for each table link // - 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); // 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 // • "_id" (BIGINT) for each table link // // 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 data = 3; } // 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; } // 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). // // 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 data = 4; } // 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; } // 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; } // Soft-delete response. message DeleteTableDataResponse { // True if a row was marked deleted (id existed and was not already 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; } // 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 // - FK columns named "_id" for each table link // // 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 data = 1; } // 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; }