From 28da618ca76abd4c620234350563482f95c5e23d Mon Sep 17 00:00:00 2001 From: Priec Date: Sat, 18 Jul 2026 00:07:32 +0200 Subject: [PATCH] optimization on the client steel scripts3 - removing datafusion from the steel dependencies --- Cargo.toml | 2 +- client | 2 +- common/build.rs | 2 + common/proto/currency_conversion.proto | 53 ++ common/proto/table_script.proto | 28 +- common/src/lib.rs | 3 + common/src/proto/descriptor.bin | Bin 105788 -> 110707 bytes .../src/proto/komp_ac.currency_conversion.rs | 535 ++++++++++++++++++ common/src/proto/komp_ac.table_script.rs | 48 +- server | 2 +- 10 files changed, 663 insertions(+), 12 deletions(-) create mode 100644 common/proto/currency_conversion.proto create mode 100644 common/src/proto/komp_ac.currency_conversion.rs diff --git a/Cargo.toml b/Cargo.toml index 1024dd6..cb49e32 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ resolver = "3" [workspace.package] # TODO: idk how to do the name, fix later # name = "komp_ac" -version = "0.8.26" +version = "0.8.27" edition = "2024" license = "GPL-3.0-or-later" authors = ["Filip Priečinský "] diff --git a/client b/client index a0695c0..f045b8f 160000 --- a/client +++ b/client @@ -1 +1 @@ -Subproject commit a0695c0d27c97c4f6215c6431239002ffda8e35b +Subproject commit f045b8fa8eb65797b929f951bb255464bc553666 diff --git a/common/build.rs b/common/build.rs index a58bd0d..5c2f890 100644 --- a/common/build.rs +++ b/common/build.rs @@ -216,6 +216,7 @@ fn main() -> Result<(), Box> { .compile_protos( &[ "proto/common.proto", + "proto/currency_conversion.proto", "proto/document_data.proto", "proto/adresar.proto", "proto/analytics.proto", @@ -242,6 +243,7 @@ fn main() -> Result<(), Box> { println!("cargo:rerun-if-changed=build.rs"); for proto in [ "proto/common.proto", + "proto/currency_conversion.proto", "proto/document_data.proto", "proto/adresar.proto", "proto/analytics.proto", diff --git a/common/proto/currency_conversion.proto b/common/proto/currency_conversion.proto new file mode 100644 index 0000000..59222ff --- /dev/null +++ b/common/proto/currency_conversion.proto @@ -0,0 +1,53 @@ +syntax = "proto3"; +package komp_ac.currency_conversion; + +service CurrencyConversion { + rpc ConvertCurrency(ConvertCurrencyRequest) returns (ConvertCurrencyResponse); + rpc ResolveRate(ResolveRateRequest) returns (ResolvedFxRate); + rpc ListRequiredCurrencies(ListRequiredCurrenciesRequest) + returns (ListRequiredCurrenciesResponse); +} + +message ConvertCurrencyRequest { + string amount = 1; + string source_currency = 2; + string target_currency = 3; + string source = 4; + string as_of_date = 5; +} + +message ResolveRateRequest { + string source_currency = 1; + string target_currency = 2; + string source = 3; + string as_of_date = 4; +} + +message FxRateComponent { + string currency = 1; + string units_per_base = 2; +} + +message ResolvedFxRate { + string source_currency = 1; + string target_currency = 2; + string rate = 3; + string effective_date = 4; + string source = 5; + int64 rate_set_id = 6; + int32 revision = 7; + string base_currency = 8; + repeated FxRateComponent components = 9; +} + +message ConvertCurrencyResponse { + string amount = 1; + string currency = 2; + ResolvedFxRate resolved_rate = 3; +} + +message ListRequiredCurrenciesRequest {} + +message ListRequiredCurrenciesResponse { + repeated string currencies = 1; +} diff --git a/common/proto/table_script.proto b/common/proto/table_script.proto index 65d7d2c..fd795f0 100644 --- a/common/proto/table_script.proto +++ b/common/proto/table_script.proto @@ -63,9 +63,9 @@ service TableScript { // // The server derives the required inputs from stored script_dependencies; // callers do not choose arbitrary tables or columns. Direct related-column - // reads are grouped by related row, while related aggregates are evaluated - // through the analytics runtime. Returned values include logical type and - // currency metadata so the client can create correctly typed ScriptValues. + // reads and related aggregates are evaluated through SQLx in one read-only + // PostgreSQL snapshot. Returned values include logical type and currency + // metadata so the client can create correctly typed ScriptValues. // // Current-table column values are intentionally not returned. The client // supplies those directly from row_data so unsaved edits participate in @@ -185,6 +185,14 @@ message ScriptDependency { string operation = 5; // Relationship table used to match the owner row to the related collection. string via_table = 6; + // Source currency for an fx_conversion dependency. + string from_currency = 7; + // Target currency for an fx_conversion dependency. + string to_currency = 8; + // Persisted exchange-rate provider/source identifier. + string source = 9; + // Requested YYYY-MM-DD date, or empty for the latest stored snapshot. + string as_of_date = 10; } // Identifies the active form row whose external Steel inputs must be hydrated. @@ -243,4 +251,18 @@ message HydrateScriptDependenciesResponse { repeated HydratedColumnValue columns = 1; // Exact related aggregate inputs declared by stored scripts. repeated HydratedAggregateValue aggregates = 2; + // Exact resolved rates declared by the table's stored scripts. + repeated HydratedFxRate fx_rates = 3; +} + +message HydratedFxRate { + string from_currency = 1; + string to_currency = 2; + string source = 3; + string requested_as_of_date = 4; + string rate = 5; + string effective_date = 6; + int64 rate_set_id = 7; + int32 revision = 8; + string base_currency = 9; } diff --git a/common/src/lib.rs b/common/src/lib.rs index edba9b1..b975297 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -20,6 +20,9 @@ pub mod proto { pub mod common { include!("proto/komp_ac.common.rs"); } + pub mod currency_conversion { + include!("proto/komp_ac.currency_conversion.rs"); + } pub mod document_data { include!("proto/komp_ac.document_data.rs"); } diff --git a/common/src/proto/descriptor.bin b/common/src/proto/descriptor.bin index 2f815c0ee5b419601b9a35390566d4cae9873b6c..a78b801f8dbcb1b2bd3b1c2b65d69128155a6642 100644 GIT binary patch delta 6146 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 F{tJl_gN*j zw?+QsZ~ovea=k4>PucTda=+&jQMQfj!i#OQOETeQ=Qq4u$HoFP9zk?cbu>G*!?TtD zvbp!$zrp!VSgwi4JLK-|@B@cFn8M%@#$4k9yV!rEmb+i(@X{&oPI_wQ=hP40_Zg}t zMyBa8JvDpMn;jdUNM-+g-FZxS(_{3~oiX}Mc_Wh}6PfAp(Ys!HW^#J`r&PB7x1(K5eSDRn8_89$V3sIBt4MS-p{RNbon7KOZD_gt86rD!A&SBH zGuTJP03}&!#k}MO&iVOZU}{oOKG=~;3d*Y;Pice>*JQ@P7yeo>u*KAlYr()&60)WS z{#+xpMv=8JeK4w8&~{Nh$xSxz@x1_mXycq8lgMOa>K?AzZ@dUV>uj5c`U2J z`ZPiwEApm;e!mhT%3~!^Z)=YgrT(Hu$YWJLba2_PhKTZ54bz7i3>Qw-!Gv~AO^UB0 zw*On8l5*=fc04$x+&a2@^z@Dr*pPq3aQV|lm;g#>AlBJL)ECo0*U6xyga&&19zSov G!u, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ConvertCurrencyResponse { + #[prost(string, tag = "1")] + pub amount: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub currency: ::prost::alloc::string::String, + #[prost(message, optional, tag = "3")] + pub resolved_rate: ::core::option::Option, +} +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct ListRequiredCurrenciesRequest {} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct ListRequiredCurrenciesResponse { + #[prost(string, repeated, tag = "1")] + pub currencies: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} +/// Generated client implementations. +pub mod currency_conversion_client { + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] + use tonic::codegen::*; + use tonic::codegen::http::Uri; + #[derive(Debug, Clone)] + pub struct CurrencyConversionClient { + inner: tonic::client::Grpc, + } + impl CurrencyConversionClient { + /// Attempt to create a new client by connecting to a given endpoint. + pub async fn connect(dst: D) -> Result + where + D: TryInto, + D::Error: Into, + { + let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; + Ok(Self::new(conn)) + } + } + impl CurrencyConversionClient + where + T: tonic::client::GrpcService, + T::Error: Into, + T::ResponseBody: Body + std::marker::Send + 'static, + ::Error: Into + std::marker::Send, + { + pub fn new(inner: T) -> Self { + let inner = tonic::client::Grpc::new(inner); + Self { inner } + } + pub fn with_origin(inner: T, origin: Uri) -> Self { + let inner = tonic::client::Grpc::with_origin(inner, origin); + Self { inner } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> CurrencyConversionClient> + where + F: tonic::service::Interceptor, + T::ResponseBody: Default, + T: tonic::codegen::Service< + http::Request, + Response = http::Response< + >::ResponseBody, + >, + >, + , + >>::Error: Into + std::marker::Send + std::marker::Sync, + { + CurrencyConversionClient::new(InterceptedService::new(inner, interceptor)) + } + /// Compress requests with the given encoding. + /// + /// This requires the server to support it otherwise it might respond with an + /// error. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.send_compressed(encoding); + self + } + /// Enable decompressing responses. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.accept_compressed(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_decoding_message_size(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_encoding_message_size(limit); + self + } + pub async fn convert_currency( + &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.currency_conversion.CurrencyConversion/ConvertCurrency", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new( + "komp_ac.currency_conversion.CurrencyConversion", + "ConvertCurrency", + ), + ); + self.inner.unary(req, path, codec).await + } + pub async fn resolve_rate( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result, 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.currency_conversion.CurrencyConversion/ResolveRate", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new( + "komp_ac.currency_conversion.CurrencyConversion", + "ResolveRate", + ), + ); + self.inner.unary(req, path, codec).await + } + pub async fn list_required_currencies( + &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.currency_conversion.CurrencyConversion/ListRequiredCurrencies", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new( + "komp_ac.currency_conversion.CurrencyConversion", + "ListRequiredCurrencies", + ), + ); + self.inner.unary(req, path, codec).await + } + } +} +/// Generated server implementations. +pub mod currency_conversion_server { + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] + use tonic::codegen::*; + /// Generated trait containing gRPC methods that should be implemented for use with CurrencyConversionServer. + #[async_trait] + pub trait CurrencyConversion: std::marker::Send + std::marker::Sync + 'static { + async fn convert_currency( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn resolve_rate( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn list_required_currencies( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + } + #[derive(Debug)] + pub struct CurrencyConversionServer { + inner: Arc, + accept_compression_encodings: EnabledCompressionEncodings, + send_compression_encodings: EnabledCompressionEncodings, + max_decoding_message_size: Option, + max_encoding_message_size: Option, + } + impl CurrencyConversionServer { + pub fn new(inner: T) -> Self { + Self::from_arc(Arc::new(inner)) + } + pub fn from_arc(inner: Arc) -> Self { + Self { + inner, + accept_compression_encodings: Default::default(), + send_compression_encodings: Default::default(), + max_decoding_message_size: None, + max_encoding_message_size: None, + } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> InterceptedService + where + F: tonic::service::Interceptor, + { + InterceptedService::new(Self::new(inner), interceptor) + } + /// Enable decompressing requests with the given encoding. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.accept_compression_encodings.enable(encoding); + self + } + /// Compress responses with the given encoding, if the client supports it. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.send_compression_encodings.enable(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.max_decoding_message_size = Some(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.max_encoding_message_size = Some(limit); + self + } + } + impl tonic::codegen::Service> for CurrencyConversionServer + where + T: CurrencyConversion, + B: Body + std::marker::Send + 'static, + B::Error: Into + std::marker::Send + 'static, + { + type Response = http::Response; + type Error = std::convert::Infallible; + type Future = BoxFuture; + fn poll_ready( + &mut self, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + fn call(&mut self, req: http::Request) -> Self::Future { + match req.uri().path() { + "/komp_ac.currency_conversion.CurrencyConversion/ConvertCurrency" => { + #[allow(non_camel_case_types)] + struct ConvertCurrencySvc(pub Arc); + impl< + T: CurrencyConversion, + > tonic::server::UnaryService + for ConvertCurrencySvc { + type Response = super::ConvertCurrencyResponse; + 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 { + ::convert_currency(&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 = ConvertCurrencySvc(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.currency_conversion.CurrencyConversion/ResolveRate" => { + #[allow(non_camel_case_types)] + struct ResolveRateSvc(pub Arc); + impl< + T: CurrencyConversion, + > tonic::server::UnaryService + for ResolveRateSvc { + type Response = super::ResolvedFxRate; + 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 { + ::resolve_rate(&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 = ResolveRateSvc(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.currency_conversion.CurrencyConversion/ListRequiredCurrencies" => { + #[allow(non_camel_case_types)] + struct ListRequiredCurrenciesSvc(pub Arc); + impl< + T: CurrencyConversion, + > tonic::server::UnaryService + for ListRequiredCurrenciesSvc { + type Response = super::ListRequiredCurrenciesResponse; + 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 { + ::list_required_currencies( + &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 = ListRequiredCurrenciesSvc(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) + } + _ => { + Box::pin(async move { + let mut response = http::Response::new( + tonic::body::Body::default(), + ); + let headers = response.headers_mut(); + headers + .insert( + tonic::Status::GRPC_STATUS, + (tonic::Code::Unimplemented as i32).into(), + ); + headers + .insert( + http::header::CONTENT_TYPE, + tonic::metadata::GRPC_CONTENT_TYPE, + ); + Ok(response) + }) + } + } + } + } + impl Clone for CurrencyConversionServer { + fn clone(&self) -> Self { + let inner = self.inner.clone(); + Self { + inner, + accept_compression_encodings: self.accept_compression_encodings, + send_compression_encodings: self.send_compression_encodings, + max_decoding_message_size: self.max_decoding_message_size, + max_encoding_message_size: self.max_encoding_message_size, + } + } + } + /// Generated gRPC service name + pub const SERVICE_NAME: &str = "komp_ac.currency_conversion.CurrencyConversion"; + impl tonic::server::NamedService for CurrencyConversionServer { + const NAME: &'static str = SERVICE_NAME; + } +} diff --git a/common/src/proto/komp_ac.table_script.rs b/common/src/proto/komp_ac.table_script.rs index de0b87b..6e30b98 100644 --- a/common/src/proto/komp_ac.table_script.rs +++ b/common/src/proto/komp_ac.table_script.rs @@ -133,6 +133,18 @@ pub struct ScriptDependency { /// Relationship table used to match the owner row to the related collection. #[prost(string, tag = "6")] pub via_table: ::prost::alloc::string::String, + /// Source currency for an fx_conversion dependency. + #[prost(string, tag = "7")] + pub from_currency: ::prost::alloc::string::String, + /// Target currency for an fx_conversion dependency. + #[prost(string, tag = "8")] + pub to_currency: ::prost::alloc::string::String, + /// Persisted exchange-rate provider/source identifier. + #[prost(string, tag = "9")] + pub source: ::prost::alloc::string::String, + /// Requested YYYY-MM-DD date, or empty for the latest stored snapshot. + #[prost(string, tag = "10")] + pub as_of_date: ::prost::alloc::string::String, } /// Identifies the active form row whose external Steel inputs must be hydrated. #[derive(Clone, PartialEq, ::prost::Message)] @@ -213,6 +225,30 @@ pub struct HydrateScriptDependenciesResponse { /// Exact related aggregate inputs declared by stored scripts. #[prost(message, repeated, tag = "2")] pub aggregates: ::prost::alloc::vec::Vec, + /// Exact resolved rates declared by the table's stored scripts. + #[prost(message, repeated, tag = "3")] + pub fx_rates: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct HydratedFxRate { + #[prost(string, tag = "1")] + pub from_currency: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub to_currency: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub source: ::prost::alloc::string::String, + #[prost(string, tag = "4")] + pub requested_as_of_date: ::prost::alloc::string::String, + #[prost(string, tag = "5")] + pub rate: ::prost::alloc::string::String, + #[prost(string, tag = "6")] + pub effective_date: ::prost::alloc::string::String, + #[prost(int64, tag = "7")] + pub rate_set_id: i64, + #[prost(int32, tag = "8")] + pub revision: i32, + #[prost(string, tag = "9")] + pub base_currency: ::prost::alloc::string::String, } /// Generated client implementations. pub mod table_script_client { @@ -423,9 +459,9 @@ pub mod table_script_client { /// /// The server derives the required inputs from stored script_dependencies; /// callers do not choose arbitrary tables or columns. Direct related-column - /// reads are grouped by related row, while related aggregates are evaluated - /// through the analytics runtime. Returned values include logical type and - /// currency metadata so the client can create correctly typed ScriptValues. + /// reads and related aggregates are evaluated through SQLx in one read-only + /// PostgreSQL snapshot. Returned values include logical type and currency + /// metadata so the client can create correctly typed ScriptValues. /// /// Current-table column values are intentionally not returned. The client /// supplies those directly from row_data so unsaved edits participate in @@ -526,9 +562,9 @@ pub mod table_script_server { /// /// The server derives the required inputs from stored script_dependencies; /// callers do not choose arbitrary tables or columns. Direct related-column - /// reads are grouped by related row, while related aggregates are evaluated - /// through the analytics runtime. Returned values include logical type and - /// currency metadata so the client can create correctly typed ScriptValues. + /// reads and related aggregates are evaluated through SQLx in one read-only + /// PostgreSQL snapshot. Returned values include logical type and currency + /// metadata so the client can create correctly typed ScriptValues. /// /// Current-table column values are intentionally not returned. The client /// supplies those directly from row_data so unsaved edits participate in diff --git a/server b/server index 8a9306c..fb2cecc 160000 --- a/server +++ b/server @@ -1 +1 @@ -Subproject commit 8a9306cd000990ba3dda5daaf63d84ad776ecf4a +Subproject commit fb2cecc1397e04477582310fd5b5afdaf6b249da