From 0c8a1b1c195b73e7bbcd31e6f7041aca746f4a38 Mon Sep 17 00:00:00 2001 From: Filipriec Date: Sat, 22 Aug 2026 11:14:54 +0200 Subject: [PATCH] new form page row2 from id --- client | 2 +- common/proto/tables_data.proto | 35 ++++-- common/src/proto/descriptor.bin | Bin 188544 -> 189628 bytes common/src/proto/komp_ac.tables_data.rs | 148 ++++++++++++++++++++++-- server | 2 +- 5 files changed, 167 insertions(+), 20 deletions(-) diff --git a/client b/client index de93a2a5..78049ab8 160000 --- a/client +++ b/client @@ -1 +1 @@ -Subproject commit de93a2a5e1af71bced9a211e6e34f8af1cb0e4bd +Subproject commit 78049ab882a32270bf0c976fd9ecef0a4409ee97 diff --git a/common/proto/tables_data.proto b/common/proto/tables_data.proto index c2060b86..c100f808 100644 --- a/common/proto/tables_data.proto +++ b/common/proto/tables_data.proto @@ -104,12 +104,15 @@ service TablesData { // - If the physical table is missing but the definition exists, returns INTERNAL rpc GetTableDataCount(GetTableDataCountRequest) returns (komp_ac.common.CountResponse); - // Fetch the last non-deleted row by id together with the exact row count. - // This is the efficient form-opening path: unlike GetTableDataByPosition at - // the final position, it does not walk the table through a large OFFSET, and - // it avoids a separate client/server round trip for the count. + // Fetch the last non-deleted row by id. This is the efficient form-opening + // path and does not scan the table through OFFSET or COUNT(*). rpc GetLastTableData(GetLastTableDataRequest) returns (GetLastTableDataResponse); + // Fetch the nearest visible row before or after an existing row id. This is + // the ordinary form-navigation path and skips deleted/id-gap rows. + rpc GetAdjacentTableData(GetAdjacentTableDataRequest) + returns (GetAdjacentTableDataResponse); + // Fetch the N-th non-deleted row by id order (1-based), then return its full data. // // Behavior: @@ -426,18 +429,36 @@ message GetTableDataCountRequest { string table_name = 2; } -// Fetch the last visible row and the exact number of visible rows. +// Fetch the last visible row by stable row id. message GetLastTableDataRequest { string profile_name = 1; string table_name = 2; } message GetLastTableDataResponse { - int64 total_count = 1; - // Absent when total_count is zero. + // Zero when the table has no visible rows. + int64 last_id = 1; + // Absent when last_id is zero. GetTableDataResponse row = 2; } +enum RowIdDirection { + ROW_ID_DIRECTION_NEXT = 0; + ROW_ID_DIRECTION_PREVIOUS = 1; +} + +message GetAdjacentTableDataRequest { + string profile_name = 1; + string table_name = 2; + int64 anchor_id = 3; + RowIdDirection direction = 4; +} + +message GetAdjacentTableDataResponse { + // Absent when no visible row exists in the requested direction. + GetTableDataResponse row = 1; +} + // Fetch by ordinal position among non-deleted rows (1-based). message GetTableDataByPositionRequest { // Required. Profile (schema) name. diff --git a/common/src/proto/descriptor.bin b/common/src/proto/descriptor.bin index 49017f96e5522fef7c4a5865cf8ee9704edb3c2d..b8a7c42a44a7fd011783fcd077a48fde21a00504 100644 GIT binary patch delta 8010 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 KXM0!sjQ;}@ts=7k delta 7095 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 diff --git a/common/src/proto/komp_ac.tables_data.rs b/common/src/proto/komp_ac.tables_data.rs index a8bf358a..bedbe498 100644 --- a/common/src/proto/komp_ac.tables_data.rs +++ b/common/src/proto/komp_ac.tables_data.rs @@ -387,7 +387,7 @@ pub struct GetTableDataCountRequest { #[prost(string, tag = "2")] pub table_name: ::prost::alloc::string::String, } -/// Fetch the last visible row and the exact number of visible rows. +/// Fetch the last visible row by stable row id. #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct GetLastTableDataRequest { #[prost(string, tag = "1")] @@ -397,12 +397,30 @@ pub struct GetLastTableDataRequest { } #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetLastTableDataResponse { + /// Zero when the table has no visible rows. #[prost(int64, tag = "1")] - pub total_count: i64, - /// Absent when total_count is zero. + pub last_id: i64, + /// Absent when last_id is zero. #[prost(message, optional, tag = "2")] pub row: ::core::option::Option, } +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct GetAdjacentTableDataRequest { + #[prost(string, tag = "1")] + pub profile_name: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub table_name: ::prost::alloc::string::String, + #[prost(int64, tag = "3")] + pub anchor_id: i64, + #[prost(enumeration = "RowIdDirection", tag = "4")] + pub direction: i32, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetAdjacentTableDataResponse { + /// Absent when no visible row exists in the requested direction. + #[prost(message, optional, tag = "1")] + pub row: ::core::option::Option, +} /// Fetch by ordinal position among non-deleted rows (1-based). #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct GetTableDataByPositionRequest { @@ -456,6 +474,32 @@ impl ResolvedTableLinkStatus { } } } +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum RowIdDirection { + Next = 0, + Previous = 1, +} +impl RowIdDirection { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::Next => "ROW_ID_DIRECTION_NEXT", + Self::Previous => "ROW_ID_DIRECTION_PREVIOUS", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "ROW_ID_DIRECTION_NEXT" => Some(Self::Next), + "ROW_ID_DIRECTION_PREVIOUS" => Some(Self::Previous), + _ => None, + } + } +} /// Generated client implementations. pub mod tables_data_client { #![allow( @@ -1034,10 +1078,8 @@ pub mod tables_data_client { ); self.inner.unary(req, path, codec).await } - /// Fetch the last non-deleted row by id together with the exact row count. - /// This is the efficient form-opening path: unlike GetTableDataByPosition at - /// the final position, it does not walk the table through a large OFFSET, and - /// it avoids a separate client/server round trip for the count. + /// Fetch the last non-deleted row by id. This is the efficient form-opening + /// path and does not scan the table through OFFSET or COUNT(\*). pub async fn get_last_table_data( &mut self, request: impl tonic::IntoRequest, @@ -1064,6 +1106,37 @@ pub mod tables_data_client { ); self.inner.unary(req, path, codec).await } + /// Fetch the nearest visible row before or after an existing row id. This is + /// the ordinary form-navigation path and skips deleted/id-gap rows. + pub async fn get_adjacent_table_data( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/komp_ac.tables_data.TablesData/GetAdjacentTableData", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new( + "komp_ac.tables_data.TablesData", + "GetAdjacentTableData", + ), + ); + self.inner.unary(req, path, codec).await + } /// Fetch the N-th non-deleted row by id order (1-based), then return its full data. /// /// Behavior: @@ -1282,10 +1355,8 @@ pub mod tables_data_server { tonic::Response, tonic::Status, >; - /// Fetch the last non-deleted row by id together with the exact row count. - /// This is the efficient form-opening path: unlike GetTableDataByPosition at - /// the final position, it does not walk the table through a large OFFSET, and - /// it avoids a separate client/server round trip for the count. + /// Fetch the last non-deleted row by id. This is the efficient form-opening + /// path and does not scan the table through OFFSET or COUNT(\*). async fn get_last_table_data( &self, request: tonic::Request, @@ -1293,6 +1364,15 @@ pub mod tables_data_server { tonic::Response, tonic::Status, >; + /// Fetch the nearest visible row before or after an existing row id. This is + /// the ordinary form-navigation path and skips deleted/id-gap rows. + async fn get_adjacent_table_data( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; /// Fetch the N-th non-deleted row by id order (1-based), then return its full data. /// /// Behavior: @@ -2128,6 +2208,52 @@ pub mod tables_data_server { }; Box::pin(fut) } + "/komp_ac.tables_data.TablesData/GetAdjacentTableData" => { + #[allow(non_camel_case_types)] + struct GetAdjacentTableDataSvc(pub Arc); + impl< + T: TablesData, + > tonic::server::UnaryService + for GetAdjacentTableDataSvc { + type Response = super::GetAdjacentTableDataResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_adjacent_table_data(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GetAdjacentTableDataSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } "/komp_ac.tables_data.TablesData/GetTableDataByPosition" => { #[allow(non_camel_case_types)] struct GetTableDataByPositionSvc(pub Arc); diff --git a/server b/server index 65f54df5..8454f4dc 160000 --- a/server +++ b/server @@ -1 +1 @@ -Subproject commit 65f54df500786ff52797140b63975fa01b05e79f +Subproject commit 8454f4dc200ce6cc47041f6358112cfcfe274178