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 49017f96..b8a7c42a 100644 Binary files a/common/src/proto/descriptor.bin and b/common/src/proto/descriptor.bin differ 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