1#![cfg_attr(test, allow(clippy::unwrap_used))]
3
4#![forbid(unsafe_code)]
117
118use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque};
119use std::future::Future;
120use std::num::NonZeroU32;
121use std::process;
122use std::sync::Arc;
123use std::time::Duration;
124
125use asupersync::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
126use asupersync::net::TcpStream;
127use asupersync::runtime::{reactor, Runtime, RuntimeBuilder};
128use asupersync::sync::Mutex as AsyncMutex;
129use asupersync::{time, Cx};
130use oracledb_protocol::thin::aq::{
131 build_aq_array_deq_payload, build_aq_array_enq_payload, build_aq_deq_payload,
132 build_aq_enq_payload, parse_aq_array_response_with_limits, parse_aq_deq_response_with_limits,
133 parse_aq_enq_response_with_limits, AqArrayResult, AqDeqOptions, AqDeqResult, AqEnqOptions,
134 AqMsgProps, AqQueueDesc,
135};
136use oracledb_protocol::thin::{
137 adjust_refetch_metadata, build_auth_phase_one_payload,
138 build_auth_phase_two_payload_with_proxy_with_seq,
139 build_auth_phase_two_token_payload_with_pop_and_proxy, build_begin_pipeline_piggyback,
140 build_change_password_payload_with_seq, build_connect_packet_payload, build_data_types_payload,
141 build_define_fetch_payload_with_seq, build_end_pipeline_payload_with_seq,
142 build_execute_payload_with_bind_rows_and_options_with_seq, build_fast_auth_phase_one_payload,
143 build_fast_auth_token_payload_with_pop_and_proxy, build_fetch_payload_with_seq,
144 build_function_payload_with_seq, build_function_payload_with_seq_and_token,
145 build_lob_create_temp_payload_with_seq, build_lob_free_temp_payload_with_seq,
146 build_lob_read_payload_with_seq, build_lob_trim_payload_with_seq,
147 build_lob_write_payload_with_seq, build_protocol_negotiation_payload,
148 classic_connect_response_is_complete, connect_data_fits_inline, parse_accept_payload,
149 parse_auth_response_with_limits, parse_define_fetch_response_borrowed_with_limits,
150 parse_define_fetch_response_with_context_and_limits,
151 parse_fetch_response_with_context_and_limits, parse_lob_create_temp_response_with_limits,
152 parse_lob_free_temp_response_with_limits, parse_lob_read_response_with_limits,
153 parse_lob_trim_response_with_limits, parse_lob_write_response_with_limits,
154 parse_plain_function_response_with_limits, parse_query_response_borrowed_with_limits,
155 parse_query_response_with_binds_options_columns_and_limits,
156 parse_tpc_txn_switch_response_with_limits, AuthResponse, BindValue, BorrowedFetchResult,
157 ClientCapabilities, ColumnMetadata, CursorValue, ExecuteOptions, LobReadResult, QueryResult,
158 QueryValueRef, SessionlessTxnState, TokenPop, TpcChangeStateResponse, TpcSwitchResponse,
159 TpcXid, TNS_DATA_FLAGS_BEGIN_PIPELINE, TNS_DATA_FLAGS_END_OF_REQUEST, TNS_FUNC_COMMIT,
160 TNS_FUNC_LOGOFF, TNS_FUNC_PING, TNS_FUNC_ROLLBACK, TNS_MSG_TYPE_END_OF_RESPONSE,
161 TNS_MSG_TYPE_FLUSH_OUT_BINDS, TNS_PACKET_FLAG_REDIRECT, TNS_PACKET_TYPE_ACCEPT,
162 TNS_PACKET_TYPE_CONNECT, TNS_PACKET_TYPE_DATA, TNS_PACKET_TYPE_REDIRECT,
163 TNS_PACKET_TYPE_REFUSE, TNS_PACKET_TYPE_RESEND, TNS_PIPELINE_MODE_ABORT_ON_ERROR,
164 TNS_PIPELINE_MODE_CONTINUE_ON_ERROR, TNS_TPC_TXN_ABORT, TNS_TPC_TXN_COMMIT, TNS_TPC_TXN_DETACH,
165 TNS_TPC_TXN_POST_DETACH, TNS_TPC_TXN_PREPARE, TNS_TPC_TXN_START, TNS_TPC_TXN_STATE_ABORTED,
166 TNS_TPC_TXN_STATE_COMMITTED, TNS_TPC_TXN_STATE_FORGOTTEN, TNS_TPC_TXN_STATE_PREPARE,
167 TNS_TPC_TXN_STATE_READ_ONLY, TNS_TPC_TXN_STATE_REQUIRES_COMMIT, TPC_TXN_FLAGS_NEW,
168 TPC_TXN_FLAGS_RESUME, TPC_TXN_FLAGS_SESSIONLESS,
169};
170use oracledb_protocol::thin::{
171 build_notify_payload_with_seq, build_subscribe_payload_with_seq,
172 check_notification_header_with_limits, parse_subscribe_response_with_limits,
173 try_parse_oac_record_with_limits, NotificationRecord, SubscribeResult, TNS_SUBSCR_OP_REGISTER,
174 TNS_SUBSCR_OP_UNREGISTER,
175};
176use oracledb_protocol::thin::{
177 build_sessionless_piggyback, build_tpc_change_state_payload_with_seq_and_version,
178 build_tpc_switch_payload_with_seq_and_version, build_tpc_txn_switch_payload_with_seq,
179 parse_tpc_change_state_response_with_limits, parse_tpc_switch_response_with_limits,
180};
181use oracledb_protocol::thin::{TNS_AQ_ARRAY_DEQ, TNS_AQ_ARRAY_ENQ};
182use oracledb_protocol::wire::{encode_packet, PacketLengthWidth, ProtocolLimits};
183use oracledb_protocol::{
184 net::{
185 connectstring::{
186 Address, AddressList, Description, Descriptor, DEFAULT_SDU as DSN_DEFAULT_SDU,
187 },
188 EasyConnect, Protocol as NetProtocol,
189 },
190 ClientIdentity,
191};
192
193const PYTHON_ORACLEDB_COMPAT_VERSION_NUM: u32 = 0x0400_1000;
194const DEFAULT_SDU: usize = 8192;
195
196const MAX_CONNECT_RESEND_ROUNDS: u8 = 8;
200
201const MAX_CONNECT_REDIRECT_ROUNDS: u8 = 8;
206
207fn tns_packet_type_name(packet_type: u8) -> &'static str {
210 match packet_type {
211 1 => "CONNECT",
212 2 => "ACCEPT",
213 3 => "ACK",
214 4 => "REFUSE",
215 5 => "REDIRECT",
216 6 => "DATA",
217 7 => "NULL",
218 9 => "ABORT",
219 11 => "RESEND",
220 12 => "MARKER",
221 13 => "ATTENTION",
222 14 => "CONTROL",
223 _ => "unknown",
224 }
225}
226const TNS_DATA_PACKET_OVERHEAD: usize = 10;
227
228pub use oracledb_protocol as protocol;
229
230pub const VERSION: &str = env!("CARGO_PKG_VERSION");
238
239mod fetch_profile {
251 use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
252 use std::sync::OnceLock;
253
254 static READ_NS: AtomicU64 = AtomicU64::new(0);
255 static DECODE_NS: AtomicU64 = AtomicU64::new(0);
256 static ENABLED: OnceLock<bool> = OnceLock::new();
257 static FORCE: AtomicBool = AtomicBool::new(false);
260
261 #[inline]
262 pub(crate) fn enabled() -> bool {
263 FORCE.load(Ordering::Relaxed)
264 || *ENABLED.get_or_init(|| std::env::var_os("ORACLEDB_PROFILE_FETCH").is_some())
265 }
266
267 #[inline]
268 pub(crate) fn add_read(ns: u64) {
269 READ_NS.fetch_add(ns, Ordering::Relaxed);
270 }
271
272 #[inline]
273 pub(crate) fn add_decode(ns: u64) {
274 DECODE_NS.fetch_add(ns, Ordering::Relaxed);
275 }
276
277 pub(crate) fn snapshot() -> (u64, u64) {
278 (
279 READ_NS.load(Ordering::Relaxed),
280 DECODE_NS.load(Ordering::Relaxed),
281 )
282 }
283
284 pub(crate) fn reset() {
285 READ_NS.store(0, Ordering::Relaxed);
286 DECODE_NS.store(0, Ordering::Relaxed);
287 }
288
289 pub(crate) fn set_force(on: bool) {
290 FORCE.store(on, Ordering::Relaxed);
291 }
292}
293
294pub fn fetch_profile_read_decode_ns() -> (u64, u64) {
302 fetch_profile::snapshot()
303}
304
305pub fn fetch_profile_reset() {
307 fetch_profile::reset();
308}
309
310pub fn fetch_profile_arm(on: bool) {
313 fetch_profile::set_force(on);
314}
315
316#[cfg(feature = "arrow")]
317#[path = "arrow/mod.rs"]
318pub mod arrow;
319mod cursor_logic;
323mod iam_pop;
324#[macro_use]
329mod obs;
330pub(crate) mod lob_stream;
333pub mod pool;
334mod recovery;
335mod request;
336pub mod retry;
338mod routine;
339mod row_stream;
342mod rows;
343pub(crate) mod shape_cache;
347#[cfg(feature = "soda")]
348pub mod soda;
349mod sql_convert;
350pub(crate) mod tls;
351pub use tls::{resolve_wallet, WalletFile, WalletResolution};
357pub mod transport;
358
359#[cfg(all(test, feature = "cassette"))]
365mod version_cassettes;
366
367#[cfg(feature = "tracing")]
371#[doc(hidden)]
372pub use tracing as __tracing;
373
374#[cfg(not(feature = "tracing"))]
377#[doc(hidden)]
378pub use obs::ObsSpanGuard;
379
380pub use cursor_logic::{
381 bind_rows_need_iterative_plsql, ExecutemanyManager, ExecutemanyManagerError,
382};
383
384pub use request::{Batch, BatchRows, Execute, Query, Registration, Scroll};
385use request::{DeadlineExpiry, QueryDeadline};
386pub use row_stream::OwnedRowStream;
387pub use rows::{
388 BatchError, BatchOutcome, BlockingRows, ExecuteOutcome, OutBinds, RegistrationOutcome,
389 ReturningRows, Row, Rows,
390};
391
392pub use routine::{OutType, RoutineCall, RoutineOutcome};
393
394pub use shape_cache::{ColumnShape, ShapeObservation, StatementShapeCache};
395
396pub use lob_stream::{ClobReader, LobReader, LobWriter};
397
398pub use sql_convert::{
399 check_bind_rows, check_positional_binds, declared_bind_count, BindError, ConversionError,
400 FromRow, FromSql, IntoBinds, Params, QueryResultExt, ToSql, TypedRow,
401};
402
403#[cfg(feature = "derive")]
424pub use oracledb_derive::FromRow;
425
426pub mod prelude {
460 pub use crate::protocol::thin::{BindValue, QueryValue};
461 pub use crate::protocol::ClientIdentity;
462 pub use crate::{
463 params, BlockingConnection, ConnectOptions, Connection, FromRow, Params, QueryResultExt,
464 };
465}
466
467use recovery::{
468 cancel_disposition, classify_recovery_result, decode_server_version_number,
469 observe_cancellation_between_round_trips, post_sync_protocol_error_disposition,
470 protocol_error_is_session_dead, protocol_error_kind, protocol_error_offset,
471 protocol_error_ora_code, run_recovery_without_current_cx,
472 server_version_number_uses_extended_layout, CancelDisposition, PostSyncProtocolDisposition,
473 RecoveryWireAction, SessionRecovery, SessionRecoveryPhase, CONNECTION_LOST_ORA_CODES,
474 SESSION_DEAD_ORA_CODES, TRANSIENT_ORA_CODES,
475};
476
477use transport::{Connector, WireTransport};
478
479type DriverConnector = transport::OracleConnector;
480type DriverTransport = <DriverConnector as Connector>::Transport;
481type SharedWriteHalf<T = DriverTransport> = Arc<AsyncMutex<<T as WireTransport>::Write>>;
482type DriverCore = ConnectionCore<DriverTransport>;
483
484struct TokenAuthentication<'a> {
485 user: &'a str,
486 token: &'a str,
487 driver_name: &'a str,
488 version_num: u32,
489 connect_string: &'a str,
490 edition: Option<&'a str>,
491 pop: Option<TokenPop<'a>>,
494 proxy_user: Option<&'a str>,
497}
498
499#[derive(Debug)]
500struct ConnectionCore<T: WireTransport> {
501 read: Option<T::Read>,
502 write: SharedWriteHalf<T>,
503 recovery: Arc<SessionRecovery>,
504 protocol_limits: ProtocolLimits,
505 inactivity_timeout: Option<Duration>,
510 classic_framing: bool,
516}
517
518impl<T: WireTransport> ConnectionCore<T> {
519 fn from_halves(read: T::Read, write: T::Write, write_name: &'static str) -> Self {
520 Self {
521 read: Some(read),
522 write: Arc::new(AsyncMutex::with_name(write_name, write)),
523 recovery: Arc::new(SessionRecovery::new()),
524 protocol_limits: ProtocolLimits::DEFAULT,
525 inactivity_timeout: None,
526 classic_framing: false,
527 }
528 }
529
530 fn set_classic_framing(&mut self, classic: bool) {
534 self.classic_framing = classic;
535 }
536
537 fn set_protocol_limits(&mut self, limits: ProtocolLimits) -> Result<()> {
538 self.protocol_limits = limits.validate()?;
539 Ok(())
540 }
541
542 fn set_inactivity_timeout(&mut self, timeout: Option<Duration>) {
545 self.inactivity_timeout = timeout;
546 }
547
548 fn read_mut(&mut self) -> Result<&mut T::Read> {
549 self.read.as_mut().ok_or_else(|| {
550 Error::ConnectionClosed("connection read half unavailable during recovery".into())
551 })
552 }
553
554 fn take_read(&mut self) -> Result<T::Read> {
555 self.read.take().ok_or_else(|| {
556 Error::ConnectionClosed("connection read half unavailable during recovery".into())
557 })
558 }
559
560 fn write_handle(&self) -> SharedWriteHalf<T> {
561 Arc::clone(&self.write)
562 }
563
564 async fn write_all(&self, cx: &Cx, packet: &[u8]) -> Result<()> {
565 write_all_shared(cx, &self.write, packet).await
566 }
567
568 async fn shutdown_write(&self, cx: &Cx) -> Result<()> {
569 shutdown_write_shared(cx, &self.write).await
570 }
571
572 async fn send_data_packet(&self, cx: &Cx, payload: &[u8], sdu: usize) -> Result<()> {
573 send_data_packet_shared(cx, &self.write, payload, sdu).await
574 }
575
576 async fn send_connect_data_packet(&self, cx: &Cx, payload: &[u8], sdu: usize) -> Result<()> {
580 send_data_packet_shared_with_width(
581 cx,
582 &self.write,
583 payload,
584 sdu,
585 PacketLengthWidth::Legacy16,
586 )
587 .await
588 }
589
590 async fn send_data_packet_with_flags(
591 &self,
592 cx: &Cx,
593 payload: &[u8],
594 sdu: usize,
595 first_packet_flags: u16,
596 last_packet_flags: u16,
597 ) -> Result<()> {
598 send_data_packet_shared_with_flags(
599 cx,
600 &self.write,
601 payload,
602 sdu,
603 first_packet_flags,
604 last_packet_flags,
605 )
606 .await
607 }
608
609 async fn read_packet(&mut self, width: PacketLengthWidth) -> Result<IncomingPacket> {
610 let limits = self.protocol_limits;
611 let inactivity = self.inactivity_timeout;
612 let result = {
613 let mut read = InactivityRead::new(self.read_mut()?, inactivity);
614 read_packet_with_limits(&mut read, width, limits).await
615 };
616 self.note_post_sync_result(map_inactivity_timeout(result))
617 }
618
619 async fn read_data_response(&mut self, cx: &Cx) -> Result<Vec<u8>> {
620 let write = Arc::clone(&self.write);
621 let limits = self.protocol_limits;
622 let inactivity = self.inactivity_timeout;
623 let result = {
624 let mut read = InactivityRead::new(self.read_mut()?, inactivity);
625 read_data_response_with_limits(&mut read, cx, &write, limits).await
626 };
627 self.note_post_sync_result(map_inactivity_timeout(result))
628 }
629
630 async fn read_classic_data_response(&mut self, cx: &Cx) -> Result<Vec<u8>> {
636 let write = Arc::clone(&self.write);
637 let limits = self.protocol_limits;
638 let inactivity = self.inactivity_timeout;
639 let result = {
640 let mut read = InactivityRead::new(self.read_mut()?, inactivity);
641 read_classic_data_response_with_limits(&mut read, cx, &write, limits).await
642 };
643 self.note_post_sync_result(map_inactivity_timeout(result))
644 }
645
646 async fn authenticate_classic_token(
650 &mut self,
651 cx: &Cx,
652 token_auth: TokenAuthentication<'_>,
653 sdu: usize,
654 ) -> Result<(AuthResponse, ClientCapabilities)> {
655 let protocol_limits = self.protocol_limits;
656 let protocol_payload = build_protocol_negotiation_payload()?;
660 trace_connect_step("send protocol negotiation (classic token)");
661 self.send_data_packet(cx, &protocol_payload, sdu).await?;
662 trace_connect_step("read protocol negotiation");
663 let response = self.read_classic_data_response(cx).await?;
664 trace_connect_bytes("protocol negotiation response", &response);
665 let negotiated = parse_auth_response_with_limits(&response, protocol_limits)?;
666
667 let data_types_payload = build_data_types_payload()?;
668 trace_connect_step("send data types (classic token)");
669 self.send_data_packet(cx, &data_types_payload, sdu).await?;
670 trace_connect_step("read data types");
671 let response = self.read_classic_data_response(cx).await?;
672 trace_connect_bytes("data types response", &response);
673 parse_auth_response_with_limits(&response, protocol_limits)?;
674
675 let auth_payload = build_auth_phase_two_token_payload_with_pop_and_proxy(
676 token_auth.user,
677 token_auth.token,
678 token_auth.driver_name,
679 token_auth.version_num,
680 token_auth.connect_string,
681 token_auth.edition,
682 token_auth.pop,
683 token_auth.proxy_user,
684 )?;
685 trace_connect_step("send AUTH token (classic phase two)");
686 self.send_data_packet(cx, &auth_payload, sdu).await?;
687 trace_connect_step("read AUTH token response");
688 let response = self.read_classic_data_response(cx).await?;
689 trace_connect_bytes("AUTH token response", &response);
690 let auth = parse_auth_response_with_limits(&response, protocol_limits)?;
691 let capabilities = negotiated
692 .capabilities
693 .or(auth.capabilities)
694 .unwrap_or_default();
695 Ok((auth, capabilities))
696 }
697
698 async fn read_data_response_probed(
710 &mut self,
711 cx: &Cx,
712 classic: bool,
713 probe: impl Fn(&[u8]) -> bool,
714 ) -> Result<Vec<u8>> {
715 if !classic {
716 return self.read_data_response(cx).await;
717 }
718 let write = Arc::clone(&self.write);
719 let limits = self.protocol_limits;
720 let inactivity = self.inactivity_timeout;
721 let result = {
722 let mut read = InactivityRead::new(self.read_mut()?, inactivity);
723 read_classic_data_response_probed_with_limits(&mut read, cx, &write, &probe, limits)
724 .await
725 };
726 self.note_post_sync_result(map_inactivity_timeout(result))
727 }
728
729 async fn read_session_close_response(
737 &mut self,
738 cx: &Cx,
739 classic: bool,
740 probe: impl Fn(&[u8]) -> bool,
741 ) -> Result<SessionCloseDisposition> {
742 let write = Arc::clone(&self.write);
743 let limits = self.protocol_limits;
744 let inactivity = self.inactivity_timeout;
745 let result = {
746 let mut read = InactivityRead::new(self.read_mut()?, inactivity);
747 read_session_close_response_with_limits(&mut read, cx, &write, classic, &probe, limits)
748 .await
749 };
750 self.note_post_sync_result(map_inactivity_timeout(result))
751 }
752
753 async fn finish_session_close(
761 &mut self,
762 cx: &Cx,
763 classic: bool,
764 probe: impl Fn(&[u8]) -> bool,
765 ) -> Result<()> {
766 if let Ok(response) = time::timeout(
767 time::wall_now(),
768 Duration::from_secs(5),
769 self.read_session_close_response(cx, classic, probe),
770 )
771 .await
772 {
773 if response? == SessionCloseDisposition::PeerHardClosed {
774 return Ok(());
775 }
776 }
777 let eof = encode_packet(
778 TNS_PACKET_TYPE_DATA,
779 0,
780 Some(oracledb_protocol::thin::TNS_DATA_FLAGS_EOF),
781 &[],
782 PacketLengthWidth::Large32,
783 )?;
784 self.write_all(cx, &eof).await?;
785 let _ = self.shutdown_write(cx).await;
786 Ok(())
787 }
788
789 async fn read_data_response_flushing_out_binds_probed(
794 &mut self,
795 cx: &Cx,
796 sdu: usize,
797 classic: bool,
798 probe: impl Fn(&[u8]) -> bool,
799 ) -> Result<Vec<u8>> {
800 if !classic {
801 return self.read_data_response_flushing_out_binds(cx, sdu).await;
802 }
803 let write = Arc::clone(&self.write);
804 let limits = self.protocol_limits;
805 let inactivity = self.inactivity_timeout;
806 let result = {
807 let mut read = InactivityRead::new(self.read_mut()?, inactivity);
808 read_classic_data_response_flushing_out_binds_probed_with_limits(
809 &mut read, cx, &write, sdu, &probe, limits,
810 )
811 .await
812 };
813 self.note_post_sync_result(map_inactivity_timeout(result))
814 }
815
816 async fn read_data_response_boundary(
817 &mut self,
818 cx: &Cx,
819 in_pipeline: bool,
820 ) -> Result<DataResponse> {
821 let write = Arc::clone(&self.write);
822 let limits = self.protocol_limits;
823 let inactivity = self.inactivity_timeout;
824 let result = {
825 let mut read = InactivityRead::new(self.read_mut()?, inactivity);
826 read_data_response_boundary_with_limits(&mut read, cx, &write, in_pipeline, limits)
827 .await
828 };
829 self.note_post_sync_result(map_inactivity_timeout(result))
830 }
831
832 async fn read_data_response_flushing_out_binds(
833 &mut self,
834 cx: &Cx,
835 sdu: usize,
836 ) -> Result<Vec<u8>> {
837 let write = Arc::clone(&self.write);
838 let limits = self.protocol_limits;
839 let inactivity = self.inactivity_timeout;
840 let result = {
841 let mut read = InactivityRead::new(self.read_mut()?, inactivity);
842 read_data_response_flushing_out_binds_with_limits(&mut read, cx, &write, sdu, limits)
843 .await
844 };
845 self.note_post_sync_result(map_inactivity_timeout(result))
846 }
847
848 fn note_post_sync_result<U>(&self, result: Result<U>) -> Result<U> {
849 if let Err(Error::Protocol(err)) = &result {
850 if post_sync_protocol_error_disposition(err) == PostSyncProtocolDisposition::Dead {
851 self.recovery.mark_dead();
852 }
853 }
854 result
855 }
856
857 fn break_and_drain_wire(&mut self, recovery_timeout: Duration) -> Result<()> {
858 self.run_recovery_drain(RecoveryWireAction::BreakAndDrain, recovery_timeout)
859 }
860
861 fn cancel_and_drain_wire(&mut self, recovery_timeout: Duration) -> Result<()> {
862 self.run_recovery_drain(RecoveryWireAction::BreakAndDrain, recovery_timeout)
863 }
864
865 fn drain_cancel_wire(&mut self, recovery_timeout: Duration) -> Result<()> {
866 self.run_recovery_drain(RecoveryWireAction::DrainCancel, recovery_timeout)
867 }
868
869 fn run_recovery_drain(
870 &mut self,
871 action: RecoveryWireAction,
872 recovery_timeout: Duration,
873 ) -> Result<()> {
874 let read = self.take_read()?;
875 let write = Arc::clone(&self.write);
876 let limits = self.protocol_limits;
877 let classic = self.classic_framing;
878 let thread = std::thread::Builder::new()
879 .name("oracledb-recovery-drain".to_string())
880 .spawn(move || {
881 let mut read = read;
882 let result = run_recovery_without_current_cx(
883 &mut read,
884 &write,
885 action,
886 recovery_timeout,
887 limits,
888 classic,
889 );
890 (read, result)
891 })
892 .map_err(|err| {
893 Error::ConnectionClosed(format!("failed to start recovery drain thread: {err}"))
894 })?;
895
896 match thread.join() {
897 Ok((read, result)) => {
898 self.read = Some(read);
899 result
900 }
901 Err(_) => Err(Error::ConnectionClosed(
902 "recovery drain thread panicked".to_string(),
903 )),
904 }
905 }
906}
907
908pub fn render_caret(sql: &str, offset: usize, headline: &str) -> String {
926 let chars: Vec<char> = sql.chars().collect();
927 let total = chars.len();
928 let target = offset.saturating_sub(1).min(total);
930
931 let mut line_start = 0usize;
932 let mut line_no = 1usize;
933 let mut col = 0usize;
934 for (i, &c) in chars.iter().enumerate() {
935 if i == target {
936 break;
937 }
938 if c == '\n' {
939 line_no += 1;
940 line_start = i + 1;
941 col = 0;
942 } else {
943 col += 1;
944 }
945 }
946
947 let mut line_end = line_start;
948 while line_end < total && chars[line_end] != '\n' {
949 line_end += 1;
950 }
951 let line: String = chars[line_start..line_end].iter().collect();
952 let gutter = line_no.to_string();
953 let pad = " ".repeat(gutter.len());
954 let caret_indent = " ".repeat(col);
955 format!("{headline}\n{pad} |\n{gutter} | {line}\n{pad} | {caret_indent}^")
956}
957
958#[derive(Debug, Clone, Default, PartialEq, Eq)]
961pub struct DbmsOutput {
962 lines: Vec<String>,
964 line_count: usize,
966 char_count: usize,
968 truncated: bool,
971}
972
973impl DbmsOutput {
974 pub fn new(lines: Vec<String>, truncated: bool) -> Self {
975 let line_count = lines.len();
976 let char_count = lines.iter().map(|line| line.chars().count()).sum();
977 Self {
978 lines,
979 line_count,
980 char_count,
981 truncated,
982 }
983 }
984
985 pub fn lines(&self) -> &[String] {
986 &self.lines
987 }
988
989 pub fn line_count(&self) -> usize {
990 self.line_count
991 }
992
993 pub fn char_count(&self) -> usize {
994 self.char_count
995 }
996
997 pub fn truncated(&self) -> bool {
998 self.truncated
999 }
1000
1001 pub fn into_lines(self) -> Vec<String> {
1002 self.lines
1003 }
1004}
1005
1006#[derive(Debug, Clone, PartialEq, Eq)]
1008pub struct ObjectAttribute {
1009 pub name: String,
1011 pub type_name: String,
1014 pub type_owner: Option<String>,
1017}
1018
1019#[derive(Debug, Clone, PartialEq, Eq)]
1022pub struct CollectionElement {
1023 pub type_name: String,
1026 pub type_owner: Option<String>,
1029}
1030
1031#[derive(Debug, Clone, PartialEq, Eq)]
1035pub struct ObjectType {
1036 pub schema: String,
1038 pub name: String,
1040 pub attributes: Vec<ObjectAttribute>,
1042 pub collection_element: Option<CollectionElement>,
1045}
1046
1047#[derive(Debug, Clone, Default, PartialEq)]
1052pub struct DecodedObject {
1053 type_schema: String,
1055 type_name: String,
1057 attributes: Vec<(String, Option<oracledb_protocol::thin::QueryValue>)>,
1060 elements: Option<Vec<Option<oracledb_protocol::thin::QueryValue>>>,
1063}
1064
1065impl DecodedObject {
1066 pub fn object(
1067 type_schema: impl Into<String>,
1068 type_name: impl Into<String>,
1069 attributes: Vec<(String, Option<oracledb_protocol::thin::QueryValue>)>,
1070 ) -> Self {
1071 Self {
1072 type_schema: type_schema.into(),
1073 type_name: type_name.into(),
1074 attributes,
1075 elements: None,
1076 }
1077 }
1078
1079 pub fn collection(
1080 type_schema: impl Into<String>,
1081 type_name: impl Into<String>,
1082 elements: Vec<Option<oracledb_protocol::thin::QueryValue>>,
1083 ) -> Self {
1084 Self {
1085 type_schema: type_schema.into(),
1086 type_name: type_name.into(),
1087 attributes: Vec::new(),
1088 elements: Some(elements),
1089 }
1090 }
1091
1092 pub fn type_schema(&self) -> &str {
1093 &self.type_schema
1094 }
1095
1096 pub fn type_name(&self) -> &str {
1097 &self.type_name
1098 }
1099
1100 pub fn attributes(&self) -> &[(String, Option<oracledb_protocol::thin::QueryValue>)] {
1101 &self.attributes
1102 }
1103
1104 pub fn elements(&self) -> Option<&[Option<oracledb_protocol::thin::QueryValue>]> {
1105 self.elements.as_deref()
1106 }
1107
1108 pub fn is_collection(&self) -> bool {
1109 self.elements.is_some()
1110 }
1111
1112 pub fn into_attributes(self) -> Vec<(String, Option<oracledb_protocol::thin::QueryValue>)> {
1113 self.attributes
1114 }
1115
1116 pub fn into_elements(self) -> Option<Vec<Option<oracledb_protocol::thin::QueryValue>>> {
1117 self.elements
1118 }
1119}
1120
1121pub fn decode_object(
1132 value: &oracledb_protocol::thin::ObjectValue,
1133 ty: &ObjectType,
1134) -> Result<DecodedObject> {
1135 use oracledb_protocol::thin::DbObjectPackedReader;
1136 use oracledb_protocol::wire::{BoundedReader, ProtocolLimits};
1137 let mut reader = DbObjectPackedReader::new(&value.packed_data);
1138 reader
1139 .limits()
1140 .check_response_bytes(value.packed_data.len())?;
1141 reader.read_header()?;
1142
1143 if let Some(elem) = &ty.collection_element {
1146 if elem.type_owner.is_some() {
1147 return Err(oracledb_protocol::ProtocolError::UnsupportedFeature(
1148 "collection of nested object/collection elements is not decodable yet",
1149 )
1150 .into());
1151 }
1152 let _collection_flags = reader.read_u8()?;
1153 let num_elements = reader.read_length()?;
1154 reader.limits().check_object_elements(num_elements)?;
1155 let mut elements: Vec<Option<oracledb_protocol::thin::QueryValue>> =
1161 reader.with_capacity_limited(num_elements, 1, ProtocolLimits::check_object_elements)?;
1162 for _ in 0..num_elements {
1163 let decoded = match reader.read_value_bytes()? {
1164 None => None,
1165 Some(bytes) => Some(decode_object_scalar(&elem.type_name, bytes)?),
1166 };
1167 elements.push(decoded);
1168 }
1169 return Ok(DecodedObject {
1170 type_schema: ty.schema.clone(),
1171 type_name: ty.name.clone(),
1172 attributes: Vec::new(),
1173 elements: Some(elements),
1174 });
1175 }
1176
1177 let mut attributes = Vec::with_capacity(ty.attributes.len());
1178 for attr in &ty.attributes {
1179 if attr.type_owner.is_some() {
1180 return Err(oracledb_protocol::ProtocolError::UnsupportedFeature(
1181 "nested object/collection attribute is not decodable yet",
1182 )
1183 .into());
1184 }
1185 let decoded = match reader.read_value_bytes()? {
1186 None => None,
1187 Some(bytes) => Some(decode_object_scalar(&attr.type_name, bytes)?),
1188 };
1189 attributes.push((attr.name.clone(), decoded));
1190 }
1191 Ok(DecodedObject {
1192 type_schema: ty.schema.clone(),
1193 type_name: ty.name.clone(),
1194 attributes,
1195 elements: None,
1196 })
1197}
1198
1199fn decode_object_scalar(
1204 type_name: &str,
1205 bytes: Vec<u8>,
1206) -> Result<oracledb_protocol::thin::QueryValue> {
1207 use oracledb_protocol::thin::{
1208 decode_datetime_value, decode_dbobject_text, decode_number_value, QueryValue,
1209 };
1210 let v = if type_name.starts_with("TIMESTAMP") {
1211 decode_datetime_value(&bytes)?
1212 } else {
1213 match type_name {
1214 "VARCHAR2" | "VARCHAR" | "CHAR" => {
1215 QueryValue::Text(decode_dbobject_text(&bytes, "DB_TYPE_VARCHAR")?)
1216 }
1217 "NVARCHAR2" | "NCHAR" => {
1218 QueryValue::Text(decode_dbobject_text(&bytes, "DB_TYPE_NCHAR")?)
1219 }
1220 "NUMBER" | "FLOAT" | "INTEGER" => decode_number_value(&bytes)?,
1221 "DATE" => decode_datetime_value(&bytes)?,
1222 "RAW" => QueryValue::Raw(bytes),
1223 _ => {
1224 return Err(oracledb_protocol::ProtocolError::UnsupportedFeature(
1225 "object attribute/element type is not decodable yet",
1226 )
1227 .into())
1228 }
1229 }
1230 };
1231 Ok(v)
1232}
1233
1234#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
1236#[non_exhaustive]
1237pub enum ErrorKind {
1238 Network,
1239 Timeout,
1240 Cancel,
1241 Protocol,
1242 Database,
1243 Conversion,
1244 Pool,
1245 ResourceLimit,
1246 Authentication,
1247}
1248
1249#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
1251pub enum ConnectionDisposition {
1252 Reusable,
1253 Dead,
1254}
1255
1256#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
1258#[non_exhaustive]
1259pub enum RetryHint {
1260 Never,
1261 RetrySameConnectionIfIdempotent,
1262 ReconnectThenRetryIfIdempotent,
1263}
1264
1265#[derive(Debug, thiserror::Error)]
1266#[non_exhaustive]
1267pub enum Error {
1268 #[error(transparent)]
1269 Protocol(#[from] oracledb_protocol::ProtocolError),
1270 #[error("I/O error: {0}")]
1271 Io(#[from] std::io::Error),
1272 #[error("asupersync runtime error: {0}")]
1273 Runtime(String),
1274 #[error(
1281 "listener redirect demands a transport protocol change (e.g. a tcps -> tcp \
1282 downgrade); refusing to follow it"
1283 )]
1284 RedirectUnsupported,
1285 #[error("listener redirect data is malformed: {0}")]
1291 InvalidRedirectData(String),
1292 #[error("listener kept redirecting the connection ({0} redirects); giving up")]
1296 ConnectRedirectLoop(u8),
1297 #[error("listener refused connection: {0}")]
1298 ListenerRefused(String),
1299 #[error("all connect addresses failed: {0}")]
1304 AllAddressesFailed(String),
1305 #[error(
1313 "use_sni=true cannot be honored: the Oracle SNI \"{0}\" is not a valid \
1314 rustls DNS name; reconnect with use_sni=false to secure the connection \
1315 with the post-handshake DN match instead"
1316 )]
1317 UnsupportedSni(String),
1318 #[error(
1319 "unexpected TNS packet type {ty} ({name})",
1320 ty = .0,
1321 name = tns_packet_type_name(*.0)
1322 )]
1323 UnexpectedPacket(u8),
1324 #[error("server kept requesting CONNECT resend ({0} rounds); giving up")]
1325 ConnectResendLoop(u8),
1326 #[error("server did not advertise fast authentication")]
1327 FastAuthRequired,
1328 #[error("server response did not contain {0}")]
1329 MissingSessionField(&'static str),
1330 #[error("call timeout of {0} ms exceeded")]
1331 CallTimeout(u32),
1332 #[error("query returned no rows")]
1333 NoRows,
1334 #[error("query returned more than one row")]
1335 TooManyRows,
1336 #[error("ORA-01013: user requested cancel of current operation")]
1346 Cancelled,
1347 #[error("DPY-4011: the database or network closed the connection: {0}")]
1356 ConnectionClosed(String),
1357 #[error("TLS/TCPS error: {0}")]
1360 Tls(String),
1361 #[error("wallet error: {0}")]
1365 Wallet(#[from] oracledb_protocol::tls::wallet::WalletError),
1366 #[error("DPY-3001: access token authentication requires a TLS (TCPS) connection")]
1372 AccessTokenRequiresTcps,
1373 #[error(
1379 "OCI IAM database-token proof-of-possession signing failed: \
1380 the bound private key is not a usable PKCS#8 RSA key"
1381 )]
1382 IamTokenProofOfPossession,
1383 #[error("{0}")]
1387 TokenSource(TokenSourceError),
1388 #[error("{0}")]
1393 UnsupportedAuthMode(UnsupportedAuthMode),
1394 #[error("{0}")]
1398 SessionlessTransaction(SessionlessError),
1399 #[error("DPY-5010: internal error: unknown transaction state {0}")]
1403 UnknownTransactionState(u32),
1404 #[error("bind validation failed: {0}")]
1406 Bind(#[from] BindError),
1407 #[error("type conversion failed: {0}")]
1411 Conversion(ConversionError),
1412 #[cfg(feature = "arrow")]
1413 #[error(transparent)]
1414 ArrowConversion(#[from] arrow::ArrowConversionError),
1415}
1416
1417pub type Result<T> = std::result::Result<T, Error>;
1418
1419pub(crate) fn duration_to_millis_saturating(duration: Duration) -> u32 {
1420 duration.as_millis().min(u128::from(u32::MAX)) as u32
1421}
1422
1423fn is_asupersync_cancellation(io_error: &std::io::Error) -> bool {
1431 io_error.kind() == std::io::ErrorKind::Interrupted && io_error.to_string() == "cancelled"
1432}
1433
1434pub type Cursor = CursorValue;
1436
1437pub trait ColumnIndex: column_index_private::Sealed {
1441 #[doc(hidden)]
1442 fn resolve(self, columns: &[ColumnMetadata]) -> std::result::Result<usize, ConversionError>;
1443}
1444
1445mod column_index_private {
1446 pub trait Sealed {}
1447}
1448
1449impl column_index_private::Sealed for usize {}
1450
1451impl ColumnIndex for usize {
1452 fn resolve(self, columns: &[ColumnMetadata]) -> std::result::Result<usize, ConversionError> {
1453 if self < columns.len() {
1454 Ok(self)
1455 } else {
1456 Err(ConversionError::OutOfRange {
1457 expected: "column index",
1458 detail: format!("no column at index {self}"),
1459 })
1460 }
1461 }
1462}
1463
1464impl column_index_private::Sealed for &str {}
1465
1466impl ColumnIndex for &str {
1467 fn resolve(self, columns: &[ColumnMetadata]) -> std::result::Result<usize, ConversionError> {
1468 columns
1469 .iter()
1470 .position(|col| col.name().eq_ignore_ascii_case(self))
1471 .ok_or_else(|| ConversionError::OutOfRange {
1472 expected: "column name",
1473 detail: format!("no column named {self:?}"),
1474 })
1475 }
1476}
1477
1478impl Error {
1505 pub fn kind(&self) -> ErrorKind {
1507 match self {
1508 Error::Protocol(err) => protocol_error_kind(err),
1509 Error::Io(_)
1510 | Error::ListenerRefused(_)
1511 | Error::AllAddressesFailed(_)
1512 | Error::ConnectionClosed(_)
1513 | Error::Tls(_)
1514 | Error::UnsupportedSni(_)
1515 | Error::Wallet(_) => ErrorKind::Network,
1516 Error::CallTimeout(_) => ErrorKind::Timeout,
1517 Error::Cancelled => ErrorKind::Cancel,
1518 Error::Conversion(_) | Error::Bind(_) => ErrorKind::Conversion,
1519 #[cfg(feature = "arrow")]
1520 Error::ArrowConversion(_) => ErrorKind::Conversion,
1521 Error::AccessTokenRequiresTcps
1522 | Error::UnsupportedAuthMode(_)
1523 | Error::IamTokenProofOfPossession
1524 | Error::TokenSource(_) => ErrorKind::Authentication,
1525 Error::RedirectUnsupported
1526 | Error::InvalidRedirectData(_)
1527 | Error::ConnectRedirectLoop(_)
1528 | Error::Runtime(_)
1529 | Error::FastAuthRequired
1530 | Error::UnexpectedPacket(_)
1531 | Error::ConnectResendLoop(_)
1532 | Error::MissingSessionField(_)
1533 | Error::SessionlessTransaction(_)
1534 | Error::UnknownTransactionState(_) => ErrorKind::Protocol,
1535 Error::NoRows | Error::TooManyRows => ErrorKind::Database,
1536 }
1537 }
1538
1539 pub fn resource_limit(&self) -> Option<oracledb_protocol::ResourceLimit> {
1542 match self {
1543 Error::Protocol(err) => err.resource_limit(),
1544 _ => None,
1545 }
1546 }
1547
1548 pub fn ora_code(&self) -> Option<i32> {
1559 match self {
1560 Error::Protocol(err) => protocol_error_ora_code(err).map(|code| code as i32),
1561 Error::Cancelled => Some(1013),
1564 _ => None,
1565 }
1566 }
1567
1568 pub fn oracle_code(&self) -> Option<i32> {
1570 self.ora_code()
1571 }
1572
1573 pub fn offset(&self) -> Option<i32> {
1578 match self {
1579 Error::Protocol(err) => protocol_error_offset(err),
1580 _ => None,
1581 }
1582 }
1583
1584 pub fn caret(&self, sql: &str) -> Option<String> {
1599 let offset = usize::try_from(self.offset()?).ok()?;
1600 let full = self.to_string();
1601 let headline = full.lines().next().unwrap_or(full.as_str());
1602 Some(render_caret(sql, offset, headline))
1603 }
1604
1605 pub fn connection_disposition(&self) -> ConnectionDisposition {
1624 match self {
1625 Error::Io(_) | Error::ConnectionClosed(_) => ConnectionDisposition::Dead,
1626 _ if self
1627 .ora_code()
1628 .is_some_and(|code| SESSION_DEAD_ORA_CODES.contains(&(code as u32))) =>
1629 {
1630 ConnectionDisposition::Dead
1631 }
1632 _ => ConnectionDisposition::Reusable,
1633 }
1634 }
1635
1636 pub fn is_connection_lost(&self) -> bool {
1637 match self {
1638 Error::Io(_) | Error::ConnectionClosed(_) => true,
1639 _ => self
1640 .ora_code()
1641 .is_some_and(|code| CONNECTION_LOST_ORA_CODES.contains(&(code as u32))),
1642 }
1643 }
1644
1645 pub fn is_transient(&self) -> bool {
1658 matches!(self, Error::CallTimeout(_) | Error::Cancelled)
1659 || self
1660 .ora_code()
1661 .is_some_and(|code| TRANSIENT_ORA_CODES.contains(&(code as u32)))
1662 }
1663
1664 pub fn retry_hint(&self) -> RetryHint {
1668 if self.is_transient() {
1669 RetryHint::RetrySameConnectionIfIdempotent
1670 } else if self.is_connection_lost() {
1671 RetryHint::ReconnectThenRetryIfIdempotent
1672 } else {
1673 RetryHint::Never
1674 }
1675 }
1676
1677 pub fn is_retryable(&self) -> bool {
1678 !matches!(self.retry_hint(), RetryHint::Never)
1679 }
1680}
1681
1682#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1685#[non_exhaustive]
1686pub enum SessionlessError {
1687 DifferingMethods,
1690 AlreadyActive,
1692 Inactive,
1694}
1695
1696impl SessionlessError {
1697 pub fn full_code(self) -> &'static str {
1699 match self {
1700 Self::DifferingMethods => "DPY-3034",
1701 Self::AlreadyActive => "DPY-3035",
1702 Self::Inactive => "DPY-3036",
1703 }
1704 }
1705
1706 pub fn message(self) -> &'static str {
1708 match self {
1709 Self::DifferingMethods => {
1710 "suspending or resuming a Sessionless Transaction can be done with \
1711 DBMS_TRANSACTION or with python-oracledb, but not both"
1712 }
1713 Self::AlreadyActive => {
1714 "suspend, commit, or rollback the current active sessionless \
1715 transaction before beginning or resuming another one"
1716 }
1717 Self::Inactive => "no Sessionless Transaction is active",
1718 }
1719 }
1720}
1721
1722impl std::fmt::Display for SessionlessError {
1723 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1724 write!(f, "{}: {}", self.full_code(), self.message())
1725 }
1726}
1727
1728fn refetch_retry_applies(err: &Error) -> bool {
1734 let message = match err {
1735 Error::Protocol(oracledb_protocol::ProtocolError::ServerError(message)) => message,
1736 Error::Protocol(oracledb_protocol::ProtocolError::ServerErrorWithRowCount {
1737 message,
1738 ..
1739 }) => message,
1740 Error::Protocol(oracledb_protocol::ProtocolError::ServerErrorInfo(details)) => {
1741 return details.code == 932 || details.code == 1007;
1744 }
1745 _ => return false,
1746 };
1747 message.starts_with("ORA-00932") || message.starts_with("ORA-01007")
1748}
1749
1750#[derive(Clone)]
1755pub struct AccessToken(String);
1756
1757const REDACTED_SECRET: &str = "***redacted***";
1758
1759impl AccessToken {
1760 pub fn new(token: impl Into<String>) -> Self {
1762 Self(token.into())
1763 }
1764
1765 pub(crate) fn expose(&self) -> &str {
1768 &self.0
1769 }
1770}
1771
1772impl std::fmt::Debug for AccessToken {
1773 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1774 f.write_str("AccessToken(")?;
1776 f.write_str(REDACTED_SECRET)?;
1777 f.write_str(")")
1778 }
1779}
1780
1781#[derive(Clone)]
1787pub struct TokenPrivateKey(String);
1788
1789impl TokenPrivateKey {
1790 pub fn new(pem: impl Into<String>) -> Self {
1792 Self(pem.into())
1793 }
1794
1795 pub(crate) fn expose(&self) -> &str {
1798 &self.0
1799 }
1800}
1801
1802impl std::fmt::Debug for TokenPrivateKey {
1803 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1804 f.write_str("TokenPrivateKey(")?;
1805 f.write_str(REDACTED_SECRET)?;
1806 f.write_str(")")
1807 }
1808}
1809
1810pub type BoxFuture<'a, T> = std::pin::Pin<Box<dyn std::future::Future<Output = T> + Send + 'a>>;
1815
1816#[derive(Clone, Copy, Eq, PartialEq)]
1825#[non_exhaustive]
1826pub enum TokenSourceError {
1827 Exec,
1830 Invalid,
1832 Timeout,
1834 Other,
1836}
1837
1838impl TokenSourceError {
1839 pub fn as_str(&self) -> &'static str {
1841 match self {
1842 Self::Exec => "exec",
1843 Self::Invalid => "invalid",
1844 Self::Timeout => "timeout",
1845 Self::Other => "other",
1846 }
1847 }
1848}
1849
1850impl std::fmt::Display for TokenSourceError {
1851 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1852 let msg = match self {
1853 Self::Exec => "token source failed to execute",
1854 Self::Invalid => "token source returned an invalid token",
1855 Self::Timeout => "token source timed out",
1856 Self::Other => "token source failed",
1857 };
1858 f.write_str(msg)
1859 }
1860}
1861
1862impl std::fmt::Debug for TokenSourceError {
1863 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1864 write!(f, "TokenSourceError::{}", {
1866 match self {
1867 Self::Exec => "Exec",
1868 Self::Invalid => "Invalid",
1869 Self::Timeout => "Timeout",
1870 Self::Other => "Other",
1871 }
1872 })
1873 }
1874}
1875
1876impl std::error::Error for TokenSourceError {}
1877
1878pub trait TokenSource: Send + Sync {
1898 fn get_token(&self) -> BoxFuture<'_, std::result::Result<String, TokenSourceError>>;
1900}
1901
1902#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
1904#[non_exhaustive]
1905pub enum AuthModeKind {
1906 Password,
1907 Proxy,
1908 External,
1909 IamToken,
1910 Kerberos,
1911 Radius,
1912}
1913
1914impl std::fmt::Display for AuthModeKind {
1915 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1916 let name = match self {
1917 Self::Password => "password",
1918 Self::Proxy => "proxy",
1919 Self::External => "external",
1920 Self::IamToken => "iam-token",
1921 Self::Kerberos => "kerberos",
1922 Self::Radius => "radius",
1923 };
1924 f.write_str(name)
1925 }
1926}
1927
1928#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
1930#[non_exhaustive]
1931pub enum AuthModeSupport {
1932 Supported,
1933 UnsupportedInThin,
1934}
1935
1936#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1938#[non_exhaustive]
1939pub struct AuthCapabilities {
1940 pub password: AuthModeSupport,
1941 pub proxy: AuthModeSupport,
1942 pub external: AuthModeSupport,
1943 pub iam_token: AuthModeSupport,
1944 pub kerberos: AuthModeSupport,
1945 pub radius: AuthModeSupport,
1946}
1947
1948impl AuthCapabilities {
1949 pub const THIN: Self = Self {
1951 password: AuthModeSupport::Supported,
1952 proxy: AuthModeSupport::Supported,
1953 external: AuthModeSupport::UnsupportedInThin,
1954 iam_token: AuthModeSupport::Supported,
1955 kerberos: AuthModeSupport::UnsupportedInThin,
1956 radius: AuthModeSupport::UnsupportedInThin,
1957 };
1958
1959 #[must_use]
1960 pub fn support(self, mode: AuthModeKind) -> AuthModeSupport {
1961 match mode {
1962 AuthModeKind::Password => self.password,
1963 AuthModeKind::Proxy => self.proxy,
1964 AuthModeKind::External => self.external,
1965 AuthModeKind::IamToken => self.iam_token,
1966 AuthModeKind::Kerberos => self.kerberos,
1967 AuthModeKind::Radius => self.radius,
1968 }
1969 }
1970}
1971
1972#[derive(Clone, Eq, PartialEq)]
1974#[non_exhaustive]
1975pub enum AuthMode {
1976 Password,
1977 Proxy,
1978 External,
1979 IamToken,
1980 Kerberos {
1981 principal: Option<String>,
1982 keytab: Option<String>,
1983 },
1984 Radius {
1985 challenge: Option<String>,
1986 },
1987}
1988
1989impl AuthMode {
1990 #[must_use]
1991 pub fn kind(&self) -> AuthModeKind {
1992 match self {
1993 Self::Password => AuthModeKind::Password,
1994 Self::Proxy => AuthModeKind::Proxy,
1995 Self::External => AuthModeKind::External,
1996 Self::IamToken => AuthModeKind::IamToken,
1997 Self::Kerberos { .. } => AuthModeKind::Kerberos,
1998 Self::Radius { .. } => AuthModeKind::Radius,
1999 }
2000 }
2001
2002 fn unsupported_in_thin(&self) -> Option<UnsupportedAuthMode> {
2003 let mode = self.kind();
2004 (AuthCapabilities::THIN.support(mode) == AuthModeSupport::UnsupportedInThin)
2005 .then_some(UnsupportedAuthMode { mode })
2006 }
2007}
2008
2009impl std::fmt::Debug for AuthMode {
2010 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2011 fn redacted(value: &Option<String>) -> Option<&'static str> {
2012 value.as_ref().map(|_| REDACTED_SECRET)
2013 }
2014
2015 match self {
2016 Self::Password => f.write_str("Password"),
2017 Self::Proxy => f.write_str("Proxy"),
2018 Self::External => f.write_str("External"),
2019 Self::IamToken => f.write_str("IamToken"),
2020 Self::Kerberos { principal, keytab } => f
2021 .debug_struct("Kerberos")
2022 .field("principal", &redacted(principal))
2023 .field("keytab", &redacted(keytab))
2024 .finish(),
2025 Self::Radius { challenge } => f
2026 .debug_struct("Radius")
2027 .field("challenge", &redacted(challenge))
2028 .finish(),
2029 }
2030 }
2031}
2032
2033#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
2035#[error("authentication mode {mode} is not supported by this thin build")]
2036pub struct UnsupportedAuthMode {
2037 mode: AuthModeKind,
2038}
2039
2040impl UnsupportedAuthMode {
2041 #[must_use]
2042 pub fn mode(&self) -> AuthModeKind {
2043 self.mode
2044 }
2045}
2046
2047pub fn parse_user_and_proxy(user: &str) -> (String, Option<String>) {
2055 if let Some(start) = user.find('[') {
2056 if user.ends_with(']') && start < user.len() - 1 {
2057 let base = user[..start].to_string();
2058 let proxy = user[start + 1..user.len() - 1].to_string();
2059 return (base, Some(proxy));
2060 }
2061 }
2062 (user.to_string(), None)
2063}
2064
2065#[derive(Clone)]
2071pub struct ConnectOptions {
2072 connect_string: String,
2075 user: String,
2077 password: String,
2079 identity: ClientIdentity,
2081 app_context: Vec<(String, String, String)>,
2084 sdu: u16,
2086 proxy_user: Option<String>,
2088 auth_mode: AuthMode,
2091 server_type_emon: bool,
2096 wallet_location: Option<String>,
2102 wallet_password: Option<String>,
2106 edition: Option<String>,
2110 ssl_server_dn_match: bool,
2113 ssl_server_cert_dn: Option<String>,
2118 use_sni: bool,
2121 access_token: Option<AccessToken>,
2126 access_token_private_key: Option<TokenPrivateKey>,
2136 token_source: Option<std::sync::Arc<dyn TokenSource>>,
2144 statement_cache_size: usize,
2151 protocol_limits: ProtocolLimits,
2153 inactivity_timeout: Option<Duration>,
2162 statement_shape_cache: Option<Arc<StatementShapeCache>>,
2171}
2172
2173impl std::fmt::Debug for ConnectOptions {
2174 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2175 let wallet_location = self.wallet_location.as_ref().map(|_| REDACTED_SECRET);
2176 let wallet_password = self.wallet_password.as_ref().map(|_| REDACTED_SECRET);
2177 let server_cert_dn = self.ssl_server_cert_dn.as_ref().map(|_| REDACTED_SECRET);
2178 f.debug_struct("ConnectOptions")
2179 .field("connect_string", &self.connect_string)
2180 .field("user", &self.full_user())
2181 .field("password", &REDACTED_SECRET)
2182 .field("identity", &self.identity)
2183 .field("app_context", &self.app_context)
2184 .field("sdu", &self.sdu)
2185 .field("proxy_user", &self.proxy_user)
2186 .field("auth_mode", &self.auth_mode)
2187 .field("server_type_emon", &self.server_type_emon)
2188 .field("wallet_location", &wallet_location)
2189 .field("wallet_password", &wallet_password)
2190 .field("edition", &self.edition)
2191 .field("ssl_server_dn_match", &self.ssl_server_dn_match)
2192 .field("ssl_server_cert_dn", &server_cert_dn)
2193 .field("use_sni", &self.use_sni)
2194 .field("access_token", &self.access_token)
2195 .field("access_token_private_key", &self.access_token_private_key)
2196 .field(
2197 "token_source",
2198 &self.token_source.as_ref().map(|_| "<token source>"),
2199 )
2200 .field("statement_cache_size", &self.statement_cache_size)
2201 .field("protocol_limits", &self.protocol_limits)
2202 .field("inactivity_timeout", &self.inactivity_timeout)
2203 .field(
2204 "statement_shape_cache",
2205 &self
2206 .statement_shape_cache
2207 .as_ref()
2208 .map(|_| "<shared statement-shape cache>"),
2209 )
2210 .finish()
2211 }
2212}
2213
2214impl ConnectOptions {
2215 pub fn new(
2225 connect_string: impl Into<String>,
2226 user: impl Into<String>,
2227 password: impl Into<String>,
2228 identity: ClientIdentity,
2229 ) -> Self {
2230 let raw_user = user.into();
2231 let (user, proxy_user) = parse_user_and_proxy(&raw_user);
2232 let auth_mode = if proxy_user.is_some() {
2233 AuthMode::Proxy
2234 } else {
2235 AuthMode::Password
2236 };
2237 Self {
2238 connect_string: connect_string.into(),
2239 user,
2240 password: password.into(),
2241 identity,
2242 app_context: Vec::new(),
2243 sdu: 8192,
2244 proxy_user,
2245 auth_mode,
2246 server_type_emon: false,
2247 wallet_location: None,
2248 wallet_password: None,
2249 ssl_server_dn_match: true,
2250 ssl_server_cert_dn: None,
2251 use_sni: false,
2252 edition: None,
2253 access_token: None,
2254 access_token_private_key: None,
2255 token_source: None,
2256 statement_cache_size: STATEMENT_CACHE_SIZE,
2257 protocol_limits: ProtocolLimits::DEFAULT,
2258 inactivity_timeout: None,
2259 statement_shape_cache: None,
2260 }
2261 }
2262
2263 pub fn external_auth(connect_string: impl Into<String>, identity: ClientIdentity) -> Self {
2268 let mut options = Self::new(connect_string, "", "", identity);
2269 options.auth_mode = AuthMode::External;
2270 options
2271 }
2272
2273 pub fn kerberos_auth(
2277 connect_string: impl Into<String>,
2278 principal: impl Into<String>,
2279 keytab: impl Into<String>,
2280 identity: ClientIdentity,
2281 ) -> Self {
2282 let mut options = Self::new(connect_string, "", "", identity);
2283 options.auth_mode = AuthMode::Kerberos {
2284 principal: Some(principal.into()),
2285 keytab: Some(keytab.into()),
2286 };
2287 options
2288 }
2289
2290 pub fn radius_auth(
2294 connect_string: impl Into<String>,
2295 challenge: impl Into<String>,
2296 identity: ClientIdentity,
2297 ) -> Self {
2298 let mut options = Self::new(connect_string, "", "", identity);
2299 options.auth_mode = AuthMode::Radius {
2300 challenge: Some(challenge.into()),
2301 };
2302 options
2303 }
2304
2305 #[must_use]
2308 pub fn with_protocol_limits(mut self, limits: ProtocolLimits) -> Self {
2309 self.protocol_limits = limits;
2310 self
2311 }
2312
2313 #[must_use]
2321 pub fn with_inactivity_timeout(mut self, timeout: Duration) -> Self {
2322 self.inactivity_timeout = Some(timeout);
2323 self
2324 }
2325
2326 #[must_use]
2333 pub fn with_statement_cache_size(mut self, size: usize) -> Self {
2334 self.statement_cache_size = size;
2335 self
2336 }
2337
2338 #[must_use]
2348 pub fn with_access_token(mut self, token: impl Into<String>) -> Self {
2349 self.access_token = Some(AccessToken::new(token));
2350 self.auth_mode = AuthMode::IamToken;
2351 self
2352 }
2353
2354 #[must_use]
2366 pub fn with_access_token_and_key(
2367 mut self,
2368 token: impl Into<String>,
2369 private_key_pkcs8_pem: impl Into<String>,
2370 ) -> Self {
2371 self.access_token = Some(AccessToken::new(token));
2372 self.access_token_private_key = Some(TokenPrivateKey::new(private_key_pkcs8_pem));
2373 self.auth_mode = AuthMode::IamToken;
2374 self
2375 }
2376
2377 #[must_use]
2393 pub fn with_token_source_and_key(
2394 mut self,
2395 source: std::sync::Arc<dyn TokenSource>,
2396 private_key_pkcs8_pem: impl Into<String>,
2397 ) -> Self {
2398 self.token_source = Some(source);
2399 self.access_token_private_key = Some(TokenPrivateKey::new(private_key_pkcs8_pem));
2400 self.auth_mode = AuthMode::IamToken;
2401 self
2402 }
2403
2404 #[must_use]
2417 pub fn with_token_source(mut self, source: std::sync::Arc<dyn TokenSource>) -> Self {
2418 self.token_source = Some(source);
2419 self.auth_mode = AuthMode::IamToken;
2420 self
2421 }
2422
2423 pub fn token_source(&self) -> Option<&std::sync::Arc<dyn TokenSource>> {
2425 self.token_source.as_ref()
2426 }
2427
2428 #[must_use]
2436 pub fn with_shared_statement_shape_cache(mut self, cache: Arc<StatementShapeCache>) -> Self {
2437 self.statement_shape_cache = Some(cache);
2438 self
2439 }
2440
2441 pub fn statement_shape_cache(&self) -> Option<&Arc<StatementShapeCache>> {
2443 self.statement_shape_cache.as_ref()
2444 }
2445
2446 #[must_use]
2451 pub fn with_external_auth(mut self) -> Self {
2452 self.auth_mode = AuthMode::External;
2453 self.user.clear();
2454 self.password.clear();
2455 self
2456 }
2457
2458 #[must_use]
2463 pub fn with_kerberos_auth(
2464 mut self,
2465 principal: impl Into<String>,
2466 keytab: impl Into<String>,
2467 ) -> Self {
2468 self.auth_mode = AuthMode::Kerberos {
2469 principal: Some(principal.into()),
2470 keytab: Some(keytab.into()),
2471 };
2472 self.user.clear();
2473 self.password.clear();
2474 self
2475 }
2476
2477 #[must_use]
2482 pub fn with_radius_auth(mut self, challenge: impl Into<String>) -> Self {
2483 self.auth_mode = AuthMode::Radius {
2484 challenge: Some(challenge.into()),
2485 };
2486 self.user.clear();
2487 self.password.clear();
2488 self
2489 }
2490
2491 #[must_use]
2497 pub fn with_edition(mut self, edition: impl Into<String>) -> Self {
2498 self.edition = Some(edition.into());
2499 self
2500 }
2501
2502 #[must_use]
2504 pub fn with_use_sni(mut self, use_sni: bool) -> Self {
2505 self.use_sni = use_sni;
2506 self
2507 }
2508
2509 #[must_use]
2512 pub fn with_wallet_location(mut self, location: impl Into<String>) -> Self {
2513 self.wallet_location = Some(location.into());
2514 self
2515 }
2516
2517 #[must_use]
2521 pub fn with_wallet_password(mut self, password: impl Into<String>) -> Self {
2522 self.wallet_password = Some(password.into());
2523 self
2524 }
2525
2526 #[must_use]
2529 pub fn with_ssl_server_dn_match(mut self, enabled: bool) -> Self {
2530 self.ssl_server_dn_match = enabled;
2531 self
2532 }
2533
2534 #[must_use]
2537 pub fn with_ssl_server_cert_dn(mut self, dn: impl Into<String>) -> Self {
2538 self.ssl_server_cert_dn = Some(dn.into());
2539 self
2540 }
2541
2542 pub fn with_server_type_emon(mut self, emon: bool) -> Self {
2546 self.server_type_emon = emon;
2547 self
2548 }
2549
2550 pub fn with_app_context(mut self, app_context: Vec<(String, String, String)>) -> Self {
2552 self.app_context = app_context;
2553 self
2554 }
2555
2556 pub fn with_proxy_user(mut self, proxy_user: Option<String>) -> Self {
2562 if proxy_user.is_some() {
2563 self.auth_mode = AuthMode::Proxy;
2564 } else if matches!(self.auth_mode, AuthMode::Proxy) {
2565 self.auth_mode = AuthMode::Password;
2566 }
2567 self.proxy_user = proxy_user;
2568 self
2569 }
2570
2571 pub fn with_sdu(mut self, sdu: u32) -> Self {
2575 let clamped = sdu.clamp(512, u32::from(u16::MAX));
2576 self.sdu = u16::try_from(clamped).unwrap_or(u16::MAX);
2577 self
2578 }
2579
2580 pub fn connect_string(&self) -> &str {
2581 &self.connect_string
2582 }
2583
2584 pub fn user(&self) -> &str {
2585 &self.user
2586 }
2587
2588 pub fn full_user(&self) -> String {
2592 match (self.user.as_str(), self.proxy_user.as_deref()) {
2593 ("", Some(proxy)) => format!("[{proxy}]"),
2594 (base, Some(proxy)) => format!("{base}[{proxy}]"),
2595 (base, None) => base.to_string(),
2596 }
2597 }
2598
2599 pub fn password(&self) -> &str {
2600 &self.password
2601 }
2602
2603 pub fn identity(&self) -> &ClientIdentity {
2604 &self.identity
2605 }
2606
2607 pub fn app_context(&self) -> &[(String, String, String)] {
2608 &self.app_context
2609 }
2610
2611 pub fn sdu(&self) -> u16 {
2612 self.sdu
2613 }
2614
2615 pub fn proxy_user(&self) -> Option<&str> {
2616 self.proxy_user.as_deref()
2617 }
2618
2619 pub fn auth_mode(&self) -> &AuthMode {
2620 &self.auth_mode
2621 }
2622
2623 pub fn auth_capabilities(&self) -> AuthCapabilities {
2624 AuthCapabilities::THIN
2625 }
2626
2627 pub fn server_type_emon(&self) -> bool {
2628 self.server_type_emon
2629 }
2630
2631 pub fn wallet_location(&self) -> Option<&str> {
2632 self.wallet_location.as_deref()
2633 }
2634
2635 pub fn wallet_password(&self) -> Option<&str> {
2636 self.wallet_password.as_deref()
2637 }
2638
2639 pub fn edition(&self) -> Option<&str> {
2640 self.edition.as_deref()
2641 }
2642
2643 pub fn ssl_server_dn_match(&self) -> bool {
2644 self.ssl_server_dn_match
2645 }
2646
2647 pub fn ssl_server_cert_dn(&self) -> Option<&str> {
2648 self.ssl_server_cert_dn.as_deref()
2649 }
2650
2651 pub fn use_sni(&self) -> bool {
2652 self.use_sni
2653 }
2654
2655 pub fn access_token(&self) -> Option<&AccessToken> {
2656 self.access_token.as_ref()
2657 }
2658
2659 pub fn statement_cache_size(&self) -> usize {
2660 self.statement_cache_size
2661 }
2662
2663 pub fn protocol_limits(&self) -> ProtocolLimits {
2664 self.protocol_limits
2665 }
2666
2667 pub fn inactivity_timeout(&self) -> Option<Duration> {
2668 self.inactivity_timeout
2669 }
2670}
2671
2672#[derive(Debug)]
2682pub struct Connection {
2683 descriptor: EasyConnect,
2684 identity: ClientIdentity,
2685 core: DriverCore,
2689 protocol_limits: ProtocolLimits,
2690 session_id: u32,
2691 serial_num: u16,
2692 server_version: Option<String>,
2693 server_version_tuple: Option<(u8, u8, u8, u8, u8)>,
2694 db_unique_name: Option<String>,
2698 capabilities: ClientCapabilities,
2699 ttc_seq_num: u8,
2700 sdu: usize,
2701 protocol_version: u16,
2705 supports_fast_auth: bool,
2710 supports_end_of_response: bool,
2711 supports_oob: bool,
2717 cursor_columns: BTreeMap<u32, Vec<ColumnMetadata>>,
2718 fetch_metadata_by_sql: HashMap<String, Vec<ColumnMetadata>>,
2723 fetch_metadata_order: VecDeque<String>,
2725 shape_cache: Arc<StatementShapeCache>,
2732 dead: bool,
2733 user: String,
2735 combo_key: Vec<u8>,
2738 statement_cache: Vec<CachedStatement>,
2745 statement_cache_size: usize,
2748 in_use_cursors: HashSet<u32>,
2756 lob_prefetch_cursors: BTreeSet<u32>,
2760 copied_cursors: HashSet<u32>,
2766 cursors_to_close: Vec<u32>,
2769 sessionless_data: Option<SessionlessData>,
2773 notification_buffer: Vec<u8>,
2778 notification_header_consumed: bool,
2782 transaction_context: Option<Vec<u8>>,
2787 txn_in_progress: bool,
2792 #[cfg(feature = "cassette")]
2802 #[allow(dead_code)]
2803 capture_guard: Option<transport::CaptureGuard>,
2804}
2805
2806struct BorrowedStreamCursorGuard<'conn> {
2814 connection: &'conn mut Connection,
2815 cursor_id: u32,
2816}
2817
2818impl<'conn> BorrowedStreamCursorGuard<'conn> {
2819 fn new(connection: &'conn mut Connection, cursor_id: u32) -> Self {
2820 Self {
2821 connection,
2822 cursor_id,
2823 }
2824 }
2825
2826 fn connection(&mut self) -> &mut Connection {
2827 self.connection
2828 }
2829
2830 fn release(mut self) {
2833 self.connection.release_cursor(self.cursor_id);
2834 self.cursor_id = 0;
2835 }
2836}
2837
2838impl Drop for BorrowedStreamCursorGuard<'_> {
2839 fn drop(&mut self) {
2840 if self.cursor_id != 0 {
2841 self.connection.close_cursor(self.cursor_id);
2842 }
2843 }
2844}
2845
2846#[derive(Clone, Debug)]
2849struct SessionlessData {
2850 transaction_id: Vec<u8>,
2851 timeout: u32,
2852 operation: u32,
2855 flags: u32,
2858 piggyback_pending: bool,
2861 started_on_server: bool,
2864}
2865
2866enum PacketRead {
2868 Appended,
2870 TimedOut,
2873 Closed,
2876}
2877
2878#[derive(Clone, Debug)]
2880#[non_exhaustive]
2881pub enum NotificationOutcome {
2882 Record(NotificationRecord),
2884 TimedOut,
2887 Closed,
2889}
2890
2891const STATEMENT_CACHE_SIZE: usize = 20;
2892
2893#[derive(Clone, Debug)]
2895pub enum PipelineRequest {
2896 #[non_exhaustive]
2897 Execute {
2898 sql: String,
2899 bind_rows: Vec<Vec<BindValue>>,
2900 prefetch_rows: u32,
2901 },
2902 Commit,
2903}
2904
2905impl PipelineRequest {
2906 pub fn execute(
2907 sql: impl Into<String>,
2908 bind_rows: Vec<Vec<BindValue>>,
2909 prefetch_rows: u32,
2910 ) -> Self {
2911 Self::Execute {
2912 sql: sql.into(),
2913 bind_rows,
2914 prefetch_rows,
2915 }
2916 }
2917
2918 pub fn commit() -> Self {
2919 Self::Commit
2920 }
2921
2922 pub fn sql(&self) -> Option<&str> {
2923 match self {
2924 Self::Execute { sql, .. } => Some(sql),
2925 Self::Commit => None,
2926 }
2927 }
2928
2929 pub fn bind_rows(&self) -> Option<&[Vec<BindValue>]> {
2930 match self {
2931 Self::Execute { bind_rows, .. } => Some(bind_rows),
2932 Self::Commit => None,
2933 }
2934 }
2935
2936 pub fn prefetch_rows(&self) -> Option<u32> {
2937 match self {
2938 Self::Execute { prefetch_rows, .. } => Some(*prefetch_rows),
2939 Self::Commit => None,
2940 }
2941 }
2942
2943 pub fn is_commit(&self) -> bool {
2944 matches!(self, Self::Commit)
2945 }
2946}
2947
2948#[derive(Debug)]
2949pub struct CancelHandle {
2950 write: SharedWriteHalf,
2951 recovery: Arc<SessionRecovery>,
2952}
2953
2954impl Connection {
2955 pub async fn connect(cx: &Cx, mut options: ConnectOptions) -> Result<Self> {
2961 observe_cancellation_between_round_trips(cx)?;
2962 let protocol_limits = options.protocol_limits.validate()?;
2963 if let Some(unsupported) = options.auth_mode.unsupported_in_thin() {
2964 return Err(Error::UnsupportedAuthMode(unsupported));
2965 }
2966 let descriptor = EasyConnect::parse(&options.connect_string)?;
2967 if (options.access_token.is_some() || options.token_source.is_some())
2978 && !descriptor.protocol.is_tls()
2979 {
2980 return Err(Error::AccessTokenRequiresTcps);
2981 }
2982 if options.access_token.is_none() {
2987 if let Some(source) = options.token_source.clone() {
2988 let token = source.get_token().await.map_err(Error::TokenSource)?;
2989 options.access_token = Some(AccessToken::new(token));
2990 }
2991 }
2992 let full_descriptor = EasyConnect::parse_descriptor(&options.connect_string)?;
2993 let primary_description = full_descriptor.first_description().clone();
2994 let connect_timeout =
2995 transport_connect_timeout_duration(primary_description.tcp_connect_timeout);
2996 let connect_timeout_ms = duration_to_millis_saturating(connect_timeout);
2997 let inactivity_timeout = options.inactivity_timeout;
3003 let keepalive_idle = keepalive_idle_from_expire_time(primary_description.expire_time);
3004 let connect_result = time::timeout(time::wall_now(), connect_timeout, async {
3005 let _span = obs_span!(
3008 "oracledb.connect",
3009 db.system = "oracle",
3010 server.address = %descriptor.host,
3011 server.port = descriptor.port as u64,
3012 db.name = %descriptor.service_name,
3013 );
3014 let token_auth = options.access_token.is_some();
3015 let descriptor_ssl_server_dn_match =
3016 effective_ssl_server_dn_match(&primary_description, &options);
3017 let descriptor_ssl_server_cert_dn = options
3018 .ssl_server_cert_dn
3019 .as_deref()
3020 .or(primary_description.security.ssl_server_cert_dn.as_deref());
3021 let identity = options.identity;
3022 let advertised_sdu =
3029 u16::try_from(resolve_effective_sdu(options.sdu, &primary_description))
3030 .unwrap_or(u16::MAX);
3031 let effective_use_sni = options.use_sni || primary_description.use_sni;
3032 let effective_wallet_location = options
3033 .wallet_location
3034 .as_deref()
3035 .or(primary_description.security.wallet_location.as_deref());
3036 let server_type = if options.server_type_emon {
3041 Some("emon")
3042 } else {
3043 None
3044 };
3045 let prepared_tls = if descriptor.protocol.is_tls() {
3062 let tls_params = tls::resolve_tls_params(
3063 &descriptor,
3064 effective_wallet_location,
3065 options.wallet_password.as_deref(),
3066 descriptor_ssl_server_dn_match,
3067 descriptor_ssl_server_cert_dn,
3068 effective_use_sni,
3069 )?;
3070 Some(tls::prepare_tls_handshake(
3075 &descriptor,
3076 server_type,
3077 &tls_params,
3078 )?)
3079 } else {
3080 None
3081 };
3082 let connector = DriverConnector::default();
3083 #[cfg(feature = "cassette")]
3092 let capture_path = transport::capture_path_from_env();
3093 #[cfg(feature = "cassette")]
3094 let capture_recorder = capture_path
3095 .as_ref()
3096 .map(|_| transport::CassetteRecorder::new());
3097 let dial = |host: String, port: u16| {
3103 let connector = &connector;
3104 let prepared_tls = prepared_tls.as_ref();
3105 #[cfg(feature = "cassette")]
3106 let capture_recorder = capture_recorder.as_ref();
3107 async move {
3108 trace_connect_step("tcp connect");
3109 let stream = TcpStream::connect_timeout((host, port), connect_timeout).await?;
3110 stream.set_nodelay(true)?;
3111 if let Some(idle) = keepalive_idle {
3117 stream.set_keepalive(Some(idle))?;
3118 }
3119 trace_connect_step("tcp connected");
3120 let halves = if let Some(prepared_tls) = prepared_tls {
3121 trace_connect_step("tls handshake");
3122 let tls_stream = tls::tls_handshake_prepared(
3129 prepared_tls,
3130 stream,
3131 tls::handshake_timeout_from_connect_timeout(connect_timeout),
3132 )
3133 .await?;
3134 trace_connect_step("tls established");
3135 #[cfg(feature = "cassette")]
3139 let _capture =
3140 capture_recorder.map(|r| transport::install_recorder_scope(r.clone()));
3141 connector.tls_split(tls_stream)
3142 } else {
3143 #[cfg(feature = "cassette")]
3144 let _capture =
3145 capture_recorder.map(|r| transport::install_recorder_scope(r.clone()));
3146 connector.plain_split(stream)
3147 };
3148 Ok::<_, TransportEstablishmentError>(halves)
3149 }
3150 };
3151 let candidates = resolve_connect_addresses(&full_descriptor, descriptor.protocol);
3162 let retry_count = primary_description.retry_count;
3163 let retry_delay = Duration::from_secs(u64::from(primary_description.retry_delay));
3164 let mut attempt_errors: Vec<String> = Vec::new();
3165 let mut connected = None;
3166 'failover: for round in 0..=retry_count {
3167 if round > 0 && !retry_delay.is_zero() {
3168 trace_connect_step("failover retry delay");
3169 let _ =
3173 time::timeout(time::wall_now(), retry_delay, std::future::pending::<()>())
3174 .await;
3175 }
3176 for candidate in &candidates {
3177 match dial(candidate.host.clone(), candidate.port).await {
3178 Ok(halves) => {
3179 connected = Some((halves, candidate.clone()));
3180 break 'failover;
3181 }
3182 Err(err) => {
3183 if err.is_terminal() {
3184 return Err(err.into_driver_error());
3185 }
3186 attempt_errors
3187 .push(format!("{}:{} ({err})", candidate.host, candidate.port));
3188 }
3189 }
3190 }
3191 }
3192 let ((read, write), active_address) = match connected {
3193 Some(pair) => pair,
3194 None => {
3195 return Err(Error::AllAddressesFailed(format!(
3196 "tried {} address(es): {}",
3197 attempt_errors.len(),
3198 attempt_errors.join("; ")
3199 )));
3200 }
3201 };
3202 let descriptor = EasyConnect {
3206 host: active_address.host,
3207 port: active_address.port,
3208 service_name: descriptor.service_name.clone(),
3209 protocol: descriptor.protocol,
3210 };
3211 let mut core = ConnectionCore::from_halves(read, write, "oracle_tcp_write");
3212 core.set_protocol_limits(protocol_limits)?;
3213 core.set_inactivity_timeout(inactivity_timeout);
3214
3215 let connect_descriptor = listener_connect_descriptor_with_server(
3216 &descriptor,
3217 &primary_description,
3218 &identity,
3219 options.server_type_emon,
3220 token_auth,
3221 descriptor_ssl_server_dn_match,
3222 descriptor_ssl_server_cert_dn,
3223 );
3224 trace_connect_value("CONNECT descriptor", &connect_descriptor);
3225 let mut connect_data = connect_descriptor;
3238 let mut packet_flags = 0u8;
3239 let mut resend_rounds = 0u8;
3240 let mut redirect_rounds = 0u8;
3241 let accept = loop {
3242 let connect_payload = build_connect_packet_payload(&connect_data, advertised_sdu)?;
3243 let packet = encode_packet(
3244 TNS_PACKET_TYPE_CONNECT,
3245 packet_flags,
3246 None,
3247 &connect_payload,
3248 PacketLengthWidth::Legacy16,
3249 )?;
3250 trace_connect_bytes("CONNECT packet", &packet);
3251 let split_connect_data = !connect_data_fits_inline(&connect_data);
3252 trace_connect_step("send CONNECT");
3253 core.write_all(cx, &packet).await?;
3254 if split_connect_data {
3255 trace_connect_step("send CONNECT descriptor (data packet)");
3256 core.send_connect_data_packet(
3257 cx,
3258 connect_data.as_bytes(),
3259 usize::from(advertised_sdu),
3260 )
3261 .await?;
3262 }
3263
3264 trace_connect_step("read ACCEPT");
3265 let reply = core.read_packet(PacketLengthWidth::Legacy16).await?;
3266 trace_connect_value(
3267 "CONNECT reply",
3268 &format!(
3269 "type={} flags=0x{:02x} payload_len={}",
3270 reply.packet_type,
3271 reply.packet_flags,
3272 reply.payload.len()
3273 ),
3274 );
3275 match reply.packet_type {
3276 TNS_PACKET_TYPE_ACCEPT => break reply,
3277 TNS_PACKET_TYPE_RESEND => {
3278 resend_rounds += 1;
3279 if resend_rounds > MAX_CONNECT_RESEND_ROUNDS {
3280 return Err(Error::ConnectResendLoop(resend_rounds));
3281 }
3282 trace_connect_step("RESEND requested; resending CONNECT");
3283 continue;
3284 }
3285 TNS_PACKET_TYPE_REDIRECT => {
3286 redirect_rounds += 1;
3287 if redirect_rounds > MAX_CONNECT_REDIRECT_ROUNDS {
3288 return Err(Error::ConnectRedirectLoop(redirect_rounds));
3289 }
3290 let redirect_data = read_redirect_data(&mut core, &reply.payload).await?;
3291 let target = parse_redirect_target(&redirect_data, descriptor.protocol)?;
3292 trace_connect_value(
3293 "REDIRECT target",
3294 &format!("{}:{}", target.host, target.port),
3295 );
3296 let (read, write) = dial(target.host, target.port)
3302 .await
3303 .map_err(TransportEstablishmentError::into_driver_error)?;
3304 core = ConnectionCore::from_halves(read, write, "oracle_tcp_write");
3305 core.set_protocol_limits(protocol_limits)?;
3306 core.set_inactivity_timeout(inactivity_timeout);
3307 connect_data = target.connect_data;
3308 packet_flags = TNS_PACKET_FLAG_REDIRECT;
3309 resend_rounds = 0;
3312 trace_connect_step("REDIRECT: reconnected; resending CONNECT");
3313 continue;
3314 }
3315 TNS_PACKET_TYPE_REFUSE => {
3316 trace_connect_step("REFUSE received");
3317 return Err(Error::ListenerRefused(
3318 String::from_utf8_lossy(&reply.payload).to_string(),
3319 ));
3320 }
3321 other => return Err(Error::UnexpectedPacket(other)),
3322 }
3323 };
3324 let accept_info = parse_accept_payload(&accept.payload)?;
3325 trace_connect_value(
3333 "ACCEPT",
3334 &format!(
3335 "sdu={} fast_auth={} end_of_response={} oob={}",
3336 accept_info.sdu,
3337 accept_info.supports_fast_auth,
3338 accept_info.supports_end_of_response,
3339 accept_info.supports_oob,
3340 ),
3341 );
3342 core.set_classic_framing(!accept_info.supports_end_of_response);
3348 let sdu = usize::try_from(accept_info.sdu)
3349 .unwrap_or(DEFAULT_SDU)
3350 .max(TNS_DATA_PACKET_OVERHEAD + 1);
3351
3352 let mut ttc_seq_num = 1;
3353 let auth_connect_string = auth_connect_descriptor(
3354 &descriptor,
3355 &primary_description,
3356 token_auth,
3357 descriptor_ssl_server_dn_match,
3358 descriptor_ssl_server_cert_dn,
3359 );
3360
3361 let (auth_two, capabilities, combo_key) = if let Some(token) = &options.access_token {
3368 if !descriptor.protocol.is_tls() {
3372 return Err(Error::AccessTokenRequiresTcps);
3373 }
3374 let pop_material = match options.access_token_private_key.as_ref() {
3382 Some(key) => {
3383 let header = iam_pop::build_signing_header(
3384 &descriptor.service_name,
3385 &descriptor.host,
3386 descriptor.port,
3387 std::time::SystemTime::now(),
3388 );
3389 let signature = iam_pop::sign_signing_header(key.expose(), &header)?;
3390 Some((header, signature))
3391 }
3392 None => None,
3393 };
3394 let pop = pop_material
3395 .as_ref()
3396 .map(|(header, signature)| TokenPop { header, signature });
3397 let (auth, capabilities) = if accept_info.supports_fast_auth {
3398 let auth_payload = build_fast_auth_token_payload_with_pop_and_proxy(
3402 &options.user,
3403 token.expose(),
3404 &identity.driver_name,
3405 PYTHON_ORACLEDB_COMPAT_VERSION_NUM,
3406 &auth_connect_string,
3407 options.edition.as_deref(),
3408 pop,
3409 options.proxy_user.as_deref(),
3410 )?;
3411 trace_connect_step("send AUTH token (fast-auth phase two)");
3412 core.send_data_packet(cx, &auth_payload, sdu).await?;
3413 trace_connect_step("read AUTH token response");
3414 let response = core.read_data_response(cx).await?;
3415 trace_connect_bytes("AUTH token response", &response);
3416 let auth = parse_auth_response_with_limits(&response, protocol_limits)?;
3417 let capabilities = auth.capabilities.unwrap_or_default();
3418 (auth, capabilities)
3419 } else {
3420 core.authenticate_classic_token(
3421 cx,
3422 TokenAuthentication {
3423 user: &options.user,
3424 token: token.expose(),
3425 driver_name: &identity.driver_name,
3426 version_num: PYTHON_ORACLEDB_COMPAT_VERSION_NUM,
3427 connect_string: &auth_connect_string,
3428 edition: options.edition.as_deref(),
3429 pop,
3430 proxy_user: options.proxy_user.as_deref(),
3431 },
3432 sdu,
3433 )
3434 .await?
3435 };
3436 (auth, capabilities, Vec::new())
3439 } else {
3440 let client_pid = process::id();
3441 let negotiated_capabilities = if accept_info.supports_fast_auth {
3449 None
3450 } else {
3451 let protocol_payload = build_protocol_negotiation_payload()?;
3452 trace_connect_step("send protocol negotiation (classic)");
3453 core.send_data_packet(cx, &protocol_payload, sdu).await?;
3454 trace_connect_step("read protocol negotiation");
3455 let response = core.read_classic_data_response(cx).await?;
3456 trace_connect_bytes("protocol negotiation response", &response);
3457 let negotiated = parse_auth_response_with_limits(&response, protocol_limits)?;
3458
3459 let data_types_payload = build_data_types_payload()?;
3460 trace_connect_step("send data types (classic)");
3461 core.send_data_packet(cx, &data_types_payload, sdu).await?;
3462 trace_connect_step("read data types");
3463 let response = core.read_classic_data_response(cx).await?;
3464 trace_connect_bytes("data types response", &response);
3465 parse_auth_response_with_limits(&response, protocol_limits)?;
3466
3467 Some(negotiated.capabilities.unwrap_or_default())
3468 };
3469 let auth_one = if accept_info.supports_fast_auth {
3470 build_fast_auth_phase_one_payload(
3471 &options.user,
3472 &identity.program,
3473 &identity.machine,
3474 &identity.osuser,
3475 &identity.terminal,
3476 client_pid,
3477 )?
3478 } else {
3479 build_auth_phase_one_payload(
3480 &options.user,
3481 &identity.program,
3482 &identity.machine,
3483 &identity.osuser,
3484 &identity.terminal,
3485 client_pid,
3486 )?
3487 };
3488 trace_connect_bytes("AUTH phase one payload", &auth_one);
3489 trace_connect_step("send AUTH phase one");
3490 core.send_data_packet(cx, &auth_one, sdu).await?;
3491 trace_connect_step("read AUTH phase one");
3492 let auth_one_response = if accept_info.supports_fast_auth {
3493 core.read_data_response(cx).await?
3494 } else {
3495 core.read_classic_data_response(cx).await?
3496 };
3497 trace_connect_bytes("AUTH phase one response", &auth_one_response);
3498 let auth_one =
3499 parse_auth_response_with_limits(&auth_one_response, protocol_limits)?;
3500 let capabilities = negotiated_capabilities
3501 .or(auth_one.capabilities)
3502 .unwrap_or_default();
3503 let verifier_type = auth_one
3504 .verifier_type
3505 .ok_or(Error::MissingSessionField("AUTH_VFR_DATA verifier type"))?;
3506 let encrypted = oracledb_protocol::crypto::generate_verifier(
3507 options.password.as_bytes(),
3508 &auth_one.session_data,
3509 verifier_type,
3510 )?;
3511 let auth_two_payload = build_auth_phase_two_payload_with_proxy_with_seq(
3512 &options.user,
3513 &encrypted,
3514 &identity.driver_name,
3515 PYTHON_ORACLEDB_COMPAT_VERSION_NUM,
3516 &auth_connect_string,
3517 next_ttc_sequence(&mut ttc_seq_num),
3518 &options.app_context,
3519 options.proxy_user.as_deref(),
3520 options.edition.as_deref(),
3521 capabilities.ttc_field_version,
3522 )?;
3523 trace_connect_bytes("AUTH phase two payload", &auth_two_payload);
3524 trace_connect_step("send AUTH phase two");
3525 core.send_data_packet(cx, &auth_two_payload, sdu).await?;
3526 trace_connect_step("read AUTH phase two");
3527 let auth_two_response = if accept_info.supports_fast_auth {
3528 core.read_data_response(cx).await?
3529 } else {
3530 core.read_classic_data_response(cx).await?
3531 };
3532 trace_connect_bytes("AUTH phase two response", &auth_two_response);
3533 let auth_two =
3534 parse_auth_response_with_limits(&auth_two_response, protocol_limits)?;
3535 oracledb_protocol::crypto::verify_server_response(
3536 &encrypted.combo_key,
3537 &auth_two.session_data,
3538 )?;
3539 (auth_two, capabilities, encrypted.combo_key)
3540 };
3541
3542 let session_id = parse_session_u32(&auth_two.session_data, "AUTH_SESSION_ID")?;
3543 let serial_num = parse_session_u16(&auth_two.session_data, "AUTH_SERIAL_NUM")?;
3544 trace_connect_value(
3549 "session established",
3550 &format!("sid={session_id} serial={serial_num}"),
3551 );
3552 let server_version = auth_two.session_data.get("AUTH_VERSION_STRING").cloned();
3553 let db_unique_name = parse_db_unique_name(&auth_two.session_data);
3554 let server_version_tuple = auth_two
3555 .session_data
3556 .get("AUTH_VERSION_NO")
3557 .and_then(|value| value.trim().parse::<u32>().ok())
3558 .map(|num| {
3559 decode_server_version_number(
3560 num,
3561 server_version_number_uses_extended_layout(capabilities.ttc_field_version),
3562 )
3563 });
3564
3565 #[cfg(feature = "cassette")]
3569 let capture_guard = match (capture_recorder, capture_path) {
3570 (Some(recorder), Some(path)) => Some(transport::CaptureGuard::new(recorder, path)),
3571 _ => None,
3572 };
3573 Ok(Self {
3574 descriptor,
3575 identity,
3576 core,
3577 protocol_limits,
3578 session_id,
3579 serial_num,
3580 server_version,
3581 server_version_tuple,
3582 db_unique_name,
3583 capabilities,
3584 ttc_seq_num,
3585 sdu,
3586 protocol_version: accept_info.protocol_version,
3587 supports_fast_auth: accept_info.supports_fast_auth,
3588 supports_end_of_response: accept_info.supports_end_of_response,
3589 supports_oob: accept_info.supports_oob,
3590 cursor_columns: BTreeMap::new(),
3591 fetch_metadata_by_sql: HashMap::new(),
3592 fetch_metadata_order: VecDeque::new(),
3593 shape_cache: options.statement_shape_cache.clone().unwrap_or_default(),
3594 dead: false,
3595 user: options.user,
3596 combo_key,
3597 statement_cache: Vec::new(),
3598 statement_cache_size: options.statement_cache_size,
3599 in_use_cursors: HashSet::new(),
3600 lob_prefetch_cursors: BTreeSet::new(),
3601 copied_cursors: HashSet::new(),
3602 cursors_to_close: Vec::new(),
3603 sessionless_data: None,
3604 notification_buffer: Vec::new(),
3605 notification_header_consumed: false,
3606 transaction_context: None,
3607 txn_in_progress: false,
3608 #[cfg(feature = "cassette")]
3609 capture_guard,
3610 })
3611 })
3612 .await;
3613 match connect_result {
3614 Ok(result) => result,
3615 Err(_) => Err(Error::CallTimeout(connect_timeout_ms)),
3616 }
3617 }
3618
3619 pub fn descriptor(&self) -> &EasyConnect {
3620 &self.descriptor
3621 }
3622
3623 pub fn host(&self) -> &str {
3627 &self.descriptor.host
3628 }
3629
3630 pub fn port(&self) -> u16 {
3632 self.descriptor.port
3633 }
3634
3635 pub fn protocol(&self) -> NetProtocol {
3638 self.descriptor.protocol
3639 }
3640
3641 pub fn identity(&self) -> &ClientIdentity {
3644 &self.identity
3645 }
3646
3647 pub fn session_id(&self) -> u32 {
3649 self.session_id
3650 }
3651
3652 pub fn serial_num(&self) -> u16 {
3654 self.serial_num
3655 }
3656
3657 pub fn server_version(&self) -> Option<&str> {
3659 self.server_version.as_deref()
3660 }
3661
3662 pub fn db_unique_name(&self) -> &str {
3667 self.db_unique_name.as_deref().unwrap_or("")
3668 }
3669
3670 pub fn server_version_tuple(&self) -> Option<(u8, u8, u8, u8, u8)> {
3673 self.server_version_tuple
3674 }
3675
3676 fn supports_oson_long_fnames(&self) -> bool {
3679 self.server_version_tuple
3680 .map(|(major, ..)| major >= 23)
3681 .unwrap_or(false)
3682 }
3683
3684 pub fn sdu(&self) -> usize {
3685 self.sdu
3686 }
3687
3688 pub fn supports_pipelining(&self) -> bool {
3692 self.supports_end_of_response
3693 }
3694
3695 pub fn protocol_version(&self) -> u16 {
3700 self.protocol_version
3701 }
3702
3703 pub fn supports_fast_auth(&self) -> bool {
3709 self.supports_fast_auth
3710 }
3711
3712 pub fn cancel_handle(&self) -> Result<CancelHandle> {
3713 Ok(CancelHandle {
3714 write: self.core.write_handle(),
3715 recovery: Arc::clone(&self.core.recovery),
3716 })
3717 }
3718
3719 pub fn is_dead(&self) -> bool {
3722 self.dead || self.core.recovery.is_dead()
3723 }
3724
3725 fn note_parse<T>(
3727 &mut self,
3728 result: std::result::Result<T, oracledb_protocol::ProtocolError>,
3729 ) -> Result<T> {
3730 match result {
3731 Ok(value) => Ok(value),
3732 Err(err) => {
3733 if protocol_error_is_session_dead(&err) {
3734 self.dead = true;
3735 self.core.recovery.mark_dead();
3736 }
3737 Err(Error::Protocol(err))
3738 }
3739 }
3740 }
3741
3742 pub async fn ping(&mut self, cx: &Cx) -> Result<()> {
3744 self.send_function(cx, TNS_FUNC_PING).await
3745 }
3746
3747 pub async fn change_password(
3752 &mut self,
3753 cx: &Cx,
3754 old_password: &str,
3755 new_password: &str,
3756 ) -> Result<()> {
3757 observe_cancellation_between_round_trips(cx)?;
3758 self.ensure_clean_before_request().await?;
3759 let (encoded_password, encoded_newpassword) =
3760 oracledb_protocol::crypto::encrypt_change_password_pair(
3761 &self.combo_key,
3762 old_password.as_bytes(),
3763 new_password.as_bytes(),
3764 )?;
3765 let seq_num = next_ttc_sequence(&mut self.ttc_seq_num);
3766 let payload = build_change_password_payload_with_seq(
3767 &self.user,
3768 &encoded_password,
3769 &encoded_newpassword,
3770 seq_num,
3771 self.capabilities.ttc_field_version,
3772 )?;
3773 self.core.send_data_packet(cx, &payload, self.sdu).await?;
3774 let limits = self.protocol_limits;
3777 let response = self
3778 .read_response_cancellable(cx, !self.supports_end_of_response, |bytes| {
3779 classic_connect_response_is_complete(bytes, limits).unwrap_or(true)
3780 })
3781 .await?;
3782 self.note_parse(
3783 parse_auth_response_with_limits(&response, self.protocol_limits).map(|_| ()),
3784 )?;
3785 Ok(())
3786 }
3787
3788 #[allow(clippy::too_many_arguments)]
3794 pub async fn subscribe_register(
3795 &mut self,
3796 cx: &Cx,
3797 namespace: u32,
3798 name: Option<&str>,
3799 public_qos: u32,
3800 operations: u32,
3801 timeout: u32,
3802 grouping_class: u8,
3803 grouping_value: u32,
3804 grouping_type: u8,
3805 ) -> Result<SubscribeResult> {
3806 observe_cancellation_between_round_trips(cx)?;
3807 self.ensure_clean_before_request().await?;
3808 let seq_num = next_ttc_sequence(&mut self.ttc_seq_num);
3809 let payload = build_subscribe_payload_with_seq(
3810 seq_num,
3811 TNS_SUBSCR_OP_REGISTER,
3812 Some(&self.user),
3813 None,
3814 namespace,
3815 name,
3816 public_qos,
3817 operations,
3818 timeout,
3819 grouping_class,
3820 grouping_value,
3821 grouping_type,
3822 0,
3823 self.capabilities.ttc_field_version,
3824 )?;
3825 self.core.send_data_packet(cx, &payload, self.sdu).await?;
3826 let capabilities = self.capabilities;
3831 let limits = self.protocol_limits;
3832 let response = self
3833 .read_response_cancellable(cx, !self.supports_end_of_response, |bytes| {
3834 response_complete(&parse_subscribe_response_with_limits(
3835 bytes,
3836 capabilities,
3837 limits,
3838 ))
3839 })
3840 .await?;
3841 self.note_parse(parse_subscribe_response_with_limits(
3842 &response,
3843 self.capabilities,
3844 self.protocol_limits,
3845 ))
3846 }
3847
3848 #[allow(clippy::too_many_arguments)]
3853 pub async fn subscribe_unregister(
3854 &mut self,
3855 cx: &Cx,
3856 registration_id: u64,
3857 client_id: &[u8],
3858 namespace: u32,
3859 name: Option<&str>,
3860 public_qos: u32,
3861 operations: u32,
3862 timeout: u32,
3863 grouping_class: u8,
3864 grouping_value: u32,
3865 grouping_type: u8,
3866 ) -> Result<()> {
3867 observe_cancellation_between_round_trips(cx)?;
3868 self.ensure_clean_before_request().await?;
3869 let seq_num = next_ttc_sequence(&mut self.ttc_seq_num);
3870 let payload = build_subscribe_payload_with_seq(
3875 seq_num,
3876 TNS_SUBSCR_OP_UNREGISTER,
3877 Some(&self.user),
3878 Some(client_id),
3879 namespace,
3880 name,
3881 public_qos,
3882 operations,
3883 timeout,
3884 grouping_class,
3885 grouping_value,
3886 grouping_type,
3887 registration_id,
3888 self.capabilities.ttc_field_version,
3889 )?;
3890 self.core.send_data_packet(cx, &payload, self.sdu).await?;
3891 let capabilities = self.capabilities;
3894 let limits = self.protocol_limits;
3895 let response = self
3896 .read_response_cancellable(cx, !self.supports_end_of_response, |bytes| {
3897 response_complete(&parse_subscribe_response_with_limits(
3898 bytes,
3899 capabilities,
3900 limits,
3901 ))
3902 })
3903 .await?;
3904 self.note_parse(parse_subscribe_response_with_limits(
3905 &response,
3906 self.capabilities,
3907 self.protocol_limits,
3908 ))?;
3909 Ok(())
3910 }
3911
3912 pub async fn notify_register(&mut self, cx: &Cx, client_id: &[u8]) -> Result<()> {
3917 observe_cancellation_between_round_trips(cx)?;
3918 self.ensure_clean_before_request().await?;
3919 let seq_num = next_ttc_sequence(&mut self.ttc_seq_num);
3920 let payload =
3921 build_notify_payload_with_seq(seq_num, client_id, self.capabilities.ttc_field_version)?;
3922 self.core
3924 .send_data_packet_with_flags(cx, &payload, self.sdu, 0, TNS_DATA_FLAGS_END_OF_REQUEST)
3925 .await?;
3926 Ok(())
3927 }
3928
3929 pub async fn recv_notification(
3940 &mut self,
3941 cx: &Cx,
3942 namespace: u32,
3943 public_qos: u32,
3944 read_timeout: Duration,
3945 ) -> Result<NotificationOutcome> {
3946 observe_cancellation_between_round_trips(cx)?;
3947 let db_name = self.descriptor.service_name.clone();
3948 loop {
3949 observe_cancellation_between_round_trips(cx)?;
3950 if !self.notification_header_consumed {
3952 if self.notification_buffer.is_empty() {
3953 match self.read_one_notification_packet(read_timeout).await? {
3954 PacketRead::Appended => continue,
3955 PacketRead::TimedOut => return Ok(NotificationOutcome::TimedOut),
3956 PacketRead::Closed => return Ok(NotificationOutcome::Closed),
3957 }
3958 }
3959 let consumed = check_notification_header_with_limits(
3960 &self.notification_buffer,
3961 self.protocol_limits,
3962 )?;
3963 self.notification_buffer.drain(..consumed);
3964 self.notification_header_consumed = true;
3965 }
3966 if !self.notification_buffer.is_empty() {
3968 if let Some((record, consumed)) = try_parse_oac_record_with_limits(
3969 &self.notification_buffer,
3970 namespace,
3971 public_qos,
3972 Some(&db_name),
3973 self.protocol_limits,
3974 )? {
3975 self.notification_buffer.drain(..consumed);
3976 return Ok(NotificationOutcome::Record(record));
3977 }
3978 }
3979 match self.read_one_notification_packet(read_timeout).await? {
3981 PacketRead::Appended => {}
3982 PacketRead::TimedOut => return Ok(NotificationOutcome::TimedOut),
3985 PacketRead::Closed => return Ok(NotificationOutcome::Closed),
3986 }
3987 }
3988 }
3989
3990 async fn read_one_notification_packet(&mut self, read_timeout: Duration) -> Result<PacketRead> {
3996 let read = self.core.read_packet(PacketLengthWidth::Large32);
3997 let packet = match time::timeout(time::wall_now(), read_timeout, read).await {
3998 Ok(Ok(packet)) => packet,
3999 Ok(Err(_)) => return Ok(PacketRead::Closed),
4002 Err(_) => return Ok(PacketRead::TimedOut),
4003 };
4004 if packet.packet_type != TNS_PACKET_TYPE_DATA {
4005 return Ok(PacketRead::Closed);
4006 }
4007 let Some((_data_flags, payload)) = packet.payload.split_at_checked(2) else {
4008 return Ok(PacketRead::Closed);
4009 };
4010 self.notification_buffer.extend_from_slice(payload);
4011 Ok(PacketRead::Appended)
4012 }
4013
4014 pub async fn ping_with_timeout(&mut self, cx: &Cx, timeout_ms: u32) -> Result<()> {
4017 if timeout_ms == 0 {
4018 return self.ping(cx).await;
4019 }
4020 let deadline = QueryDeadline::from_timeout(Duration::from_millis(u64::from(timeout_ms)));
4021 match deadline.run(self.ping(cx)).await {
4022 Ok(result) => result,
4023 Err(DeadlineExpiry::BeforeStart) => self.reject_before_operation_start(cx, timeout_ms),
4024 Err(DeadlineExpiry::InFlight) => self.recover_from_call_timeout(cx, timeout_ms).await,
4028 }
4029 }
4030
4031 pub async fn commit(&mut self, cx: &Cx) -> Result<()> {
4034 let _span = obs_span!("oracledb.commit");
4035 self.send_function(cx, TNS_FUNC_COMMIT).await?;
4036 self.sessionless_data = None;
4039 Ok(())
4040 }
4041
4042 pub async fn rollback(&mut self, cx: &Cx) -> Result<()> {
4044 let _span = obs_span!("oracledb.rollback");
4045 self.send_function(cx, TNS_FUNC_ROLLBACK).await?;
4046 self.sessionless_data = None;
4047 Ok(())
4048 }
4049
4050 pub async fn enable_dbms_output(&mut self, cx: &Cx, buffer_bytes: Option<u32>) -> Result<()> {
4055 let arg = buffer_bytes.map_or_else(|| "null".to_string(), |n| n.to_string());
4058 self.execute_query_with_bind_rows_and_options_core(
4059 cx,
4060 &format!("begin dbms_output.enable({arg}); end;"),
4061 0,
4062 &[],
4063 ExecuteOptions::default(),
4064 )
4065 .await?;
4066 Ok(())
4067 }
4068
4069 pub async fn read_dbms_output(
4076 &mut self,
4077 cx: &Cx,
4078 max_lines: usize,
4079 max_chars: usize,
4080 ) -> Result<DbmsOutput> {
4081 const NUMBER_SIZE: u32 = 22;
4083 let mut out = DbmsOutput::default();
4084 loop {
4085 observe_cancellation_between_round_trips(cx)?;
4086 let binds = [vec![
4087 BindValue::Output {
4088 ora_type_num: oracledb_protocol::thin::ORA_TYPE_NUM_VARCHAR,
4089 csfrm: oracledb_protocol::thin::CS_FORM_IMPLICIT,
4090 buffer_size: 32767,
4091 },
4092 BindValue::Output {
4093 ora_type_num: oracledb_protocol::thin::ORA_TYPE_NUM_NUMBER,
4094 csfrm: 0,
4095 buffer_size: NUMBER_SIZE,
4096 },
4097 ]];
4098 let res = self
4099 .execute_query_with_bind_rows_and_options_core(
4100 cx,
4101 "begin dbms_output.get_line(:1, :2); end;",
4102 0,
4103 &binds,
4104 ExecuteOptions::default(),
4105 )
4106 .await?;
4107 let status = res
4109 .out_values
4110 .get(1)
4111 .and_then(|(_, v)| v.as_ref())
4112 .and_then(oracledb_protocol::thin::QueryValue::as_i64)
4113 .unwrap_or(1);
4114 if status != 0 {
4115 break; }
4117 let line = res
4118 .out_values
4119 .first()
4120 .and_then(|(_, v)| v.as_ref())
4121 .and_then(oracledb_protocol::thin::QueryValue::as_text)
4122 .unwrap_or("")
4123 .to_string();
4124 let chars = line.chars().count();
4125 if out.lines.len() >= max_lines || out.char_count + chars > max_chars {
4131 out.truncated = true;
4132 break;
4133 }
4134 out.char_count += chars;
4135 out.lines.push(line);
4136 out.line_count += 1;
4137 }
4138 Ok(out)
4139 }
4140
4141 async fn start_sessionless_transaction(
4147 &mut self,
4148 cx: &Cx,
4149 transaction_id: &[u8],
4150 timeout: u32,
4151 flags: u32,
4152 defer_round_trip: bool,
4153 ) -> Result<()> {
4154 if self.sessionless_data.is_some() {
4155 return Err(Error::SessionlessTransaction(
4156 SessionlessError::AlreadyActive,
4157 ));
4158 }
4159 let data = SessionlessData {
4160 transaction_id: transaction_id.to_vec(),
4161 timeout,
4162 operation: TNS_TPC_TXN_START,
4163 flags,
4164 piggyback_pending: defer_round_trip,
4165 started_on_server: false,
4166 };
4167 if defer_round_trip {
4168 self.sessionless_data = Some(data);
4170 return Ok(());
4171 }
4172 observe_cancellation_between_round_trips(cx)?;
4174 self.ensure_clean_before_request().await?;
4175 let seq_num = next_ttc_sequence(&mut self.ttc_seq_num);
4176 let payload = build_tpc_txn_switch_payload_with_seq(
4177 seq_num,
4178 0,
4179 data.operation,
4180 data.flags | TPC_TXN_FLAGS_SESSIONLESS,
4181 data.timeout,
4182 Some(transaction_id),
4183 );
4184 self.core.send_data_packet(cx, &payload, self.sdu).await?;
4185 let capabilities = self.capabilities;
4188 let limits = self.protocol_limits;
4189 let response = self
4190 .read_response_cancellable(cx, !self.supports_end_of_response, |bytes| {
4191 response_complete(&parse_tpc_txn_switch_response_with_limits(
4192 bytes,
4193 capabilities,
4194 limits,
4195 ))
4196 })
4197 .await?;
4198 let state = self.note_parse(parse_tpc_txn_switch_response_with_limits(
4199 &response,
4200 self.capabilities,
4201 self.protocol_limits,
4202 ))?;
4203 self.sessionless_data = Some(data);
4204 self.apply_sessionless_state(state);
4205 Ok(())
4206 }
4207
4208 pub async fn begin_sessionless_transaction(
4211 &mut self,
4212 cx: &Cx,
4213 transaction_id: &[u8],
4214 timeout: u32,
4215 defer_round_trip: bool,
4216 ) -> Result<()> {
4217 self.start_sessionless_transaction(
4218 cx,
4219 transaction_id,
4220 timeout,
4221 TPC_TXN_FLAGS_NEW,
4222 defer_round_trip,
4223 )
4224 .await
4225 }
4226
4227 pub async fn resume_sessionless_transaction(
4230 &mut self,
4231 cx: &Cx,
4232 transaction_id: &[u8],
4233 timeout: u32,
4234 defer_round_trip: bool,
4235 ) -> Result<()> {
4236 self.start_sessionless_transaction(
4237 cx,
4238 transaction_id,
4239 timeout,
4240 TPC_TXN_FLAGS_RESUME,
4241 defer_round_trip,
4242 )
4243 .await
4244 }
4245
4246 pub async fn suspend_sessionless_transaction(&mut self, cx: &Cx) -> Result<()> {
4249 match &self.sessionless_data {
4250 None => return Err(Error::SessionlessTransaction(SessionlessError::Inactive)),
4251 Some(data) if data.started_on_server => {
4252 return Err(Error::SessionlessTransaction(
4253 SessionlessError::DifferingMethods,
4254 ));
4255 }
4256 Some(_) => {}
4257 }
4258 observe_cancellation_between_round_trips(cx)?;
4259 self.ensure_clean_before_request().await?;
4260 let seq_num = next_ttc_sequence(&mut self.ttc_seq_num);
4261 let payload = build_tpc_txn_switch_payload_with_seq(
4262 seq_num,
4263 0,
4264 TNS_TPC_TXN_DETACH,
4265 TPC_TXN_FLAGS_SESSIONLESS,
4266 0,
4267 None,
4268 );
4269 self.core.send_data_packet(cx, &payload, self.sdu).await?;
4270 let capabilities = self.capabilities;
4273 let limits = self.protocol_limits;
4274 let response = self
4275 .read_response_cancellable(cx, !self.supports_end_of_response, |bytes| {
4276 response_complete(&parse_tpc_txn_switch_response_with_limits(
4277 bytes,
4278 capabilities,
4279 limits,
4280 ))
4281 })
4282 .await?;
4283 let state = self.note_parse(parse_tpc_txn_switch_response_with_limits(
4284 &response,
4285 self.capabilities,
4286 self.protocol_limits,
4287 ))?;
4288 self.sessionless_data = None;
4291 self.apply_sessionless_state(state);
4292 Ok(())
4293 }
4294
4295 async fn tpc_switch_round_trip(
4298 &mut self,
4299 cx: &Cx,
4300 operation: u32,
4301 flags: u32,
4302 timeout: u32,
4303 xid: Option<&TpcXid<'_>>,
4304 context: Option<&[u8]>,
4305 ) -> Result<TpcSwitchResponse> {
4306 observe_cancellation_between_round_trips(cx)?;
4307 self.ensure_clean_before_request().await?;
4308 let seq_num = next_ttc_sequence(&mut self.ttc_seq_num);
4309 let payload = build_tpc_switch_payload_with_seq_and_version(
4310 seq_num,
4311 operation,
4312 flags,
4313 timeout,
4314 xid,
4315 context,
4316 self.capabilities.ttc_field_version,
4317 );
4318 self.core.send_data_packet(cx, &payload, self.sdu).await?;
4319 let capabilities = self.capabilities;
4322 let limits = self.protocol_limits;
4323 let response = self
4324 .read_response_cancellable(cx, !self.supports_end_of_response, |bytes| {
4325 response_complete(&parse_tpc_switch_response_with_limits(
4326 bytes,
4327 capabilities,
4328 limits,
4329 ))
4330 })
4331 .await?;
4332 self.note_parse(parse_tpc_switch_response_with_limits(
4333 &response,
4334 self.capabilities,
4335 self.protocol_limits,
4336 ))
4337 }
4338
4339 async fn tpc_change_state_round_trip(
4343 &mut self,
4344 cx: &Cx,
4345 operation: u32,
4346 requested_state: u32,
4347 xid: Option<&TpcXid<'_>>,
4348 context: Option<&[u8]>,
4349 ) -> Result<TpcChangeStateResponse> {
4350 observe_cancellation_between_round_trips(cx)?;
4351 self.ensure_clean_before_request().await?;
4352 let seq_num = next_ttc_sequence(&mut self.ttc_seq_num);
4353 let payload = build_tpc_change_state_payload_with_seq_and_version(
4354 seq_num,
4355 operation,
4356 requested_state,
4357 0,
4358 xid,
4359 context,
4360 self.capabilities.ttc_field_version,
4361 );
4362 self.core.send_data_packet(cx, &payload, self.sdu).await?;
4363 let capabilities = self.capabilities;
4366 let limits = self.protocol_limits;
4367 let response = self
4368 .read_response_cancellable(cx, !self.supports_end_of_response, |bytes| {
4369 response_complete(&parse_tpc_change_state_response_with_limits(
4370 bytes,
4371 capabilities,
4372 limits,
4373 ))
4374 })
4375 .await?;
4376 self.note_parse(parse_tpc_change_state_response_with_limits(
4377 &response,
4378 self.capabilities,
4379 self.protocol_limits,
4380 ))
4381 }
4382
4383 pub async fn tpc_begin(
4388 &mut self,
4389 cx: &Cx,
4390 format_id: u32,
4391 global_transaction_id: &[u8],
4392 branch_qualifier: &[u8],
4393 flags: u32,
4394 timeout: u32,
4395 ) -> Result<()> {
4396 let xid = TpcXid {
4397 format_id,
4398 global_transaction_id,
4399 branch_qualifier,
4400 };
4401 let response = self
4402 .tpc_switch_round_trip(cx, TNS_TPC_TXN_START, flags, timeout, Some(&xid), None)
4403 .await?;
4404 self.transaction_context = Some(response.context);
4405 self.txn_in_progress = response.txn_in_progress;
4406 Ok(())
4407 }
4408
4409 pub async fn tpc_end(
4413 &mut self,
4414 cx: &Cx,
4415 xid: Option<(u32, &[u8], &[u8])>,
4416 flags: u32,
4417 ) -> Result<()> {
4418 let xid = xid.map(|(format_id, gtid, bqual)| TpcXid {
4419 format_id,
4420 global_transaction_id: gtid,
4421 branch_qualifier: bqual,
4422 });
4423 let context = self.transaction_context.clone();
4424 let response = self
4425 .tpc_switch_round_trip(
4426 cx,
4427 TNS_TPC_TXN_DETACH,
4428 flags,
4429 0,
4430 xid.as_ref(),
4431 context.as_deref(),
4432 )
4433 .await?;
4434 self.txn_in_progress = response.txn_in_progress;
4435 self.transaction_context = None;
4436 Ok(())
4437 }
4438
4439 pub async fn tpc_prepare(&mut self, cx: &Cx, xid: Option<(u32, &[u8], &[u8])>) -> Result<bool> {
4443 let xid = xid.map(|(format_id, gtid, bqual)| TpcXid {
4444 format_id,
4445 global_transaction_id: gtid,
4446 branch_qualifier: bqual,
4447 });
4448 let context = self.transaction_context.clone();
4449 let response = self
4450 .tpc_change_state_round_trip(
4451 cx,
4452 TNS_TPC_TXN_PREPARE,
4453 TNS_TPC_TXN_STATE_PREPARE,
4454 xid.as_ref(),
4455 context.as_deref(),
4456 )
4457 .await?;
4458 self.txn_in_progress = response.txn_in_progress;
4459 match response.state {
4460 TNS_TPC_TXN_STATE_REQUIRES_COMMIT => Ok(true),
4461 TNS_TPC_TXN_STATE_READ_ONLY => Ok(false),
4462 other => Err(Error::UnknownTransactionState(other)),
4463 }
4464 }
4465
4466 pub async fn tpc_commit(
4471 &mut self,
4472 cx: &Cx,
4473 xid: Option<(u32, &[u8], &[u8])>,
4474 one_phase: bool,
4475 ) -> Result<()> {
4476 let xid = xid.map(|(format_id, gtid, bqual)| TpcXid {
4477 format_id,
4478 global_transaction_id: gtid,
4479 branch_qualifier: bqual,
4480 });
4481 let requested_state = if one_phase {
4482 TNS_TPC_TXN_STATE_READ_ONLY
4483 } else {
4484 TNS_TPC_TXN_STATE_COMMITTED
4485 };
4486 let context = self.transaction_context.clone();
4487 let response = self
4488 .tpc_change_state_round_trip(
4489 cx,
4490 TNS_TPC_TXN_COMMIT,
4491 requested_state,
4492 xid.as_ref(),
4493 context.as_deref(),
4494 )
4495 .await?;
4496 self.txn_in_progress = response.txn_in_progress;
4497 let state = response.state;
4500 let ok = if one_phase {
4501 state == TNS_TPC_TXN_STATE_READ_ONLY || state == TNS_TPC_TXN_STATE_COMMITTED
4502 } else {
4503 state == TNS_TPC_TXN_STATE_FORGOTTEN
4504 };
4505 if !ok {
4506 return Err(Error::UnknownTransactionState(state));
4507 }
4508 self.transaction_context = None;
4509 Ok(())
4510 }
4511
4512 pub async fn tpc_rollback(&mut self, cx: &Cx, xid: Option<(u32, &[u8], &[u8])>) -> Result<()> {
4516 let xid = xid.map(|(format_id, gtid, bqual)| TpcXid {
4517 format_id,
4518 global_transaction_id: gtid,
4519 branch_qualifier: bqual,
4520 });
4521 let context = self.transaction_context.clone();
4522 let response = self
4523 .tpc_change_state_round_trip(
4524 cx,
4525 TNS_TPC_TXN_ABORT,
4526 TNS_TPC_TXN_STATE_ABORTED,
4527 xid.as_ref(),
4528 context.as_deref(),
4529 )
4530 .await?;
4531 self.txn_in_progress = response.txn_in_progress;
4532 if response.state != TNS_TPC_TXN_STATE_ABORTED {
4533 return Err(Error::UnknownTransactionState(response.state));
4534 }
4535 Ok(())
4536 }
4537
4538 pub fn transaction_in_progress(&self) -> bool {
4541 self.txn_in_progress
4542 }
4543
4544 pub fn prepare_sessionless_suspend_on_success(&mut self) -> Result<()> {
4549 match &mut self.sessionless_data {
4550 None => Err(Error::SessionlessTransaction(SessionlessError::Inactive)),
4551 Some(data) if data.started_on_server => Err(Error::SessionlessTransaction(
4552 SessionlessError::DifferingMethods,
4553 )),
4554 Some(data) => {
4555 if data.piggyback_pending {
4556 data.operation |= TNS_TPC_TXN_POST_DETACH;
4557 } else {
4558 data.operation = TNS_TPC_TXN_POST_DETACH;
4559 data.flags = TPC_TXN_FLAGS_SESSIONLESS;
4560 data.piggyback_pending = true;
4561 }
4562 Ok(())
4563 }
4564 }
4565 }
4566
4567 fn take_sessionless_piggyback(&mut self) -> Option<Vec<u8>> {
4572 let data = self.sessionless_data.as_mut()?;
4573 if !data.piggyback_pending {
4574 return None;
4575 }
4576 data.piggyback_pending = false;
4577 let xid = if data.operation & TNS_TPC_TXN_START != 0 {
4578 Some(data.transaction_id.clone())
4579 } else {
4580 None
4581 };
4582 let flags = data.flags | TPC_TXN_FLAGS_SESSIONLESS;
4583 let operation = data.operation;
4584 let timeout = data.timeout;
4585 let seq_num = next_ttc_sequence(&mut self.ttc_seq_num);
4586 Some(build_sessionless_piggyback(
4587 seq_num,
4588 0,
4589 operation,
4590 flags,
4591 timeout,
4592 xid.as_deref(),
4593 ))
4594 }
4595
4596 fn apply_sessionless_state(&mut self, state: Option<SessionlessTxnState>) {
4600 match state {
4601 Some(SessionlessTxnState::Unset) => {
4605 self.sessionless_data = None;
4606 self.txn_in_progress = false;
4607 }
4608 Some(SessionlessTxnState::Set { started_on_server }) => {
4614 self.txn_in_progress = true;
4615 match self.sessionless_data.as_mut() {
4616 Some(data) => {
4617 data.started_on_server = started_on_server;
4618 data.piggyback_pending = false;
4619 }
4620 None => {
4621 self.sessionless_data = Some(SessionlessData {
4622 transaction_id: Vec::new(),
4623 timeout: 0,
4624 operation: TNS_TPC_TXN_START,
4625 flags: 0,
4626 piggyback_pending: false,
4627 started_on_server,
4628 });
4629 }
4630 }
4631 }
4632 None => {}
4633 }
4634 }
4635
4636 #[allow(dead_code)]
4637 async fn execute_query_collect_core(
4638 &mut self,
4639 cx: &Cx,
4640 sql: &str,
4641 prefetch_rows: u32,
4642 ) -> Result<QueryResult> {
4643 let mut result = self
4644 .execute_query_with_bind_rows_and_options_core(
4645 cx,
4646 sql,
4647 prefetch_rows,
4648 &[],
4649 ExecuteOptions::default(),
4650 )
4651 .await?;
4652 if !columns_require_define(&result.columns) || result.cursor_id == 0 {
4653 return Ok(result);
4654 }
4655 if !result.rows.is_empty() {
4658 return Ok(result);
4659 }
4660 let cursor_id = result.cursor_id;
4661 let columns = result.columns.clone();
4662 let fetched_result = self
4663 .define_and_fetch_rows_with_columns(cx, cursor_id, prefetch_rows.max(1), &columns, None)
4664 .await;
4665 let fetched = self.close_cursor_on_error(cursor_id, fetched_result)?;
4666 result.rows = fetched.rows;
4667 result.more_rows = fetched.more_rows;
4668 if !fetched.columns.is_empty() {
4669 result.columns = fetched.columns;
4670 }
4671 if result.cursor_id == 0 {
4672 result.cursor_id = cursor_id;
4673 }
4674 Ok(result)
4675 }
4676
4677 #[cfg(feature = "arrow")]
4683 pub(crate) fn statement_has_cached_cursor(&self, sql: &str) -> bool {
4684 self.statement_cache
4685 .iter()
4686 .any(|entry| entry.sql == sql && entry.cursor_id != 0)
4687 }
4688
4689 #[cfg(feature = "arrow")]
4706 pub(crate) async fn establish_cold_define(
4707 &mut self,
4708 cx: &Cx,
4709 warm: bool,
4710 result: &mut QueryResult,
4711 prefetch_rows: u32,
4712 ) -> Result<()> {
4713 if warm
4714 || result.cursor_id == 0
4715 || !result.rows.is_empty()
4716 || !columns_require_define(&result.columns)
4717 {
4718 return Ok(());
4719 }
4720 let cursor_id = result.cursor_id;
4721 let columns = result.columns.clone();
4722 let fetched_result = self
4723 .define_and_fetch_rows_with_columns(cx, cursor_id, prefetch_rows.max(1), &columns, None)
4724 .await;
4725 let fetched = self.close_cursor_on_error(cursor_id, fetched_result)?;
4726 result.rows = fetched.rows;
4727 result.more_rows = fetched.more_rows;
4728 if !fetched.columns.is_empty() {
4729 result.columns = fetched.columns;
4730 }
4731 if result.cursor_id == 0 {
4732 result.cursor_id = cursor_id;
4733 }
4734 Ok(())
4735 }
4736
4737 pub async fn query<'p>(
4760 &mut self,
4761 cx: &Cx,
4762 sql: &str,
4763 params: impl Into<crate::Params<'p>>,
4764 ) -> Result<Rows<'_>> {
4765 self.query_with(cx, Query::owned_sql(sql.to_string()).bind(params))
4766 .await
4767 }
4768
4769 pub async fn query_one<'p>(
4771 &mut self,
4772 cx: &Cx,
4773 sql: &str,
4774 params: impl Into<crate::Params<'p>>,
4775 ) -> Result<Row> {
4776 let mut rows = self
4777 .query_with(
4778 cx,
4779 Query::owned_sql(sql.to_string())
4780 .bind(params)
4781 .arraysize(NonZeroU32::new(2).expect("two is non-zero"))
4782 .prefetch(2),
4783 )
4784 .await?;
4785 rows.materialize_for_cardinality(cx).await?;
4786 rows.one()
4787 }
4788
4789 pub async fn query_opt<'p>(
4791 &mut self,
4792 cx: &Cx,
4793 sql: &str,
4794 params: impl Into<crate::Params<'p>>,
4795 ) -> Result<Option<Row>> {
4796 let mut rows = self
4797 .query_with(
4798 cx,
4799 Query::owned_sql(sql.to_string())
4800 .bind(params)
4801 .arraysize(NonZeroU32::new(2).expect("two is non-zero"))
4802 .prefetch(2),
4803 )
4804 .await?;
4805 rows.materialize_for_cardinality(cx).await?;
4806 rows.opt()
4807 }
4808
4809 pub async fn query_all<'p>(
4811 &mut self,
4812 cx: &Cx,
4813 sql: &str,
4814 params: impl Into<crate::Params<'p>>,
4815 ) -> Result<Vec<Row>> {
4816 self.query(cx, sql, params).await?.collect(cx).await
4817 }
4818
4819 pub async fn query_with<'conn, 'q>(
4822 &'conn mut self,
4823 cx: &Cx,
4824 query: Query<'q>,
4825 ) -> Result<Rows<'conn>> {
4826 let Query {
4827 sql,
4828 params,
4829 arraysize,
4830 prefetch,
4831 prefetch_set: _,
4832 materialize_lobs,
4833 scrollable,
4834 timeout,
4835 } = query;
4836 let sql_owned = sql.into_owned();
4837 let binds = crate::sql_convert::resolve_params(&sql_owned, params)?;
4838 let bind_rows = if binds.is_empty() {
4839 Vec::new()
4840 } else {
4841 vec![binds]
4842 };
4843 let exec_options = ExecuteOptions::default().with_scrollable(scrollable);
4844 let deadline = QueryDeadline::new(cx, timeout);
4845 let mut result = match deadline
4846 .run(self.execute_query_with_bind_rows_and_options_core(
4847 cx,
4848 &sql_owned,
4849 prefetch,
4850 &bind_rows,
4851 exec_options,
4852 ))
4853 .await
4854 {
4855 Ok(result) => result?,
4856 Err(DeadlineExpiry::BeforeStart) => {
4857 return self.reject_before_operation_start(cx, deadline.timeout_ms());
4858 }
4859 Err(DeadlineExpiry::InFlight) => {
4860 return self
4861 .recover_from_call_timeout(cx, deadline.timeout_ms())
4862 .await
4863 }
4864 };
4865 if materialize_lobs
4866 && columns_require_define(&result.columns)
4867 && result.cursor_id != 0
4868 && result.rows.is_empty()
4869 {
4870 let cursor_id = result.cursor_id;
4871 let columns = result.columns.clone();
4872 let fetched = match deadline
4873 .run(self.define_and_fetch_rows_with_columns(
4874 cx,
4875 cursor_id,
4876 prefetch.max(1),
4877 &columns,
4878 None,
4879 ))
4880 .await
4881 {
4882 Ok(result) => self.close_cursor_on_error(cursor_id, result)?,
4883 Err(DeadlineExpiry::BeforeStart) => {
4884 self.release_cursor(cursor_id);
4885 return self.reject_before_operation_start(cx, deadline.timeout_ms());
4886 }
4887 Err(DeadlineExpiry::InFlight) => {
4888 let recovered = self
4889 .recover_from_call_timeout(cx, deadline.timeout_ms())
4890 .await;
4891 self.close_cursor(cursor_id);
4892 return recovered;
4893 }
4894 };
4895 result.rows = fetched.rows;
4896 result.more_rows = fetched.more_rows;
4897 if !fetched.columns.is_empty() {
4898 result.columns = fetched.columns;
4899 }
4900 if result.cursor_id == 0 {
4901 result.cursor_id = cursor_id;
4902 }
4903 }
4904 Ok(Rows::from_result(
4905 self, sql_owned, arraysize, deadline, scrollable, result,
4906 ))
4907 }
4908
4909 pub async fn execute<'p>(
4911 &mut self,
4912 cx: &Cx,
4913 sql: &str,
4914 params: impl Into<crate::Params<'p>>,
4915 ) -> Result<ExecuteOutcome> {
4916 self.execute_with(cx, Execute::owned_sql(sql.to_string()).bind(params))
4917 .await
4918 }
4919
4920 pub async fn execute_with<'e>(
4922 &mut self,
4923 cx: &Cx,
4924 execute: Execute<'e>,
4925 ) -> Result<ExecuteOutcome> {
4926 let deadline = QueryDeadline::new(cx, execute.timeout_duration());
4927 self.execute_with_deadline(cx, execute, deadline).await
4928 }
4929
4930 async fn execute_with_deadline<'e>(
4931 &mut self,
4932 cx: &Cx,
4933 execute: Execute<'e>,
4934 deadline: QueryDeadline,
4935 ) -> Result<ExecuteOutcome> {
4936 let Execute {
4937 sql,
4938 params,
4939 timeout: _,
4940 options,
4941 } = execute;
4942 let sql_owned = sql.into_owned();
4943 let binds = crate::sql_convert::resolve_params(&sql_owned, params)?;
4944 let bind_rows = if binds.is_empty() {
4945 Vec::new()
4946 } else {
4947 vec![binds]
4948 };
4949 let result = match deadline
4950 .run(self.execute_query_with_bind_rows_and_options_core(
4951 cx, &sql_owned, 0, &bind_rows, options,
4952 ))
4953 .await
4954 {
4955 Ok(result) => result?,
4956 Err(DeadlineExpiry::BeforeStart) => {
4957 return self.reject_before_operation_start(cx, deadline.timeout_ms());
4958 }
4959 Err(DeadlineExpiry::InFlight) => {
4960 return self
4961 .recover_from_call_timeout(cx, deadline.timeout_ms())
4962 .await
4963 }
4964 };
4965 Ok(ExecuteOutcome::from_query_result(result))
4966 }
4967
4968 pub async fn execute_many<'b>(
4970 &mut self,
4971 cx: &Cx,
4972 sql: &str,
4973 rows: impl Into<crate::BatchRows<'b>>,
4974 ) -> Result<BatchOutcome> {
4975 self.execute_many_with(cx, Batch::owned_sql(sql.to_string(), rows))
4976 .await
4977 }
4978
4979 pub async fn execute_many_with<'b>(
4981 &mut self,
4982 cx: &Cx,
4983 batch: Batch<'b>,
4984 ) -> Result<BatchOutcome> {
4985 let Batch {
4986 sql,
4987 rows,
4988 timeout,
4989 options,
4990 } = batch;
4991 rows.validate_rectangular()?;
4992 if rows.is_empty() {
4993 return Ok(BatchOutcome::empty(options.arraydmlrowcounts()));
4994 }
4995 let sql_owned = sql.into_owned();
4996 let deadline = QueryDeadline::new(cx, timeout);
4997 let result = match deadline
4998 .run(self.execute_query_with_bind_rows_and_options_core(
4999 cx,
5000 &sql_owned,
5001 0,
5002 rows.as_slice(),
5003 options,
5004 ))
5005 .await
5006 {
5007 Ok(result) => result?,
5008 Err(DeadlineExpiry::BeforeStart) => {
5009 return self.reject_before_operation_start(cx, deadline.timeout_ms());
5010 }
5011 Err(DeadlineExpiry::InFlight) => {
5012 return self
5013 .recover_from_call_timeout(cx, deadline.timeout_ms())
5014 .await
5015 }
5016 };
5017 Ok(BatchOutcome::from_query_result(result))
5018 }
5019
5020 pub async fn register_query<'r>(
5022 &mut self,
5023 cx: &Cx,
5024 registration: Registration<'r>,
5025 ) -> Result<RegistrationOutcome> {
5026 let Registration {
5027 sql,
5028 params,
5029 registration_id,
5030 timeout,
5031 } = registration;
5032 let sql_owned = sql.into_owned();
5033 let binds = crate::sql_convert::resolve_params(&sql_owned, params)?;
5034 let bind_rows = if binds.is_empty() {
5035 Vec::new()
5036 } else {
5037 vec![binds]
5038 };
5039 let exec_options = ExecuteOptions::default().with_registration_id(registration_id);
5040 let deadline = QueryDeadline::new(cx, timeout);
5041 let result = match deadline
5042 .run(self.execute_query_with_bind_rows_and_options_core(
5043 cx,
5044 &sql_owned,
5045 0,
5046 &bind_rows,
5047 exec_options,
5048 ))
5049 .await
5050 {
5051 Ok(result) => result?,
5052 Err(DeadlineExpiry::BeforeStart) => {
5053 return self.reject_before_operation_start(cx, deadline.timeout_ms());
5054 }
5055 Err(DeadlineExpiry::InFlight) => {
5056 return self
5057 .recover_from_call_timeout(cx, deadline.timeout_ms())
5058 .await
5059 }
5060 };
5061 Ok(RegistrationOutcome::from_query_result(result))
5062 }
5063
5064 pub(crate) async fn execute_query_with_binds_core(
5065 &mut self,
5066 cx: &Cx,
5067 sql: &str,
5068 prefetch_rows: u32,
5069 binds: &[BindValue],
5070 ) -> Result<QueryResult> {
5071 let bind_rows = if binds.is_empty() {
5072 Vec::new()
5073 } else {
5074 vec![binds.to_vec()]
5075 };
5076 self.execute_query_with_bind_rows_and_options_core(
5077 cx,
5078 sql,
5079 prefetch_rows,
5080 &bind_rows,
5081 ExecuteOptions::default(),
5082 )
5083 .await
5084 }
5085
5086 pub(crate) async fn execute_query_with_bind_rows_and_options_core(
5087 &mut self,
5088 cx: &Cx,
5089 sql: &str,
5090 prefetch_rows: u32,
5091 bind_rows: &[Vec<BindValue>],
5092 exec_options: ExecuteOptions,
5093 ) -> Result<QueryResult> {
5094 match self
5095 .execute_query_with_bind_rows_options_adjusted(
5096 cx,
5097 sql,
5098 prefetch_rows,
5099 bind_rows,
5100 exec_options,
5101 )
5102 .await
5103 {
5104 Err(err) if refetch_retry_applies(&err) && statement_is_query(sql) => {
5112 observe_cancellation_between_round_trips(cx)?;
5113 self.forget_fetch_metadata(sql);
5116 self.execute_query_with_bind_rows_options_adjusted(
5117 cx,
5118 sql,
5119 prefetch_rows,
5120 bind_rows,
5121 exec_options,
5122 )
5123 .await
5124 }
5125 other => other,
5126 }
5127 }
5128
5129 async fn execute_query_with_bind_rows_options_adjusted(
5130 &mut self,
5131 cx: &Cx,
5132 sql: &str,
5133 prefetch_rows: u32,
5134 bind_rows: &[Vec<BindValue>],
5135 exec_options: ExecuteOptions,
5136 ) -> Result<QueryResult> {
5137 let _span = obs_span!(
5142 "oracledb.execute",
5143 db.statement = %crate::obs::sql_digest(sql),
5144 db.bind_count = bind_rows.first().map_or(0, Vec::len) as u64,
5145 db.bind_rows = bind_rows.len() as u64,
5146 db.rows_fetched = tracing::field::Empty,
5147 );
5148 observe_cancellation_between_round_trips(cx)?;
5149 if !exec_options.scroll_operation() && !exec_options.parse_only() {
5154 crate::sql_convert::validate_bind_rows_shape(sql, bind_rows)?;
5155 crate::sql_convert::validate_bind_rows_types(bind_rows)?;
5161 }
5162 self.ensure_clean_before_request().await?;
5165 let mut exec_options = exec_options.with_max_string_size(self.capabilities.max_string_size);
5166 if exec_options.suspend_on_success() {
5170 self.prepare_sessionless_suspend_on_success()?;
5171 }
5172 let use_cache = exec_options.cache_statement() && !exec_options.parse_only();
5173 let mut is_copy = false;
5179 let bind_shape = bind_type_shape(bind_rows);
5184 if exec_options.cursor_id() == 0 && !exec_options.parse_only() {
5185 if use_cache {
5186 if self.statement_is_in_use(sql) {
5187 is_copy = true;
5190 } else if let Some(cursor_id) = self.statement_cache_get(sql, &bind_shape) {
5191 exec_options = exec_options.with_cursor_id(cursor_id);
5192 }
5193 } else if let Some(cursor_id) = self.statement_cache_take(sql, &bind_shape) {
5194 exec_options = exec_options.with_cursor_id(cursor_id);
5197 }
5198 }
5199 if exec_options.cursor_id() != 0 && statement_is_query(sql) {
5206 if let Some(columns) = self.cursor_columns.get(&exec_options.cursor_id()) {
5207 if columns.iter().any(|column| {
5208 column.ora_type_num() == oracledb_protocol::thin::ORA_TYPE_NUM_VECTOR
5209 }) {
5210 exec_options = exec_options.with_no_prefetch(true);
5211 }
5212 }
5213 }
5214 let piggyback = self.take_close_cursors_piggyback();
5215 if piggyback.is_none() {
5216 let has_ref_cursor_output = bind_rows.iter().any(|row| {
5217 row.iter().any(|value| {
5218 matches!(
5219 value,
5220 BindValue::Output {
5221 ora_type_num: oracledb_protocol::thin::ORA_TYPE_NUM_CURSOR,
5222 ..
5223 }
5224 )
5225 })
5226 });
5227 if has_ref_cursor_output {
5228 let _ = next_ttc_sequence(&mut self.ttc_seq_num);
5231 }
5232 }
5233 self.protocol_limits.check_batch_rows(bind_rows.len())?;
5238 if let Some(first_row) = bind_rows.first() {
5239 self.protocol_limits.check_binds(first_row.len())?;
5240 }
5241 let sessionless_piggyback = self.take_sessionless_piggyback();
5242 let seq_num = next_ttc_sequence(&mut self.ttc_seq_num);
5243 let mut payload = build_execute_payload_with_bind_rows_and_options_with_seq(
5244 sql,
5245 prefetch_rows,
5246 seq_num,
5247 statement_is_query(sql),
5248 bind_rows,
5249 exec_options,
5250 self.capabilities.ttc_field_version,
5251 )?;
5252 if let Some(piggyback_bytes) = sessionless_piggyback {
5253 let mut combined = piggyback_bytes;
5254 combined.extend_from_slice(&payload);
5255 payload = combined;
5256 }
5257 if let Some(mut piggyback_bytes) = piggyback {
5258 piggyback_bytes.extend_from_slice(&payload);
5259 payload = piggyback_bytes;
5260 }
5261 trace_query_bytes("EXECUTE query payload", &payload);
5262 self.core.send_data_packet(cx, &payload, self.sdu).await?;
5263 let known_columns = if exec_options.cursor_id() != 0 {
5264 self.cursor_columns
5265 .get(&exec_options.cursor_id())
5266 .cloned()
5267 .unwrap_or_default()
5268 } else {
5269 Vec::new()
5270 };
5271 let capabilities = self.capabilities;
5275 let limits = self.protocol_limits;
5276 let first_bind_row = bind_rows.first().map(Vec::as_slice).unwrap_or(&[]);
5277 let classic = !self.supports_end_of_response;
5278 let response = self
5279 .read_flushing_out_binds_cancellable(cx, classic, |bytes| {
5280 response_complete(&parse_query_response_with_binds_options_columns_and_limits(
5281 bytes,
5282 capabilities,
5283 first_bind_row,
5284 exec_options,
5285 &known_columns,
5286 limits,
5287 ))
5288 })
5289 .await?;
5290 trace_query_bytes("EXECUTE query response", &response);
5291 let parsed = parse_query_response_with_binds_options_columns_and_limits(
5292 &response,
5293 self.capabilities,
5294 bind_rows.first().map(Vec::as_slice).unwrap_or(&[]),
5295 exec_options,
5296 &known_columns,
5297 self.protocol_limits,
5298 );
5299 match self.note_parse(parsed) {
5300 Ok(result) => {
5301 if result.cursor_id != 0
5302 && !result.rows.is_empty()
5303 && columns_have_lob_prefetch_fields(&result.columns)
5304 {
5305 self.lob_prefetch_cursors.insert(result.cursor_id);
5306 }
5307 self.apply_sessionless_state(result.sessionless_txn_state);
5310 if let Some(txn_in_progress) = result.txn_in_progress {
5315 self.txn_in_progress = txn_in_progress;
5316 }
5317 if is_copy {
5318 if result.cursor_id != 0 {
5322 self.copied_cursors.insert(result.cursor_id);
5323 }
5324 } else if use_cache {
5325 self.statement_cache_put(sql, result.cursor_id, bind_shape);
5326 }
5327 if result.cursor_id != 0 && statement_is_query(sql) && !exec_options.parse_only() {
5334 self.in_use_cursors.insert(result.cursor_id);
5335 }
5336 self.invalidate_bound_ref_cursors(bind_rows);
5343 self.remember_cursor_columns(&result);
5344 obs_record!(_span, db.rows_fetched = result.rows.len() as u64);
5345 if exec_options.parse_only() {
5346 return Ok(result);
5347 }
5348 self.apply_refetch_metadata(cx, sql, result, prefetch_rows.max(2))
5349 .await
5350 }
5351 Err(err) => {
5352 if use_cache {
5355 self.statement_cache_invalidate(sql, exec_options.cursor_id());
5356 }
5357 Err(err)
5358 }
5359 }
5360 }
5361
5362 pub async fn execute_raw(
5388 &mut self,
5389 cx: &Cx,
5390 sql: &str,
5391 prefetch_rows: u32,
5392 bind_rows: &[Vec<BindValue>],
5393 exec_options: ExecuteOptions,
5394 timeout_ms: Option<u32>,
5395 ) -> Result<QueryResult> {
5396 self.execute_query_with_bind_rows_options_call_timeout(
5397 cx,
5398 sql,
5399 prefetch_rows,
5400 bind_rows,
5401 exec_options,
5402 timeout_ms,
5403 )
5404 .await
5405 }
5406
5407 pub(crate) async fn execute_query_with_bind_rows_options_call_timeout(
5408 &mut self,
5409 cx: &Cx,
5410 sql: &str,
5411 prefetch_rows: u32,
5412 bind_rows: &[Vec<BindValue>],
5413 exec_options: ExecuteOptions,
5414 timeout_ms: Option<u32>,
5415 ) -> Result<QueryResult> {
5416 let Some(timeout_ms) = timeout_ms.filter(|value| *value > 0) else {
5417 return self
5418 .execute_query_with_bind_rows_and_options_core(
5419 cx,
5420 sql,
5421 prefetch_rows,
5422 bind_rows,
5423 exec_options,
5424 )
5425 .await;
5426 };
5427 let deadline = QueryDeadline::from_timeout(Duration::from_millis(u64::from(timeout_ms)));
5428 match deadline
5429 .run(self.execute_query_with_bind_rows_and_options_core(
5430 cx,
5431 sql,
5432 prefetch_rows,
5433 bind_rows,
5434 exec_options,
5435 ))
5436 .await
5437 {
5438 Ok(result) => result,
5439 Err(DeadlineExpiry::BeforeStart) => self.reject_before_operation_start(cx, timeout_ms),
5440 Err(DeadlineExpiry::InFlight) => self.recover_from_call_timeout(cx, timeout_ms).await,
5441 }
5442 }
5443
5444 async fn ensure_clean_before_request(&mut self) -> Result<()> {
5461 if self.core.recovery.phase() == SessionRecoveryPhase::InFlight {
5462 self.core.recovery.mark_break_required();
5463 }
5464 if !self.core.recovery.begin_pending_drain()? {
5465 return Ok(());
5466 }
5467 match self
5468 .core
5469 .cancel_and_drain_wire(BREAK_DRAIN_RECOVERY_TIMEOUT)
5470 {
5471 Ok(()) => {
5472 self.core.recovery.finish_drain_ready();
5473 Ok(())
5474 }
5475 Err(err) => {
5476 self.core.recovery.mark_dead();
5477 self.dead = true;
5478 Err(err)
5479 }
5480 }
5481 }
5482
5483 fn in_flight_read_cancellation_error(&mut self, cx: &Cx, err: &Error) -> Option<Error> {
5501 let Error::Io(io_error) = err else {
5502 return None;
5503 };
5504 if io_error.kind() != std::io::ErrorKind::Interrupted
5505 || !(is_asupersync_cancellation(io_error) || cx.checkpoint().is_err())
5506 {
5507 return None;
5508 }
5509
5510 let timeout_ms = cx
5511 .budget()
5512 .remaining_time(time::wall_now())
5513 .map(duration_to_millis_saturating)
5514 .unwrap_or(0);
5515 let disposition = cancel_disposition(cx.cancel_reason());
5516 if disposition == CancelDisposition::Close {
5517 self.core.recovery.mark_dead();
5520 self.dead = true;
5521 }
5522 Some(disposition.into_error(timeout_ms))
5523 }
5524
5525 async fn read_response_cancellable(
5531 &mut self,
5532 cx: &Cx,
5533 classic: bool,
5534 probe: impl Fn(&[u8]) -> bool,
5535 ) -> Result<Vec<u8>> {
5536 let recovery = Arc::clone(&self.core.recovery);
5540 let mut guard = CancelDrainGuard::arm(recovery)?;
5541 let response = match self
5542 .core
5543 .read_data_response_probed(cx, classic, probe)
5544 .await
5545 {
5546 Ok(response) => response,
5547 Err(err) => {
5548 if let Some(cancelled) = self.in_flight_read_cancellation_error(cx, &err) {
5549 return Err(cancelled);
5550 }
5551 return Err(err);
5552 }
5553 };
5554 guard.disarm();
5555 Ok(response)
5556 }
5557
5558 async fn read_pipeline_response_cancellable(&mut self, cx: &Cx) -> Result<DataResponse> {
5564 let recovery = Arc::clone(&self.core.recovery);
5565 let mut guard = CancelDrainGuard::arm(recovery)?;
5566 let response = match self.core.read_data_response_boundary(cx, true).await {
5567 Ok(response) => response,
5568 Err(err) => {
5569 if let Some(cancelled) = self.in_flight_read_cancellation_error(cx, &err) {
5570 return Err(cancelled);
5571 }
5572 return Err(err);
5573 }
5574 };
5575 guard.disarm();
5576 Ok(response)
5577 }
5578
5579 async fn read_flushing_out_binds_cancellable(
5584 &mut self,
5585 cx: &Cx,
5586 classic: bool,
5587 probe: impl Fn(&[u8]) -> bool,
5588 ) -> Result<Vec<u8>> {
5589 let recovery = Arc::clone(&self.core.recovery);
5590 let mut guard = CancelDrainGuard::arm(recovery)?;
5591 let response = match self
5592 .core
5593 .read_data_response_flushing_out_binds_probed(cx, self.sdu, classic, probe)
5594 .await
5595 {
5596 Ok(response) => response,
5597 Err(err) => {
5598 if let Some(cancelled) = self.in_flight_read_cancellation_error(cx, &err) {
5599 return Err(cancelled);
5600 }
5601 return Err(err);
5602 }
5603 };
5604 guard.disarm();
5605 Ok(response)
5606 }
5607
5608 pub async fn fetch_rows(
5609 &mut self,
5610 cx: &Cx,
5611 cursor_id: u32,
5612 arraysize: u32,
5613 previous_row: Option<&[Option<oracledb_protocol::thin::QueryValue>]>,
5614 ) -> Result<QueryResult> {
5615 self.fetch_rows_with_columns(cx, cursor_id, arraysize, &[], previous_row)
5616 .await
5617 }
5618
5619 pub async fn fetch_rows_with_columns(
5620 &mut self,
5621 cx: &Cx,
5622 cursor_id: u32,
5623 arraysize: u32,
5624 known_columns: &[ColumnMetadata],
5625 previous_row: Option<&[Option<oracledb_protocol::thin::QueryValue>]>,
5626 ) -> Result<QueryResult> {
5627 let _span = obs_span!(
5631 "oracledb.fetch",
5632 db.cursor_id = cursor_id as u64,
5633 db.arraysize = arraysize as u64,
5634 db.rows_fetched = tracing::field::Empty,
5635 );
5636 observe_cancellation_between_round_trips(cx)?;
5637 self.ensure_clean_before_request().await?;
5640 let columns = self
5641 .cursor_columns
5642 .get(&cursor_id)
5643 .cloned()
5644 .unwrap_or_else(|| known_columns.to_vec());
5645 let lob_prefetch = self.lob_prefetch_cursors.contains(&cursor_id);
5646 let seq_num = next_ttc_sequence(&mut self.ttc_seq_num);
5647 let payload = if lob_prefetch {
5648 build_define_fetch_payload_with_seq(
5649 cursor_id,
5650 arraysize,
5651 seq_num,
5652 &columns,
5653 self.capabilities.ttc_field_version,
5654 )?
5655 } else {
5656 build_fetch_payload_with_seq(
5657 cursor_id,
5658 arraysize,
5659 seq_num,
5660 self.capabilities.ttc_field_version,
5661 )
5662 };
5663 trace_query_bytes("FETCH payload", &payload);
5664 self.core.send_data_packet(cx, &payload, self.sdu).await?;
5665 let capabilities = self.capabilities;
5670 let limits = self.protocol_limits;
5671 let classic = !self.supports_end_of_response;
5672 let profile = fetch_profile::enabled();
5673 let read_start = profile.then(time::wall_now);
5674 let response = self
5675 .read_response_cancellable(cx, classic, |bytes| {
5676 response_complete(&if lob_prefetch {
5677 parse_define_fetch_response_with_context_and_limits(
5678 bytes,
5679 capabilities,
5680 &columns,
5681 previous_row,
5682 limits,
5683 )
5684 } else {
5685 parse_fetch_response_with_context_and_limits(
5686 bytes,
5687 capabilities,
5688 &columns,
5689 previous_row,
5690 limits,
5691 )
5692 })
5693 })
5694 .await?;
5695 if let Some(start) = read_start {
5696 fetch_profile::add_read(time::wall_now().duration_since(start));
5697 }
5698 trace_query_bytes("FETCH response", &response);
5699 let decode_start = profile.then(time::wall_now);
5700 let parsed = if lob_prefetch {
5701 parse_define_fetch_response_with_context_and_limits(
5702 &response,
5703 self.capabilities,
5704 &columns,
5705 previous_row,
5706 self.protocol_limits,
5707 )
5708 } else {
5709 parse_fetch_response_with_context_and_limits(
5710 &response,
5711 self.capabilities,
5712 &columns,
5713 previous_row,
5714 self.protocol_limits,
5715 )
5716 };
5717 if let Some(start) = decode_start {
5718 fetch_profile::add_decode(time::wall_now().duration_since(start));
5719 }
5720 let result = self.note_parse(parsed)?;
5721 obs_record!(_span, db.rows_fetched = result.rows.len() as u64);
5722 self.remember_cursor_columns(&result);
5723 Ok(result)
5724 }
5725
5726 pub async fn fetch_rows_ref(
5738 &mut self,
5739 cx: &Cx,
5740 cursor_id: u32,
5741 arraysize: u32,
5742 previous_row: Option<&[Option<oracledb_protocol::thin::QueryValue>]>,
5743 ) -> Result<BorrowedFetchResult> {
5744 observe_cancellation_between_round_trips(cx)?;
5745 self.ensure_clean_before_request().await?;
5746 let columns = self
5747 .cursor_columns
5748 .get(&cursor_id)
5749 .cloned()
5750 .unwrap_or_default();
5751 let lob_prefetch = self.lob_prefetch_cursors.contains(&cursor_id);
5752 let seq_num = next_ttc_sequence(&mut self.ttc_seq_num);
5753 let payload = if lob_prefetch {
5754 build_define_fetch_payload_with_seq(
5755 cursor_id,
5756 arraysize,
5757 seq_num,
5758 &columns,
5759 self.capabilities.ttc_field_version,
5760 )?
5761 } else {
5762 build_fetch_payload_with_seq(
5763 cursor_id,
5764 arraysize,
5765 seq_num,
5766 self.capabilities.ttc_field_version,
5767 )
5768 };
5769 trace_query_bytes("FETCH payload", &payload);
5770 self.core.send_data_packet(cx, &payload, self.sdu).await?;
5771 let capabilities = self.capabilities;
5775 let limits = self.protocol_limits;
5776 let classic = !self.supports_end_of_response;
5777 let profile = fetch_profile::enabled();
5778 let read_start = profile.then(time::wall_now);
5779 let response = self
5780 .read_response_cancellable(cx, classic, |bytes| {
5781 if lob_prefetch {
5782 response_complete(&parse_define_fetch_response_borrowed_with_limits(
5783 bytes,
5784 capabilities,
5785 &columns,
5786 previous_row,
5787 limits,
5788 ))
5789 } else {
5790 response_complete(&parse_query_response_borrowed_with_limits(
5791 bytes,
5792 capabilities,
5793 &columns,
5794 previous_row,
5795 limits,
5796 ))
5797 }
5798 })
5799 .await?;
5800 if let Some(start) = read_start {
5801 fetch_profile::add_read(time::wall_now().duration_since(start));
5802 }
5803 trace_query_bytes("FETCH response", &response);
5804 let decode_start = profile.then(time::wall_now);
5805 let parsed = if lob_prefetch {
5806 parse_define_fetch_response_borrowed_with_limits(
5807 &response,
5808 self.capabilities,
5809 &columns,
5810 previous_row,
5811 self.protocol_limits,
5812 )
5813 } else {
5814 parse_query_response_borrowed_with_limits(
5815 &response,
5816 self.capabilities,
5817 &columns,
5818 previous_row,
5819 self.protocol_limits,
5820 )
5821 };
5822 if let Some(start) = decode_start {
5823 fetch_profile::add_decode(time::wall_now().duration_since(start));
5824 }
5825 let result = self.note_parse(parsed)?;
5826 if cursor_id != 0 && !result.batch.columns().is_empty() {
5832 self.cursor_columns
5833 .insert(cursor_id, result.batch.columns().to_vec());
5834 }
5835 Ok(result)
5836 }
5837
5838 pub async fn fetch_rows_request(
5857 &mut self,
5858 cx: &Cx,
5859 cursor_id: u32,
5860 arraysize: u32,
5861 ) -> Result<()> {
5862 observe_cancellation_between_round_trips(cx)?;
5863 self.ensure_clean_before_request().await?;
5866 let columns = self
5867 .cursor_columns
5868 .get(&cursor_id)
5869 .cloned()
5870 .unwrap_or_default();
5871 let lob_prefetch = self.lob_prefetch_cursors.contains(&cursor_id);
5872 let seq_num = next_ttc_sequence(&mut self.ttc_seq_num);
5873 let payload = if lob_prefetch {
5874 build_define_fetch_payload_with_seq(
5875 cursor_id,
5876 arraysize,
5877 seq_num,
5878 &columns,
5879 self.capabilities.ttc_field_version,
5880 )?
5881 } else {
5882 build_fetch_payload_with_seq(
5883 cursor_id,
5884 arraysize,
5885 seq_num,
5886 self.capabilities.ttc_field_version,
5887 )
5888 };
5889 trace_query_bytes("FETCH payload (prefetch)", &payload);
5890 self.core.recovery.begin_operation()?;
5891 match self.core.send_data_packet(cx, &payload, self.sdu).await {
5892 Ok(()) => Ok(()),
5893 Err(err) => {
5894 self.core.recovery.mark_dead();
5895 self.dead = true;
5896 Err(err)
5897 }
5898 }?;
5899 Ok(())
5904 }
5905
5906 pub async fn fetch_rows_ref_response(
5918 &mut self,
5919 cx: &Cx,
5920 cursor_id: u32,
5921 previous_row: Option<&[Option<oracledb_protocol::thin::QueryValue>]>,
5922 ) -> Result<BorrowedFetchResult> {
5923 observe_cancellation_between_round_trips(cx)?;
5924 let columns = self
5929 .cursor_columns
5930 .get(&cursor_id)
5931 .cloned()
5932 .unwrap_or_default();
5933 let lob_prefetch = self.lob_prefetch_cursors.contains(&cursor_id);
5934 let capabilities = self.capabilities;
5937 let limits = self.protocol_limits;
5938 let classic = !self.supports_end_of_response;
5939 let profile = fetch_profile::enabled();
5940 let read_start = profile.then(time::wall_now);
5941 let response = self
5942 .read_response_cancellable(cx, classic, |bytes| {
5943 if lob_prefetch {
5944 response_complete(&parse_define_fetch_response_borrowed_with_limits(
5945 bytes,
5946 capabilities,
5947 &columns,
5948 previous_row,
5949 limits,
5950 ))
5951 } else {
5952 response_complete(&parse_query_response_borrowed_with_limits(
5953 bytes,
5954 capabilities,
5955 &columns,
5956 previous_row,
5957 limits,
5958 ))
5959 }
5960 })
5961 .await?;
5962 if let Some(start) = read_start {
5963 fetch_profile::add_read(time::wall_now().duration_since(start));
5964 }
5965 trace_query_bytes("FETCH response (prefetch)", &response);
5966 let decode_start = profile.then(time::wall_now);
5967 let parsed = if lob_prefetch {
5968 parse_define_fetch_response_borrowed_with_limits(
5969 &response,
5970 self.capabilities,
5971 &columns,
5972 previous_row,
5973 self.protocol_limits,
5974 )
5975 } else {
5976 parse_query_response_borrowed_with_limits(
5977 &response,
5978 self.capabilities,
5979 &columns,
5980 previous_row,
5981 self.protocol_limits,
5982 )
5983 };
5984 if let Some(start) = decode_start {
5985 fetch_profile::add_decode(time::wall_now().duration_since(start));
5986 }
5987 let result = self.note_parse(parsed)?;
5988 if cursor_id != 0 && !result.batch.columns().is_empty() {
5989 self.cursor_columns
5990 .insert(cursor_id, result.batch.columns().to_vec());
5991 }
5992 Ok(result)
5993 }
5994
5995 pub async fn for_each_row_ref<F>(
6014 &mut self,
6015 cx: &Cx,
6016 sql: &str,
6017 arraysize: u32,
6018 mut callback: F,
6019 ) -> Result<()>
6020 where
6021 F: FnMut(&[Option<QueryValueRef<'_>>]) -> Result<()>,
6022 {
6023 let _stream_span = obs_span!(
6031 "oracledb.stream",
6032 db.statement = %crate::obs::sql_digest(sql),
6033 db.arraysize = arraysize as u64,
6034 db.rows_streamed = tracing::field::Empty,
6035 db.pages_fetched = tracing::field::Empty,
6036 db.prefetch_inflight_max = tracing::field::Empty,
6037 );
6038 #[cfg(feature = "tracing")]
6039 let mut rows_streamed: u64 = 0;
6040 #[cfg(feature = "tracing")]
6041 let mut pages_fetched: u64 = 0;
6042 #[cfg(feature = "tracing")]
6043 let mut prefetch_inflight_max: u64 = 0;
6044
6045 let first = self
6051 .execute_query_with_bind_rows_and_options_core(
6052 cx,
6053 sql,
6054 arraysize,
6055 &[],
6056 ExecuteOptions::default(),
6057 )
6058 .await?;
6059 let cursor_id = first.cursor_id;
6060 let mut cursor = BorrowedStreamCursorGuard::new(self, cursor_id);
6061 if cursor_id != 0 && columns_have_lob_prefetch_fields(&first.columns) {
6062 cursor.connection().lob_prefetch_cursors.insert(cursor_id);
6063 }
6064
6065 for row in &first.rows {
6070 let refs: Vec<Option<QueryValueRef<'_>>> = row
6071 .iter()
6072 .map(|cell| cell.as_ref().map(QueryValueRef::Owned))
6073 .collect();
6074 callback(&refs)?;
6075 }
6076 #[cfg(feature = "tracing")]
6077 {
6078 rows_streamed += first.rows.len() as u64;
6079 }
6080
6081 let mut more_rows = first.more_rows;
6082 let mut previous_row: Option<Vec<Option<oracledb_protocol::thin::QueryValue>>> =
6083 first.rows.last().cloned();
6084
6085 if more_rows && cursor_id != 0 {
6107 cursor
6108 .connection()
6109 .fetch_rows_request(cx, cursor_id, arraysize)
6110 .await?;
6111 #[cfg(feature = "tracing")]
6112 {
6113 prefetch_inflight_max = 1;
6115 }
6116 }
6117
6118 while more_rows && cursor_id != 0 {
6119 let result = cursor
6121 .connection()
6122 .fetch_rows_ref_response(cx, cursor_id, previous_row.as_deref())
6123 .await?;
6124 let next_more = result.more_rows;
6125
6126 if next_more {
6131 cursor
6132 .connection()
6133 .fetch_rows_request(cx, cursor_id, arraysize)
6134 .await?;
6135 }
6136
6137 let row_count = result.batch.row_count();
6145 #[cfg(feature = "tracing")]
6146 {
6147 pages_fetched += 1;
6148 rows_streamed += row_count as u64;
6149 if next_more {
6150 prefetch_inflight_max = prefetch_inflight_max.max(1);
6151 }
6152 }
6153 let mut last_owned: Option<Vec<Option<oracledb_protocol::thin::QueryValue>>> = None;
6154 let mut row_idx = 0usize;
6155 result.batch.for_each_row_ref(|row| {
6156 if row_idx + 1 == row_count {
6157 last_owned = Some(
6158 row.iter()
6159 .map(|cell| cell.map(|v| v.to_owned_value()))
6160 .collect(),
6161 );
6162 }
6163 row_idx += 1;
6164 callback(row)
6165 })?;
6166 if let Some(last) = last_owned {
6167 previous_row = Some(last);
6168 }
6169 more_rows = next_more;
6170 }
6171
6172 obs_record!(_stream_span, db.rows_streamed = rows_streamed);
6173 obs_record!(_stream_span, db.pages_fetched = pages_fetched);
6174 obs_record!(
6175 _stream_span,
6176 db.prefetch_inflight_max = prefetch_inflight_max
6177 );
6178 cursor.release();
6179 Ok(())
6180 }
6181
6182 pub async fn define_and_fetch_rows_with_columns(
6183 &mut self,
6184 cx: &Cx,
6185 cursor_id: u32,
6186 arraysize: u32,
6187 define_columns: &[ColumnMetadata],
6188 previous_row: Option<&[Option<oracledb_protocol::thin::QueryValue>]>,
6189 ) -> Result<QueryResult> {
6190 observe_cancellation_between_round_trips(cx)?;
6191 self.ensure_clean_before_request().await?;
6192 let seq_num = next_ttc_sequence(&mut self.ttc_seq_num);
6193 let payload = build_define_fetch_payload_with_seq(
6194 cursor_id,
6195 arraysize,
6196 seq_num,
6197 define_columns,
6198 self.capabilities.ttc_field_version,
6199 )?;
6200 trace_query_bytes("DEFINE FETCH payload", &payload);
6201 self.core.send_data_packet(cx, &payload, self.sdu).await?;
6202 let capabilities = self.capabilities;
6203 let limits = self.protocol_limits;
6204 let response = self
6205 .read_response_cancellable(cx, !self.supports_end_of_response, |bytes| {
6206 response_complete(&parse_define_fetch_response_with_context_and_limits(
6207 bytes,
6208 capabilities,
6209 define_columns,
6210 previous_row,
6211 limits,
6212 ))
6213 })
6214 .await?;
6215 trace_query_bytes("DEFINE FETCH response", &response);
6216 let result = parse_define_fetch_response_with_context_and_limits(
6217 &response,
6218 self.capabilities,
6219 define_columns,
6220 previous_row,
6221 self.protocol_limits,
6222 )
6223 .map_err(Error::from)?;
6224 if columns_have_lob_prefetch_fields(define_columns) {
6225 self.lob_prefetch_cursors.insert(cursor_id);
6226 if result.cursor_id != 0 {
6227 self.lob_prefetch_cursors.insert(result.cursor_id);
6228 }
6229 } else {
6230 self.lob_prefetch_cursors.remove(&cursor_id);
6231 if result.cursor_id != 0 {
6232 self.lob_prefetch_cursors.remove(&result.cursor_id);
6233 }
6234 }
6235 self.cursor_columns
6236 .insert(cursor_id, define_columns.to_vec());
6237 self.remember_cursor_columns(&result);
6238 Ok(result)
6239 }
6240
6241 pub async fn fetch_cursor(
6253 &mut self,
6254 cx: &Cx,
6255 cursor: &oracledb_protocol::thin::CursorValue,
6256 max_rows: usize,
6257 ) -> Result<QueryResult> {
6258 const ARRAYSIZE: usize = 100;
6263 let fetch_size =
6264 |fetched: usize| -> u32 { max_rows.saturating_sub(fetched).clamp(1, ARRAYSIZE) as u32 };
6265 let mut rows: Vec<Vec<Option<oracledb_protocol::thin::QueryValue>>> = Vec::new();
6266 let mut batch = self
6269 .define_and_fetch_rows_with_columns(
6270 cx,
6271 cursor.cursor_id,
6272 fetch_size(0),
6273 &cursor.columns,
6274 None,
6275 )
6276 .await?;
6277 let mut more = batch.more_rows;
6278 let mut cid = if batch.cursor_id != 0 {
6279 batch.cursor_id
6280 } else {
6281 cursor.cursor_id
6282 };
6283 rows.append(&mut batch.rows);
6284 while more && cid != 0 && rows.len() < max_rows {
6286 observe_cancellation_between_round_trips(cx)?;
6287 let previous_row = rows.last().cloned();
6288 let mut next = self
6289 .fetch_rows_with_columns(
6290 cx,
6291 cid,
6292 fetch_size(rows.len()),
6293 &cursor.columns,
6294 previous_row.as_deref(),
6295 )
6296 .await?;
6297 more = next.more_rows;
6298 if next.cursor_id != 0 {
6299 cid = next.cursor_id;
6300 }
6301 rows.append(&mut next.rows);
6302 }
6303 rows.truncate(max_rows);
6304 self.release_cursor(cid);
6305 Ok(QueryResult {
6306 columns: cursor.columns.clone(),
6307 rows,
6308 ..Default::default()
6309 })
6310 }
6311
6312 pub async fn describe_object_type(
6320 &mut self,
6321 cx: &Cx,
6322 schema: &str,
6323 type_name: &str,
6324 ) -> Result<ObjectType> {
6325 let schema = schema.to_ascii_uppercase();
6326 let name = type_name.to_ascii_uppercase();
6327 let binds = || {
6328 vec![
6329 oracledb_protocol::thin::BindValue::Text(schema.clone()),
6330 oracledb_protocol::thin::BindValue::Text(name.clone()),
6331 ]
6332 };
6333 let row_text =
6334 |row: &[Option<oracledb_protocol::thin::QueryValue>], i: usize| -> Option<String> {
6335 match row.get(i) {
6336 Some(Some(v)) => {
6337 oracledb_protocol::thin::QueryValue::as_text(v).map(str::to_string)
6338 }
6339 _ => None,
6340 }
6341 };
6342
6343 let coll = self
6346 .execute_query_with_binds_core(
6347 cx,
6348 "select elem_type_name, elem_type_owner from all_coll_types \
6349 where owner = :1 and type_name = :2",
6350 10,
6351 &binds(),
6352 )
6353 .await?;
6354 if let Some(row) = coll.rows.first() {
6355 return Ok(ObjectType {
6356 schema,
6357 name,
6358 attributes: Vec::new(),
6359 collection_element: Some(CollectionElement {
6360 type_name: row_text(row, 0).unwrap_or_default(),
6361 type_owner: row_text(row, 1),
6362 }),
6363 });
6364 }
6365
6366 let res = self
6369 .execute_query_with_binds_core(
6370 cx,
6371 "select attr_name, attr_type_name, attr_type_owner from all_type_attrs \
6372 where owner = :1 and type_name = :2 order by attr_no",
6373 1000,
6374 &binds(),
6375 )
6376 .await?;
6377 let attributes: Vec<ObjectAttribute> = res
6378 .rows
6379 .iter()
6380 .map(|row| ObjectAttribute {
6381 name: row_text(row, 0).unwrap_or_default(),
6382 type_name: row_text(row, 1).unwrap_or_default(),
6383 type_owner: row_text(row, 2),
6384 })
6385 .collect();
6386 if attributes.is_empty() {
6387 return Err(Error::Protocol(
6388 oracledb_protocol::ProtocolError::UnsupportedFeature(
6389 "object type not found or has no attributes",
6390 ),
6391 ));
6392 }
6393 Ok(ObjectType {
6394 schema,
6395 name,
6396 attributes,
6397 collection_element: None,
6398 })
6399 }
6400
6401 pub async fn scroll_cursor(
6406 &mut self,
6407 cx: &Cx,
6408 sql: &str,
6409 cursor_id: u32,
6410 arraysize: u32,
6411 fetch_orientation: u32,
6412 fetch_pos: u32,
6413 ) -> Result<QueryResult> {
6414 observe_cancellation_between_round_trips(cx)?;
6415 self.ensure_clean_before_request().await?;
6416 let exec_options = ExecuteOptions::default()
6417 .with_max_string_size(self.capabilities.max_string_size)
6418 .with_cursor_id(cursor_id)
6419 .with_scrollable(true)
6420 .with_scroll_operation(true)
6421 .with_fetch_orientation(fetch_orientation)
6422 .with_fetch_pos(fetch_pos)
6423 .with_cache_statement(false);
6424 let piggyback = self.take_close_cursors_piggyback();
6425 let seq_num = next_ttc_sequence(&mut self.ttc_seq_num);
6426 let mut payload = build_execute_payload_with_bind_rows_and_options_with_seq(
6427 sql,
6428 arraysize,
6429 seq_num,
6430 true,
6431 &[],
6432 exec_options,
6433 self.capabilities.ttc_field_version,
6434 )?;
6435 if let Some(mut piggyback_bytes) = piggyback {
6436 piggyback_bytes.extend_from_slice(&payload);
6437 payload = piggyback_bytes;
6438 }
6439 trace_query_bytes("SCROLL payload", &payload);
6440 self.core.send_data_packet(cx, &payload, self.sdu).await?;
6441 let known_columns = self
6442 .cursor_columns
6443 .get(&cursor_id)
6444 .cloned()
6445 .unwrap_or_default();
6446 let capabilities = self.capabilities;
6449 let limits = self.protocol_limits;
6450 let response = self
6451 .read_flushing_out_binds_cancellable(cx, !self.supports_end_of_response, |bytes| {
6452 response_complete(&parse_query_response_with_binds_options_columns_and_limits(
6453 bytes,
6454 capabilities,
6455 &[],
6456 exec_options,
6457 &known_columns,
6458 limits,
6459 ))
6460 })
6461 .await?;
6462 trace_query_bytes("SCROLL response", &response);
6463 let parsed = parse_query_response_with_binds_options_columns_and_limits(
6464 &response,
6465 self.capabilities,
6466 &[],
6467 exec_options,
6468 &known_columns,
6469 self.protocol_limits,
6470 );
6471 let result = self.note_parse(parsed)?;
6472 self.remember_cursor_columns(&result);
6473 Ok(result)
6474 }
6475
6476 pub async fn read_lob(
6483 &mut self,
6484 cx: &Cx,
6485 locator: &[u8],
6486 offset: u64,
6487 amount: u64,
6488 ) -> Result<LobReadResult> {
6489 let _span = obs_span!(
6492 "oracledb.lob",
6493 db.operation = "read",
6494 db.lob_offset = offset,
6495 db.lob_amount = amount,
6496 );
6497 observe_cancellation_between_round_trips(cx)?;
6498 self.ensure_clean_before_request().await?;
6499 let seq_num = next_ttc_sequence(&mut self.ttc_seq_num);
6500 let payload = build_lob_read_payload_with_seq(
6501 locator,
6502 offset,
6503 amount,
6504 seq_num,
6505 self.capabilities.ttc_field_version,
6506 )?;
6507 trace_query_bytes("LOB READ payload", &payload);
6508 self.core.send_data_packet(cx, &payload, self.sdu).await?;
6509 let capabilities = self.capabilities;
6510 let limits = self.protocol_limits;
6511 let response = self
6512 .read_response_cancellable(cx, !self.supports_end_of_response, |bytes| {
6513 response_complete(&parse_lob_read_response_with_limits(
6514 bytes,
6515 capabilities,
6516 locator,
6517 limits,
6518 ))
6519 })
6520 .await?;
6521 trace_query_bytes("LOB READ response", &response);
6522 self.note_parse(parse_lob_read_response_with_limits(
6523 &response,
6524 self.capabilities,
6525 locator,
6526 self.protocol_limits,
6527 ))
6528 }
6529
6530 pub async fn read_lob_with_timeout(
6531 &mut self,
6532 cx: &Cx,
6533 locator: &[u8],
6534 offset: u64,
6535 amount: u64,
6536 timeout_ms: Option<u32>,
6537 ) -> Result<LobReadResult> {
6538 self.read_lob_call_timeout(cx, locator, offset, amount, timeout_ms)
6539 .await
6540 }
6541
6542 pub async fn aq_enq_one(
6545 &mut self,
6546 cx: &Cx,
6547 queue: &AqQueueDesc,
6548 props: &AqMsgProps,
6549 enq_options: &AqEnqOptions,
6550 ) -> Result<Option<Vec<u8>>> {
6551 observe_cancellation_between_round_trips(cx)?;
6552 self.ensure_clean_before_request().await?;
6553 self.protocol_limits.check_frame_bytes(queue.name.len())?;
6554 self.protocol_limits
6555 .check_frame_bytes(queue.payload_toid.len())?;
6556 let seq_num = next_ttc_sequence(&mut self.ttc_seq_num);
6557 let payload = build_aq_enq_payload(
6558 queue,
6559 props,
6560 enq_options,
6561 seq_num,
6562 self.capabilities.ttc_field_version,
6563 self.supports_oson_long_fnames(),
6564 )?;
6565 trace_query_bytes("AQ ENQ payload", &payload);
6566 self.core.send_data_packet(cx, &payload, self.sdu).await?;
6567 let capabilities = self.capabilities;
6570 let limits = self.protocol_limits;
6571 let response = self
6572 .read_response_cancellable(cx, !self.supports_end_of_response, |bytes| {
6573 response_complete(&parse_aq_enq_response_with_limits(
6574 bytes,
6575 capabilities,
6576 limits,
6577 ))
6578 })
6579 .await?;
6580 trace_query_bytes("AQ ENQ response", &response);
6581 self.note_parse(parse_aq_enq_response_with_limits(
6582 &response,
6583 self.capabilities,
6584 self.protocol_limits,
6585 ))
6586 }
6587
6588 pub async fn aq_deq_one(
6591 &mut self,
6592 cx: &Cx,
6593 queue: &AqQueueDesc,
6594 deq_options: &AqDeqOptions,
6595 ) -> Result<AqDeqResult> {
6596 observe_cancellation_between_round_trips(cx)?;
6597 self.ensure_clean_before_request().await?;
6598 self.protocol_limits.check_frame_bytes(queue.name.len())?;
6599 self.protocol_limits
6600 .check_frame_bytes(queue.payload_toid.len())?;
6601 let seq_num = next_ttc_sequence(&mut self.ttc_seq_num);
6602 let payload = build_aq_deq_payload(
6603 queue,
6604 deq_options,
6605 seq_num,
6606 self.capabilities.ttc_field_version,
6607 )?;
6608 trace_query_bytes("AQ DEQ payload", &payload);
6609 self.core.send_data_packet(cx, &payload, self.sdu).await?;
6610 let capabilities = self.capabilities;
6613 let limits = self.protocol_limits;
6614 let response = self
6615 .read_response_cancellable(cx, !self.supports_end_of_response, |bytes| {
6616 response_complete(&parse_aq_deq_response_with_limits(
6617 bytes,
6618 capabilities,
6619 &queue.kind,
6620 limits,
6621 ))
6622 })
6623 .await?;
6624 trace_query_bytes("AQ DEQ response", &response);
6625 self.note_parse(parse_aq_deq_response_with_limits(
6626 &response,
6627 self.capabilities,
6628 &queue.kind,
6629 self.protocol_limits,
6630 ))
6631 }
6632
6633 pub async fn aq_enq_many(
6636 &mut self,
6637 cx: &Cx,
6638 queue: &AqQueueDesc,
6639 props_list: &[AqMsgProps],
6640 enq_options: &AqEnqOptions,
6641 ) -> Result<Vec<Vec<u8>>> {
6642 observe_cancellation_between_round_trips(cx)?;
6643 self.ensure_clean_before_request().await?;
6644 self.protocol_limits.check_batch_rows(props_list.len())?;
6645 self.protocol_limits.check_frame_bytes(queue.name.len())?;
6646 self.protocol_limits
6647 .check_frame_bytes(queue.payload_toid.len())?;
6648 let seq_num = next_ttc_sequence(&mut self.ttc_seq_num);
6649 let payload = build_aq_array_enq_payload(
6650 queue,
6651 props_list,
6652 enq_options,
6653 seq_num,
6654 self.capabilities.ttc_field_version,
6655 self.supports_oson_long_fnames(),
6656 )?;
6657 trace_query_bytes("AQ ARRAY ENQ payload", &payload);
6658 self.core.send_data_packet(cx, &payload, self.sdu).await?;
6659 let capabilities = self.capabilities;
6662 let limits = self.protocol_limits;
6663 let response = self
6664 .read_response_cancellable(cx, !self.supports_end_of_response, |bytes| {
6665 response_complete(&parse_aq_array_response_with_limits(
6666 bytes,
6667 capabilities,
6668 TNS_AQ_ARRAY_ENQ,
6669 props_list.len() as u32,
6670 &queue.kind,
6671 limits,
6672 ))
6673 })
6674 .await?;
6675 trace_query_bytes("AQ ARRAY ENQ response", &response);
6676 let result: AqArrayResult = self.note_parse(parse_aq_array_response_with_limits(
6677 &response,
6678 self.capabilities,
6679 TNS_AQ_ARRAY_ENQ,
6680 props_list.len() as u32,
6681 &queue.kind,
6682 self.protocol_limits,
6683 ))?;
6684 Ok(result.enq_msgids)
6685 }
6686
6687 pub async fn aq_deq_many(
6690 &mut self,
6691 cx: &Cx,
6692 queue: &AqQueueDesc,
6693 deq_options: &AqDeqOptions,
6694 max_num_messages: u32,
6695 ) -> Result<Vec<oracledb_protocol::thin::aq::AqDeqMessage>> {
6696 observe_cancellation_between_round_trips(cx)?;
6697 self.ensure_clean_before_request().await?;
6698 self.protocol_limits
6699 .check_batch_rows(max_num_messages as usize)?;
6700 self.protocol_limits.check_frame_bytes(queue.name.len())?;
6701 self.protocol_limits
6702 .check_frame_bytes(queue.payload_toid.len())?;
6703 let seq_num = next_ttc_sequence(&mut self.ttc_seq_num);
6704 let payload = build_aq_array_deq_payload(
6705 queue,
6706 deq_options,
6707 max_num_messages,
6708 seq_num,
6709 self.capabilities.ttc_field_version,
6710 )?;
6711 trace_query_bytes("AQ ARRAY DEQ payload", &payload);
6712 self.core.send_data_packet(cx, &payload, self.sdu).await?;
6713 let capabilities = self.capabilities;
6716 let limits = self.protocol_limits;
6717 let response = self
6718 .read_response_cancellable(cx, !self.supports_end_of_response, |bytes| {
6719 response_complete(&parse_aq_array_response_with_limits(
6720 bytes,
6721 capabilities,
6722 TNS_AQ_ARRAY_DEQ,
6723 max_num_messages,
6724 &queue.kind,
6725 limits,
6726 ))
6727 })
6728 .await?;
6729 trace_query_bytes("AQ ARRAY DEQ response", &response);
6730 let result: AqArrayResult = self.note_parse(parse_aq_array_response_with_limits(
6731 &response,
6732 self.capabilities,
6733 TNS_AQ_ARRAY_DEQ,
6734 max_num_messages,
6735 &queue.kind,
6736 self.protocol_limits,
6737 ))?;
6738 Ok(result.deq_messages)
6739 }
6740
6741 pub async fn create_temp_lob(
6742 &mut self,
6743 cx: &Cx,
6744 ora_type_num: u8,
6745 csfrm: u8,
6746 ) -> Result<LobReadResult> {
6747 observe_cancellation_between_round_trips(cx)?;
6748 self.ensure_clean_before_request().await?;
6749 let seq_num = next_ttc_sequence(&mut self.ttc_seq_num);
6750 let payload = build_lob_create_temp_payload_with_seq(
6751 ora_type_num,
6752 csfrm,
6753 seq_num,
6754 self.capabilities.ttc_field_version,
6755 )?;
6756 trace_query_bytes("LOB CREATE TEMP payload", &payload);
6757 self.core.send_data_packet(cx, &payload, self.sdu).await?;
6758 let capabilities = self.capabilities;
6759 let limits = self.protocol_limits;
6760 let response = self
6761 .read_response_cancellable(cx, !self.supports_end_of_response, |bytes| {
6762 response_complete(&parse_lob_create_temp_response_with_limits(
6763 bytes,
6764 capabilities,
6765 limits,
6766 ))
6767 })
6768 .await?;
6769 trace_query_bytes("LOB CREATE TEMP response", &response);
6770 self.note_parse(parse_lob_create_temp_response_with_limits(
6771 &response,
6772 self.capabilities,
6773 self.protocol_limits,
6774 ))
6775 }
6776
6777 pub async fn write_lob(
6778 &mut self,
6779 cx: &Cx,
6780 locator: &[u8],
6781 offset: u64,
6782 data: &[u8],
6783 ) -> Result<LobReadResult> {
6784 let _span = obs_span!(
6787 "oracledb.lob",
6788 db.operation = "write",
6789 db.lob_offset = offset,
6790 db.lob_bytes = data.len() as u64,
6791 );
6792 observe_cancellation_between_round_trips(cx)?;
6793 self.ensure_clean_before_request().await?;
6794 self.protocol_limits.check_frame_bytes(locator.len())?;
6795 self.protocol_limits.check_frame_bytes(data.len())?;
6796 let seq_num = next_ttc_sequence(&mut self.ttc_seq_num);
6797 let payload = build_lob_write_payload_with_seq(
6798 locator,
6799 offset,
6800 data,
6801 seq_num,
6802 self.capabilities.ttc_field_version,
6803 )?;
6804 trace_query_bytes("LOB WRITE payload", &payload);
6805 self.core.send_data_packet(cx, &payload, self.sdu).await?;
6806 let capabilities = self.capabilities;
6807 let limits = self.protocol_limits;
6808 let response = self
6809 .read_response_cancellable(cx, !self.supports_end_of_response, |bytes| {
6810 response_complete(&parse_lob_write_response_with_limits(
6811 bytes,
6812 capabilities,
6813 locator,
6814 limits,
6815 ))
6816 })
6817 .await?;
6818 trace_query_bytes("LOB WRITE response", &response);
6819 self.note_parse(parse_lob_write_response_with_limits(
6820 &response,
6821 self.capabilities,
6822 locator,
6823 self.protocol_limits,
6824 ))
6825 }
6826
6827 pub async fn write_lob_with_timeout(
6828 &mut self,
6829 cx: &Cx,
6830 locator: &[u8],
6831 offset: u64,
6832 data: &[u8],
6833 timeout_ms: Option<u32>,
6834 ) -> Result<LobReadResult> {
6835 self.write_lob_call_timeout(cx, locator, offset, data, timeout_ms)
6836 .await
6837 }
6838
6839 pub async fn trim_lob(
6840 &mut self,
6841 cx: &Cx,
6842 locator: &[u8],
6843 new_size: u64,
6844 ) -> Result<LobReadResult> {
6845 observe_cancellation_between_round_trips(cx)?;
6846 self.ensure_clean_before_request().await?;
6847 self.protocol_limits.check_frame_bytes(locator.len())?;
6848 let seq_num = next_ttc_sequence(&mut self.ttc_seq_num);
6849 let payload = build_lob_trim_payload_with_seq(
6850 locator,
6851 new_size,
6852 seq_num,
6853 self.capabilities.ttc_field_version,
6854 )?;
6855 trace_query_bytes("LOB TRIM payload", &payload);
6856 self.core.send_data_packet(cx, &payload, self.sdu).await?;
6857 let capabilities = self.capabilities;
6858 let limits = self.protocol_limits;
6859 let response = self
6860 .read_response_cancellable(cx, !self.supports_end_of_response, |bytes| {
6861 response_complete(&parse_lob_trim_response_with_limits(
6862 bytes,
6863 capabilities,
6864 locator,
6865 limits,
6866 ))
6867 })
6868 .await?;
6869 trace_query_bytes("LOB TRIM response", &response);
6870 self.note_parse(parse_lob_trim_response_with_limits(
6871 &response,
6872 self.capabilities,
6873 locator,
6874 self.protocol_limits,
6875 ))
6876 }
6877
6878 pub async fn trim_lob_with_timeout(
6879 &mut self,
6880 cx: &Cx,
6881 locator: &[u8],
6882 new_size: u64,
6883 timeout_ms: Option<u32>,
6884 ) -> Result<LobReadResult> {
6885 self.trim_lob_call_timeout(cx, locator, new_size, timeout_ms)
6886 .await
6887 }
6888
6889 pub async fn free_temp_lobs(&mut self, cx: &Cx, locators: &[Vec<u8>]) -> Result<()> {
6890 observe_cancellation_between_round_trips(cx)?;
6891 if locators.is_empty() {
6892 return Ok(());
6893 }
6894 self.ensure_clean_before_request().await?;
6895 self.protocol_limits.check_lob_chunks(locators.len())?;
6896 let returned_parameter_len = locators.iter().try_fold(0usize, |total, locator| {
6897 self.protocol_limits.check_frame_bytes(locator.len())?;
6898 total.checked_add(locator.len()).ok_or(
6899 oracledb_protocol::ProtocolError::ResourceLimit {
6900 limit: "frame_bytes",
6901 observed: usize::MAX,
6902 maximum: self.protocol_limits.max_frame_bytes,
6903 },
6904 )
6905 })?;
6906 self.protocol_limits
6907 .check_frame_bytes(returned_parameter_len)?;
6908 let seq_num = next_ttc_sequence(&mut self.ttc_seq_num);
6909 let payload = build_lob_free_temp_payload_with_seq(
6910 locators,
6911 seq_num,
6912 self.capabilities.ttc_field_version,
6913 )?;
6914 trace_query_bytes("LOB FREE TEMP payload", &payload);
6915 self.core.send_data_packet(cx, &payload, self.sdu).await?;
6916 let capabilities = self.capabilities;
6917 let limits = self.protocol_limits;
6918 let response = self
6919 .read_response_cancellable(cx, !self.supports_end_of_response, |bytes| {
6920 response_complete(&parse_lob_free_temp_response_with_limits(
6921 bytes,
6922 capabilities,
6923 returned_parameter_len,
6924 limits,
6925 ))
6926 })
6927 .await?;
6928 trace_query_bytes("LOB FREE TEMP response", &response);
6929 self.note_parse(parse_lob_free_temp_response_with_limits(
6930 &response,
6931 self.capabilities,
6932 returned_parameter_len,
6933 self.protocol_limits,
6934 ))
6935 }
6936
6937 pub async fn free_temp_lobs_with_timeout(
6938 &mut self,
6939 cx: &Cx,
6940 locators: &[Vec<u8>],
6941 timeout_ms: Option<u32>,
6942 ) -> Result<()> {
6943 self.free_temp_lobs_call_timeout(cx, locators, timeout_ms)
6944 .await
6945 }
6946
6947 #[allow(dead_code)]
6948 async fn execute_query_call_timeout(
6949 &mut self,
6950 cx: &Cx,
6951 sql: &str,
6952 prefetch_rows: u32,
6953 timeout_ms: Option<u32>,
6954 ) -> Result<QueryResult> {
6955 let Some(timeout_ms) = timeout_ms.filter(|value| *value > 0) else {
6956 return self
6957 .execute_query_with_bind_rows_and_options_core(
6958 cx,
6959 sql,
6960 prefetch_rows,
6961 &[],
6962 ExecuteOptions::default(),
6963 )
6964 .await;
6965 };
6966 let deadline = QueryDeadline::from_timeout(Duration::from_millis(u64::from(timeout_ms)));
6967 match deadline
6968 .run(self.execute_query_with_bind_rows_and_options_core(
6969 cx,
6970 sql,
6971 prefetch_rows,
6972 &[],
6973 ExecuteOptions::default(),
6974 ))
6975 .await
6976 {
6977 Ok(result) => result,
6978 Err(DeadlineExpiry::BeforeStart) => self.reject_before_operation_start(cx, timeout_ms),
6979 Err(DeadlineExpiry::InFlight) => self.recover_from_call_timeout(cx, timeout_ms).await,
6980 }
6981 }
6982
6983 async fn execute_query_with_binds_call_timeout(
6984 &mut self,
6985 cx: &Cx,
6986 sql: &str,
6987 prefetch_rows: u32,
6988 binds: &[BindValue],
6989 timeout_ms: Option<u32>,
6990 ) -> Result<QueryResult> {
6991 let Some(timeout_ms) = timeout_ms.filter(|value| *value > 0) else {
6992 return self
6993 .execute_query_with_binds_core(cx, sql, prefetch_rows, binds)
6994 .await;
6995 };
6996 let deadline = QueryDeadline::from_timeout(Duration::from_millis(u64::from(timeout_ms)));
6997 match deadline
6998 .run(self.execute_query_with_binds_core(cx, sql, prefetch_rows, binds))
6999 .await
7000 {
7001 Ok(result) => result,
7002 Err(DeadlineExpiry::BeforeStart) => self.reject_before_operation_start(cx, timeout_ms),
7003 Err(DeadlineExpiry::InFlight) => self.recover_from_call_timeout(cx, timeout_ms).await,
7004 }
7005 }
7006
7007 #[allow(dead_code)]
7008 async fn execute_query_with_bind_rows_call_timeout(
7009 &mut self,
7010 cx: &Cx,
7011 sql: &str,
7012 prefetch_rows: u32,
7013 bind_rows: &[Vec<BindValue>],
7014 timeout_ms: Option<u32>,
7015 ) -> Result<QueryResult> {
7016 let Some(timeout_ms) = timeout_ms.filter(|value| *value > 0) else {
7017 return self
7018 .execute_query_with_bind_rows_and_options_core(
7019 cx,
7020 sql,
7021 prefetch_rows,
7022 bind_rows,
7023 ExecuteOptions::default(),
7024 )
7025 .await;
7026 };
7027 let deadline = QueryDeadline::from_timeout(Duration::from_millis(u64::from(timeout_ms)));
7028 match deadline
7029 .run(self.execute_query_with_bind_rows_and_options_core(
7030 cx,
7031 sql,
7032 prefetch_rows,
7033 bind_rows,
7034 ExecuteOptions::default(),
7035 ))
7036 .await
7037 {
7038 Ok(result) => result,
7039 Err(DeadlineExpiry::BeforeStart) => self.reject_before_operation_start(cx, timeout_ms),
7040 Err(DeadlineExpiry::InFlight) => self.recover_from_call_timeout(cx, timeout_ms).await,
7041 }
7042 }
7043
7044 async fn read_lob_call_timeout(
7045 &mut self,
7046 cx: &Cx,
7047 locator: &[u8],
7048 offset: u64,
7049 amount: u64,
7050 timeout_ms: Option<u32>,
7051 ) -> Result<LobReadResult> {
7052 let Some(timeout_ms) = timeout_ms.filter(|value| *value > 0) else {
7053 return self.read_lob(cx, locator, offset, amount).await;
7054 };
7055 let deadline = QueryDeadline::from_timeout(Duration::from_millis(u64::from(timeout_ms)));
7056 match deadline
7057 .run(self.read_lob(cx, locator, offset, amount))
7058 .await
7059 {
7060 Ok(result) => result,
7061 Err(DeadlineExpiry::BeforeStart) => self.reject_before_operation_start(cx, timeout_ms),
7062 Err(DeadlineExpiry::InFlight) => self.recover_from_call_timeout(cx, timeout_ms).await,
7063 }
7064 }
7065
7066 async fn write_lob_call_timeout(
7067 &mut self,
7068 cx: &Cx,
7069 locator: &[u8],
7070 offset: u64,
7071 data: &[u8],
7072 timeout_ms: Option<u32>,
7073 ) -> Result<LobReadResult> {
7074 let Some(timeout_ms) = timeout_ms.filter(|value| *value > 0) else {
7075 return self.write_lob(cx, locator, offset, data).await;
7076 };
7077 let deadline = QueryDeadline::from_timeout(Duration::from_millis(u64::from(timeout_ms)));
7078 match deadline
7079 .run(self.write_lob(cx, locator, offset, data))
7080 .await
7081 {
7082 Ok(result) => result,
7083 Err(DeadlineExpiry::BeforeStart) => self.reject_before_operation_start(cx, timeout_ms),
7084 Err(DeadlineExpiry::InFlight) => self.recover_from_call_timeout(cx, timeout_ms).await,
7085 }
7086 }
7087
7088 async fn trim_lob_call_timeout(
7089 &mut self,
7090 cx: &Cx,
7091 locator: &[u8],
7092 new_size: u64,
7093 timeout_ms: Option<u32>,
7094 ) -> Result<LobReadResult> {
7095 let Some(timeout_ms) = timeout_ms.filter(|value| *value > 0) else {
7096 return self.trim_lob(cx, locator, new_size).await;
7097 };
7098 let deadline = QueryDeadline::from_timeout(Duration::from_millis(u64::from(timeout_ms)));
7099 match deadline.run(self.trim_lob(cx, locator, new_size)).await {
7100 Ok(result) => result,
7101 Err(DeadlineExpiry::BeforeStart) => self.reject_before_operation_start(cx, timeout_ms),
7102 Err(DeadlineExpiry::InFlight) => self.recover_from_call_timeout(cx, timeout_ms).await,
7103 }
7104 }
7105
7106 async fn free_temp_lobs_call_timeout(
7107 &mut self,
7108 cx: &Cx,
7109 locators: &[Vec<u8>],
7110 timeout_ms: Option<u32>,
7111 ) -> Result<()> {
7112 let Some(timeout_ms) = timeout_ms.filter(|value| *value > 0) else {
7113 return self.free_temp_lobs(cx, locators).await;
7114 };
7115 let deadline = QueryDeadline::from_timeout(Duration::from_millis(u64::from(timeout_ms)));
7116 match deadline.run(self.free_temp_lobs(cx, locators)).await {
7117 Ok(result) => result,
7118 Err(DeadlineExpiry::BeforeStart) => self.reject_before_operation_start(cx, timeout_ms),
7119 Err(DeadlineExpiry::InFlight) => self.recover_from_call_timeout(cx, timeout_ms).await,
7120 }
7121 }
7122
7123 pub async fn direct_path_prepare(
7126 &mut self,
7127 cx: &Cx,
7128 schema_name: &str,
7129 table_name: &str,
7130 column_names: &[String],
7131 ) -> Result<oracledb_protocol::dpl::DirectPathPrepareResult> {
7132 observe_cancellation_between_round_trips(cx)?;
7133 self.ensure_clean_before_request().await?;
7134 self.protocol_limits.check_columns(column_names.len())?;
7135 let seq_num = next_ttc_sequence(&mut self.ttc_seq_num);
7136 let payload = oracledb_protocol::dpl::build_direct_path_prepare_payload_with_version(
7137 schema_name,
7138 table_name,
7139 column_names,
7140 seq_num,
7141 self.capabilities.ttc_field_version,
7142 )?;
7143 trace_query_bytes("DIRECT PATH PREPARE payload", &payload);
7144 self.core.send_data_packet(cx, &payload, self.sdu).await?;
7145 let capabilities = self.capabilities;
7148 let limits = self.protocol_limits;
7149 let response = self
7150 .read_response_cancellable(cx, !self.supports_end_of_response, |bytes| {
7151 response_complete(
7152 &oracledb_protocol::dpl::parse_direct_path_prepare_response_with_limits(
7153 bytes,
7154 capabilities,
7155 limits,
7156 ),
7157 )
7158 })
7159 .await?;
7160 trace_query_bytes("DIRECT PATH PREPARE response", &response);
7161 oracledb_protocol::dpl::parse_direct_path_prepare_response_with_limits(
7162 &response,
7163 self.capabilities,
7164 self.protocol_limits,
7165 )
7166 .map_err(Error::from)
7167 }
7168
7169 pub async fn direct_path_load_stream(
7171 &mut self,
7172 cx: &Cx,
7173 cursor_id: u16,
7174 stream: &oracledb_protocol::dpl::DirectPathStream,
7175 ) -> Result<()> {
7176 observe_cancellation_between_round_trips(cx)?;
7177 self.ensure_clean_before_request().await?;
7178 let seq_num = next_ttc_sequence(&mut self.ttc_seq_num);
7179 let payload = oracledb_protocol::dpl::build_direct_path_load_stream_payload_with_version(
7180 cursor_id,
7181 stream,
7182 seq_num,
7183 self.capabilities.ttc_field_version,
7184 )?;
7185 trace_query_bytes("DIRECT PATH LOAD STREAM payload", &payload);
7186 self.core.send_data_packet(cx, &payload, self.sdu).await?;
7187 let capabilities = self.capabilities;
7190 let limits = self.protocol_limits;
7191 let response = self
7192 .read_response_cancellable(cx, !self.supports_end_of_response, |bytes| {
7193 response_complete(
7194 &oracledb_protocol::dpl::parse_direct_path_simple_response_with_limits(
7195 bytes,
7196 capabilities,
7197 limits,
7198 ),
7199 )
7200 })
7201 .await?;
7202 trace_query_bytes("DIRECT PATH LOAD STREAM response", &response);
7203 oracledb_protocol::dpl::parse_direct_path_simple_response_with_limits(
7204 &response,
7205 self.capabilities,
7206 self.protocol_limits,
7207 )
7208 .map_err(Error::from)
7209 }
7210
7211 pub async fn direct_path_op(&mut self, cx: &Cx, cursor_id: u16, op_code: u32) -> Result<()> {
7215 observe_cancellation_between_round_trips(cx)?;
7216 self.ensure_clean_before_request().await?;
7217 let seq_num = next_ttc_sequence(&mut self.ttc_seq_num);
7218 let payload = oracledb_protocol::dpl::build_direct_path_op_payload_with_version(
7219 cursor_id,
7220 op_code,
7221 seq_num,
7222 self.capabilities.ttc_field_version,
7223 );
7224 trace_query_bytes("DIRECT PATH OP payload", &payload);
7225 self.core.send_data_packet(cx, &payload, self.sdu).await?;
7226 let capabilities = self.capabilities;
7229 let limits = self.protocol_limits;
7230 let response = self
7231 .read_response_cancellable(cx, !self.supports_end_of_response, |bytes| {
7232 response_complete(
7233 &oracledb_protocol::dpl::parse_direct_path_simple_response_with_limits(
7234 bytes,
7235 capabilities,
7236 limits,
7237 ),
7238 )
7239 })
7240 .await?;
7241 trace_query_bytes("DIRECT PATH OP response", &response);
7242 oracledb_protocol::dpl::parse_direct_path_simple_response_with_limits(
7243 &response,
7244 self.capabilities,
7245 self.protocol_limits,
7246 )
7247 .map_err(Error::from)
7248 }
7249
7250 pub async fn direct_path_load(
7257 &mut self,
7258 cx: &Cx,
7259 schema_name: &str,
7260 table_name: &str,
7261 column_names: &[String],
7262 rows: &[Vec<oracledb_protocol::dpl::DirectPathColumnValue>],
7263 batch_size: u32,
7264 ) -> Result<()> {
7265 let prepare = self
7266 .direct_path_prepare(cx, schema_name, table_name, column_names)
7267 .await?;
7268 let load_result = self
7269 .direct_path_load_batches(cx, &prepare, rows, batch_size)
7270 .await;
7271 let op_code = if load_result.is_ok() {
7272 oracledb_protocol::dpl::TNS_DP_OP_FINISH
7273 } else {
7274 oracledb_protocol::dpl::TNS_DP_OP_ABORT
7275 };
7276 let op_result = self.direct_path_op(cx, prepare.cursor_id, op_code).await;
7277 load_result?;
7278 op_result
7279 }
7280
7281 pub async fn direct_path_load_prepared(
7286 &mut self,
7287 cx: &Cx,
7288 prepare: &oracledb_protocol::dpl::DirectPathPrepareResult,
7289 rows: &[Vec<oracledb_protocol::dpl::DirectPathColumnValue>],
7290 batch_size: u32,
7291 ) -> Result<()> {
7292 let load_result = self
7293 .direct_path_load_batches(cx, prepare, rows, batch_size)
7294 .await;
7295 let op_code = if load_result.is_ok() {
7296 oracledb_protocol::dpl::TNS_DP_OP_FINISH
7297 } else {
7298 oracledb_protocol::dpl::TNS_DP_OP_ABORT
7299 };
7300 let op_result = self.direct_path_op(cx, prepare.cursor_id, op_code).await;
7301 load_result?;
7302 op_result
7303 }
7304
7305 async fn direct_path_load_batches(
7306 &mut self,
7307 cx: &Cx,
7308 prepare: &oracledb_protocol::dpl::DirectPathPrepareResult,
7309 rows: &[Vec<oracledb_protocol::dpl::DirectPathColumnValue>],
7310 batch_size: u32,
7311 ) -> Result<()> {
7312 for row in rows {
7323 if row.len() != prepare.column_metadata.len() {
7324 return Err(oracledb_protocol::ProtocolError::TtcDecode(
7325 "direct path row width does not match column metadata",
7326 )
7327 .into());
7328 }
7329 }
7330 let mut state =
7331 oracledb_protocol::dpl::BatchLoadState::for_rows(rows.len() as u64, batch_size)?;
7332 let mut row_num: u64 = 1;
7334 while !state.is_done() {
7335 observe_cancellation_between_round_trips(cx)?;
7336 let start = usize::try_from(state.offset()).map_err(|_| {
7337 oracledb_protocol::ProtocolError::TtcDecode("direct path offset overflow")
7338 })?;
7339 let end = start + state.num_rows() as usize;
7340 let stream = oracledb_protocol::dpl::encode_direct_path_rows(
7341 &prepare.column_metadata,
7342 &rows[start..end],
7343 row_num,
7344 )?;
7345 row_num += (end - start) as u64;
7346 self.direct_path_load_stream(cx, prepare.cursor_id, &stream)
7347 .await?;
7348 state.next_batch();
7349 }
7350 Ok(())
7351 }
7352
7353 async fn break_and_drain(&mut self) -> Result<()> {
7365 self.core.recovery.begin_drain_after_break()?;
7366 match self.core.break_and_drain_wire(BREAK_DRAIN_RECOVERY_TIMEOUT) {
7367 Ok(()) => {
7368 self.core.recovery.finish_drain_ready();
7369 Ok(())
7370 }
7371 Err(err) => {
7372 self.core.recovery.mark_dead();
7375 self.dead = true;
7376 Err(err)
7377 }
7378 }
7379 }
7380
7381 pub(crate) fn reject_before_operation_start<T>(
7386 &mut self,
7387 cx: &Cx,
7388 timeout_ms: u32,
7389 ) -> Result<T> {
7390 let disposition = cx
7391 .cancel_reason()
7392 .map(|reason| CancelDisposition::from_kind(reason.kind))
7393 .unwrap_or(CancelDisposition::Timeout);
7394 if disposition == CancelDisposition::Close {
7395 self.core.recovery.mark_dead();
7396 self.dead = true;
7397 }
7398 Err(disposition.into_error(timeout_ms))
7399 }
7400
7401 pub(crate) async fn recover_from_call_timeout<T>(
7427 &mut self,
7428 cx: &Cx,
7429 timeout_ms: u32,
7430 ) -> Result<T> {
7431 match self.break_and_drain().await {
7432 Ok(()) => {
7433 let disposition = cx
7440 .cancel_reason()
7441 .map(|reason| CancelDisposition::from_kind(reason.kind))
7442 .unwrap_or(CancelDisposition::Timeout);
7443 if disposition == CancelDisposition::Close {
7444 self.core.recovery.mark_dead();
7447 self.dead = true;
7448 }
7449 Err(disposition.into_error(timeout_ms))
7450 }
7451 Err(closed) => Err(closed),
7452 }
7453 }
7454
7455 async fn drain_cancel_response(&mut self) -> Result<()> {
7469 self.core.recovery.begin_drain_after_break()?;
7470 match self.core.drain_cancel_wire(BREAK_DRAIN_RECOVERY_TIMEOUT) {
7471 Ok(()) => {
7472 self.core.recovery.finish_drain_ready();
7473 Ok(())
7474 }
7475 Err(err) => {
7476 self.core.recovery.mark_dead();
7477 self.dead = true;
7478 Err(err)
7479 }
7480 }
7481 }
7482
7483 pub async fn cancel(&mut self, _cx: &Cx) -> Result<()> {
7508 self.core.recovery.begin_drain_after_break()?;
7509 match self
7510 .core
7511 .cancel_and_drain_wire(BREAK_DRAIN_RECOVERY_TIMEOUT)
7512 {
7513 Ok(()) => {
7514 self.core.recovery.finish_drain_ready();
7515 Ok(())
7516 }
7517 Err(err) => {
7518 self.core.recovery.mark_dead();
7519 self.dead = true;
7520 Err(err)
7521 }
7522 }
7523 }
7524
7525 pub fn supports_oob(&self) -> bool {
7533 self.supports_oob
7534 }
7535
7536 fn remember_cursor_columns(&mut self, result: &QueryResult) {
7537 if result.cursor_id != 0 && !result.columns.is_empty() {
7538 if self.cursor_columns.get(&result.cursor_id) == Some(&result.columns) {
7546 return;
7547 }
7548 self.cursor_columns
7549 .insert(result.cursor_id, result.columns.clone());
7550 }
7551 }
7552
7553 fn remember_fetch_metadata(&mut self, sql: &str, columns: &[ColumnMetadata]) {
7557 const FETCH_METADATA_RETENTION_CAP: usize = 100;
7558 if !self.fetch_metadata_by_sql.contains_key(sql) {
7559 if self.fetch_metadata_order.len() >= FETCH_METADATA_RETENTION_CAP {
7560 if let Some(oldest) = self.fetch_metadata_order.pop_front() {
7561 self.fetch_metadata_by_sql.remove(&oldest);
7562 }
7563 }
7564 self.fetch_metadata_order.push_back(sql.to_string());
7565 }
7566 self.fetch_metadata_by_sql
7567 .insert(sql.to_string(), columns.to_vec());
7568 }
7569
7570 fn forget_fetch_metadata(&mut self, sql: &str) -> bool {
7575 if self.fetch_metadata_by_sql.remove(sql).is_some() {
7576 self.fetch_metadata_order.retain(|entry| entry != sql);
7577 return true;
7578 }
7579 false
7580 }
7581
7582 async fn apply_refetch_metadata(
7588 &mut self,
7589 cx: &Cx,
7590 sql: &str,
7591 mut result: QueryResult,
7592 arraysize: u32,
7593 ) -> Result<QueryResult> {
7594 if result.columns.is_empty() {
7595 return Ok(result);
7596 }
7597 if self.shape_cache.observe(sql, &result.columns).self_healed {
7606 self.forget_fetch_metadata(sql);
7607 }
7608 if let Some(previous_columns) = self.fetch_metadata_by_sql.get(sql) {
7609 let mut adjusted = result.columns.clone();
7610 let mut any_adjusted = false;
7611 for (index, column) in adjusted.iter_mut().enumerate() {
7612 if let Some(previous) = previous_columns.get(index) {
7613 any_adjusted |= adjust_refetch_metadata(previous, column);
7614 }
7615 }
7616 if any_adjusted && result.cursor_id != 0 {
7617 observe_cancellation_between_round_trips(cx)?;
7618 let cursor_id = result.cursor_id;
7619 let redefined_result = self
7620 .define_and_fetch_rows_with_columns(
7621 cx,
7622 cursor_id,
7623 arraysize.max(1),
7624 &adjusted,
7625 None,
7626 )
7627 .await;
7628 let mut redefined = self.close_cursor_on_error(cursor_id, redefined_result)?;
7629 if redefined.columns.is_empty() {
7630 redefined.columns = adjusted;
7631 }
7632 if redefined.cursor_id == 0 {
7633 redefined.cursor_id = cursor_id;
7634 }
7635 result = redefined;
7636 }
7637 }
7638 self.remember_fetch_metadata(sql, &result.columns);
7639 Ok(result)
7640 }
7641
7642 fn statement_cache_get(&mut self, sql: &str, bind_shape: &[BindShapeSlot]) -> Option<u32> {
7656 let index = self
7657 .statement_cache
7658 .iter()
7659 .position(|entry| entry.sql == sql)?;
7660 let cursor_id = self.statement_cache[index].cursor_id;
7661 if cursor_id != 0 && self.in_use_cursors.contains(&cursor_id) {
7662 return None;
7663 }
7664 if !bind_shape_is_compatible(&self.statement_cache[index].bind_shape, bind_shape) {
7665 self.statement_cache_invalidate(sql, cursor_id);
7666 return None;
7667 }
7668 let entry = self.statement_cache.remove(index);
7669 self.statement_cache.push(entry);
7670 Some(cursor_id)
7671 }
7672
7673 fn statement_cache_take(&mut self, sql: &str, bind_shape: &[BindShapeSlot]) -> Option<u32> {
7680 let index = self
7681 .statement_cache
7682 .iter()
7683 .position(|entry| entry.sql == sql)?;
7684 if !bind_shape_is_compatible(&self.statement_cache[index].bind_shape, bind_shape) {
7685 let cursor_id = self.statement_cache[index].cursor_id;
7686 self.statement_cache_invalidate(sql, cursor_id);
7687 return None;
7688 }
7689 Some(self.statement_cache.remove(index).cursor_id)
7690 }
7691
7692 fn statement_cache_put(&mut self, sql: &str, cursor_id: u32, bind_shape: Vec<BindShapeSlot>) {
7697 let to_close = statement_cache_insert(
7698 &mut self.statement_cache,
7699 self.statement_cache_size,
7700 sql,
7701 cursor_id,
7702 bind_shape,
7703 );
7704 for cursor_id in &to_close {
7705 self.lob_prefetch_cursors.remove(cursor_id);
7706 self.cursor_columns.remove(cursor_id);
7707 }
7708 self.cursors_to_close.extend(to_close);
7709 }
7710
7711 fn invalidate_bound_ref_cursors(&mut self, bind_rows: &[Vec<BindValue>]) {
7717 for row in bind_rows {
7718 for value in row {
7719 if let BindValue::Cursor { cursor_id } = value {
7720 if *cursor_id == 0 {
7721 continue;
7722 }
7723 self.statement_cache
7724 .retain(|entry| entry.cursor_id != *cursor_id);
7725 self.cursor_columns.remove(cursor_id);
7726 self.lob_prefetch_cursors.remove(cursor_id);
7727 }
7728 }
7729 }
7730 }
7731
7732 pub fn release_cursor(&mut self, cursor_id: u32) {
7739 if cursor_id == 0 {
7740 return;
7741 }
7742 self.in_use_cursors.remove(&cursor_id);
7743 if self.copied_cursors.remove(&cursor_id) {
7747 self.cursors_to_close.push(cursor_id);
7748 self.cursor_columns.remove(&cursor_id);
7749 self.lob_prefetch_cursors.remove(&cursor_id);
7750 }
7751 }
7752
7753 pub(crate) fn close_cursor_on_error<T>(
7759 &mut self,
7760 cursor_id: u32,
7761 result: Result<T>,
7762 ) -> Result<T> {
7763 if result.is_err() {
7764 self.close_cursor(cursor_id);
7765 }
7766 result
7767 }
7768
7769 pub fn close_cursor(&mut self, cursor_id: u32) {
7779 if cursor_id == 0 {
7780 return;
7781 }
7782 self.statement_cache
7783 .retain(|entry| entry.cursor_id != cursor_id);
7784 self.in_use_cursors.remove(&cursor_id);
7785 self.copied_cursors.remove(&cursor_id);
7786 self.cursor_columns.remove(&cursor_id);
7787 self.lob_prefetch_cursors.remove(&cursor_id);
7788 if !self.cursors_to_close.contains(&cursor_id) {
7789 self.cursors_to_close.push(cursor_id);
7790 }
7791 }
7792
7793 fn statement_is_in_use(&self, sql: &str) -> bool {
7797 self.statement_cache
7798 .iter()
7799 .find(|entry| entry.sql == sql)
7800 .is_some_and(|entry| {
7801 entry.cursor_id != 0 && self.in_use_cursors.contains(&entry.cursor_id)
7802 })
7803 }
7804
7805 fn statement_cache_invalidate(&mut self, sql: &str, cursor_id: u32) {
7808 if let Some(index) = self
7809 .statement_cache
7810 .iter()
7811 .position(|entry| entry.sql == sql)
7812 {
7813 self.statement_cache.remove(index);
7814 }
7815 if cursor_id != 0 {
7816 self.cursors_to_close.push(cursor_id);
7817 self.cursor_columns.remove(&cursor_id);
7818 self.lob_prefetch_cursors.remove(&cursor_id);
7819 self.in_use_cursors.remove(&cursor_id);
7820 self.copied_cursors.remove(&cursor_id);
7821 }
7822 }
7823
7824 fn take_close_cursors_piggyback(&mut self) -> Option<Vec<u8>> {
7827 if self.cursors_to_close.is_empty() {
7828 return None;
7829 }
7830 let cursor_ids = std::mem::take(&mut self.cursors_to_close);
7831 let seq_num = next_ttc_sequence(&mut self.ttc_seq_num);
7832 Some(oracledb_protocol::thin::build_close_cursors_piggyback(
7833 &cursor_ids,
7834 seq_num,
7835 self.capabilities.ttc_field_version,
7836 ))
7837 }
7838
7839 pub async fn close(mut self, cx: &Cx) -> Result<()> {
7842 observe_cancellation_between_round_trips(cx)?;
7843 match time::timeout(time::wall_now(), Duration::from_secs(5), self.rollback(cx)).await {
7844 Ok(result) => result?,
7845 Err(_) => {
7846 let eof = encode_packet(
7847 TNS_PACKET_TYPE_DATA,
7848 0,
7849 Some(oracledb_protocol::thin::TNS_DATA_FLAGS_EOF),
7850 &[],
7851 PacketLengthWidth::Large32,
7852 )?;
7853 let _ = self.core.write_all(cx, &eof).await;
7854 let _ = self.core.shutdown_write(cx).await;
7855 return Ok(());
7856 }
7857 }
7858 self.ensure_clean_before_request().await?;
7859 let seq_num = next_ttc_sequence(&mut self.ttc_seq_num);
7860 self.core
7861 .send_data_packet(
7862 cx,
7863 &build_function_payload_with_seq(
7864 TNS_FUNC_LOGOFF,
7865 seq_num,
7866 self.capabilities.ttc_field_version,
7867 ),
7868 self.sdu,
7869 )
7870 .await?;
7871 let capabilities = self.capabilities;
7872 let limits = self.protocol_limits;
7873 self.core
7874 .finish_session_close(cx, !self.supports_end_of_response, |bytes| {
7875 response_complete(&parse_plain_function_response_with_limits(
7876 bytes,
7877 capabilities,
7878 limits,
7879 ))
7880 })
7881 .await
7882 }
7883
7884 pub async fn run_pipeline(
7902 &mut self,
7903 cx: &Cx,
7904 requests: &[PipelineRequest],
7905 continue_on_error: bool,
7906 ) -> Result<Vec<Vec<u8>>> {
7907 if !self.supports_end_of_response {
7912 return Err(Error::Protocol(
7913 oracledb_protocol::ProtocolError::UnsupportedFeature(
7914 "pipelining requires END_OF_RESPONSE framing, which this server \
7915 did not negotiate (requires Oracle Database 23ai or later)",
7916 ),
7917 ));
7918 }
7919 observe_cancellation_between_round_trips(cx)?;
7920 if requests.is_empty() {
7921 return Ok(Vec::new());
7922 }
7923 self.ensure_clean_before_request().await?;
7924 self.protocol_limits
7925 .check_length_prefixed_elements(requests.len())?;
7926 let pipeline_mode = if continue_on_error {
7927 TNS_PIPELINE_MODE_CONTINUE_ON_ERROR
7928 } else {
7929 TNS_PIPELINE_MODE_ABORT_ON_ERROR
7930 };
7931 for (index, request) in requests.iter().enumerate() {
7932 let token_num = index as u64 + 1;
7933 let mut payload = Vec::new();
7934 let mut first_packet_flags = 0u16;
7935 if index == 0 {
7936 if let Some(close_piggyback) = self.take_close_cursors_piggyback() {
7946 payload.extend_from_slice(&close_piggyback);
7947 }
7948 let piggyback_seq = next_ttc_sequence(&mut self.ttc_seq_num);
7949 payload.extend_from_slice(&build_begin_pipeline_piggyback(
7950 piggyback_seq,
7951 token_num,
7952 pipeline_mode,
7953 ));
7954 first_packet_flags |= TNS_DATA_FLAGS_BEGIN_PIPELINE;
7955 }
7956 let seq_num = next_ttc_sequence(&mut self.ttc_seq_num);
7957 match request {
7958 PipelineRequest::Execute {
7959 sql,
7960 bind_rows,
7961 prefetch_rows,
7962 } => {
7963 self.protocol_limits.check_batch_rows(bind_rows.len())?;
7964 if let Some(first_row) = bind_rows.first() {
7965 self.protocol_limits.check_binds(first_row.len())?;
7966 }
7967 payload.extend_from_slice(
7968 &build_execute_payload_with_bind_rows_and_options_with_seq(
7969 sql,
7970 *prefetch_rows,
7971 seq_num,
7972 statement_is_query(sql),
7973 bind_rows,
7974 ExecuteOptions::default()
7975 .with_token_num(token_num)
7976 .with_max_string_size(self.capabilities.max_string_size),
7977 self.capabilities.ttc_field_version,
7978 )?,
7979 );
7980 }
7981 PipelineRequest::Commit => {
7982 payload.extend_from_slice(&build_function_payload_with_seq_and_token(
7983 TNS_FUNC_COMMIT,
7984 seq_num,
7985 token_num,
7986 self.capabilities.ttc_field_version,
7987 ));
7988 }
7989 }
7990 trace_query_bytes("PIPELINE op payload", &payload);
7991 self.core
7992 .send_data_packet_with_flags(
7993 cx,
7994 &payload,
7995 self.sdu,
7996 first_packet_flags,
7997 TNS_DATA_FLAGS_END_OF_REQUEST,
7998 )
7999 .await?;
8000 }
8001 let seq_num = next_ttc_sequence(&mut self.ttc_seq_num);
8002 let end_payload = build_end_pipeline_payload_with_seq(seq_num);
8003 trace_query_bytes("PIPELINE end payload", &end_payload);
8004 self.core
8005 .send_data_packet(cx, &end_payload, self.sdu)
8006 .await?;
8007 let mut responses = Vec::with_capacity(requests.len() + 1);
8008 for _ in 0..=requests.len() {
8009 let response = match self.read_pipeline_response_cancellable(cx).await {
8010 Ok(response) => response,
8011 Err(Error::CallTimeout(timeout_ms)) => {
8012 return self.recover_from_call_timeout(cx, timeout_ms).await;
8013 }
8014 Err(err) => return Err(err),
8015 };
8016 trace_query_bytes("PIPELINE response", &response.payload);
8017 responses.push(response.payload);
8018 }
8019 Ok(responses)
8020 }
8021
8022 pub async fn run_pipeline_decoded(
8045 &mut self,
8046 cx: &Cx,
8047 requests: &[PipelineRequest],
8048 continue_on_error: bool,
8049 ) -> Result<Vec<Result<QueryResult>>> {
8050 let raw = self.run_pipeline(cx, requests, continue_on_error).await?;
8051 let mut decoded = Vec::with_capacity(requests.len());
8054 for (index, request) in requests.iter().enumerate() {
8055 let payload = &raw[index];
8056 let outcome = match request {
8057 PipelineRequest::Commit => {
8058 match parse_plain_function_response_with_limits(
8062 payload,
8063 self.capabilities,
8064 self.protocol_limits,
8065 ) {
8066 Ok(txn_in_progress) => Ok(QueryResult {
8067 txn_in_progress: Some(txn_in_progress),
8068 ..QueryResult::default()
8069 }),
8070 Err(err) => Err(Error::Protocol(err)),
8071 }
8072 }
8073 PipelineRequest::Execute { sql, bind_rows, .. } => {
8074 parse_query_response_with_binds_options_columns_and_limits(
8075 payload,
8076 self.capabilities,
8077 bind_rows.first().map(Vec::as_slice).unwrap_or(&[]),
8078 ExecuteOptions::default(),
8079 &[],
8080 self.protocol_limits,
8081 )
8082 .map_err(Error::Protocol)
8083 .inspect(|result| {
8084 self.remember_cursor_columns(result);
8087 if result.cursor_id != 0 && statement_is_query(sql) {
8088 self.in_use_cursors.insert(result.cursor_id);
8089 }
8090 })
8091 }
8092 };
8093 if let Ok(result) = &outcome {
8096 if let Some(txn_in_progress) = result.txn_in_progress {
8097 self.txn_in_progress = txn_in_progress;
8098 }
8099 }
8100 decoded.push(outcome);
8101 }
8102 Ok(decoded)
8103 }
8104
8105 async fn send_function(&mut self, cx: &Cx, function_code: u8) -> Result<()> {
8106 observe_cancellation_between_round_trips(cx)?;
8107 self.ensure_clean_before_request().await?;
8108 let seq_num = next_ttc_sequence(&mut self.ttc_seq_num);
8109 self.core
8110 .send_data_packet(
8111 cx,
8112 &build_function_payload_with_seq(
8113 function_code,
8114 seq_num,
8115 self.capabilities.ttc_field_version,
8116 ),
8117 self.sdu,
8118 )
8119 .await?;
8120 let capabilities = self.capabilities;
8121 let limits = self.protocol_limits;
8122 let response = self
8123 .read_response_cancellable(cx, !self.supports_end_of_response, |bytes| {
8124 response_complete(&parse_plain_function_response_with_limits(
8125 bytes,
8126 capabilities,
8127 limits,
8128 ))
8129 })
8130 .await?;
8131 let txn_in_progress = self.note_parse(parse_plain_function_response_with_limits(
8137 &response,
8138 self.capabilities,
8139 self.protocol_limits,
8140 ))?;
8141 self.txn_in_progress = txn_in_progress;
8142 Ok(())
8143 }
8144
8145 pub async fn supplement_json_column_metadata(
8157 &mut self,
8158 cx: &Cx,
8159 columns: &mut [ColumnMetadata],
8160 timeout_ms: Option<u32>,
8161 ) -> Result<()> {
8162 let candidates = json_lob_probe_candidates(columns);
8163 if candidates.is_empty() {
8164 return Ok(());
8165 }
8166 for (index, column_name) in candidates {
8167 let result = self
8168 .execute_query_with_binds_call_timeout(
8169 cx,
8170 "select 1 \
8171 from all_json_columns \
8172 where owner = sys_context('USERENV', 'CURRENT_SCHEMA') \
8173 and column_name = :1",
8174 1,
8175 &[BindValue::Text(column_name)],
8176 timeout_ms,
8177 )
8178 .await?;
8179 if !result.rows.is_empty() {
8180 columns[index] = columns[index].clone().with_is_json(true);
8181 }
8182 }
8183 Ok(())
8184 }
8185}
8186
8187impl CancelHandle {
8188 pub async fn cancel(&mut self, cx: &Cx) -> Result<()> {
8197 observe_cancellation_between_round_trips(cx)?;
8198 if !self.should_send_break_request()? {
8199 return Ok(());
8200 }
8201 let mut write = lock_write(cx, &self.write).await?;
8202 if !self.should_send_break_request()? {
8203 return Ok(());
8204 }
8205 match send_marker(&mut *write, TNS_MARKER_TYPE_BREAK).await {
8206 Ok(()) => self.recovery.mark_break_sent(),
8207 Err(err) => {
8208 self.recovery.mark_dead();
8209 Err(err)
8210 }
8211 }
8212 }
8213
8214 pub(crate) fn cancel_for_recovery(&mut self) -> Result<()> {
8224 if !self.should_send_break_request()? {
8225 return Ok(());
8226 }
8227 match crate::recovery::send_break_without_current_cx(
8228 &self.write,
8229 BREAK_DRAIN_RECOVERY_TIMEOUT,
8230 ) {
8231 Ok(()) => self.recovery.mark_break_sent(),
8232 Err(err) => {
8233 self.recovery.mark_dead();
8234 Err(err)
8235 }
8236 }
8237 }
8238
8239 fn should_send_break_request(&self) -> Result<bool> {
8240 match self.recovery.phase() {
8241 SessionRecoveryPhase::Dead => {
8242 Err(Error::ConnectionClosed("connection is closed".into()))
8243 }
8244 SessionRecoveryPhase::BreakSent | SessionRecoveryPhase::Draining => Ok(false),
8245 SessionRecoveryPhase::Ready | SessionRecoveryPhase::InFlight => Ok(true),
8246 }
8247 }
8248
8249 pub fn cancel_blocking(&mut self) -> Result<()> {
8251 let runtime = build_io_runtime()?;
8252 runtime.block_on(async {
8253 let cx = Cx::current()
8254 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
8255 self.cancel(&cx).await
8256 })
8257 }
8258}
8259
8260pub struct BlockingConnection;
8286
8287impl BlockingConnection {
8288 pub fn connect(options: ConnectOptions) -> Result<Connection> {
8290 let runtime = build_io_runtime()?;
8291 runtime.block_on(async {
8292 let cx = Cx::current()
8293 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
8294 Connection::connect(&cx, options).await
8295 })
8296 }
8297
8298 pub fn ping(connection: &mut Connection) -> Result<()> {
8299 let runtime = build_io_runtime()?;
8300 runtime.block_on(async {
8301 let cx = Cx::current()
8302 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
8303 connection.ping(&cx).await
8304 })
8305 }
8306
8307 pub fn ping_with_timeout(connection: &mut Connection, timeout_ms: u32) -> Result<()> {
8308 let runtime = build_io_runtime()?;
8309 runtime.block_on(async {
8310 let cx = Cx::current()
8311 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
8312 connection.ping_with_timeout(&cx, timeout_ms).await
8313 })
8314 }
8315
8316 pub fn change_password(
8317 connection: &mut Connection,
8318 old_password: &str,
8319 new_password: &str,
8320 ) -> Result<()> {
8321 let runtime = build_io_runtime()?;
8322 runtime.block_on(async {
8323 let cx = Cx::current()
8324 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
8325 connection
8326 .change_password(&cx, old_password, new_password)
8327 .await
8328 })
8329 }
8330
8331 pub fn cancel(connection: &mut Connection) -> Result<()> {
8333 block_on_io(|cx| async move { connection.cancel(&cx).await })
8334 }
8335
8336 pub fn commit(connection: &mut Connection) -> Result<()> {
8337 let runtime = build_io_runtime()?;
8338 runtime.block_on(async {
8339 let cx = Cx::current()
8340 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
8341 connection.commit(&cx).await
8342 })
8343 }
8344
8345 #[allow(clippy::too_many_arguments)]
8348 pub fn subscribe_register(
8349 connection: &mut Connection,
8350 namespace: u32,
8351 name: Option<&str>,
8352 public_qos: u32,
8353 operations: u32,
8354 timeout: u32,
8355 grouping_class: u8,
8356 grouping_value: u32,
8357 grouping_type: u8,
8358 ) -> Result<SubscribeResult> {
8359 let runtime = build_io_runtime()?;
8360 runtime.block_on(async {
8361 let cx = Cx::current()
8362 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
8363 connection
8364 .subscribe_register(
8365 &cx,
8366 namespace,
8367 name,
8368 public_qos,
8369 operations,
8370 timeout,
8371 grouping_class,
8372 grouping_value,
8373 grouping_type,
8374 )
8375 .await
8376 })
8377 }
8378
8379 #[allow(clippy::too_many_arguments)]
8382 pub fn subscribe_unregister(
8383 connection: &mut Connection,
8384 registration_id: u64,
8385 client_id: &[u8],
8386 namespace: u32,
8387 name: Option<&str>,
8388 public_qos: u32,
8389 operations: u32,
8390 timeout: u32,
8391 grouping_class: u8,
8392 grouping_value: u32,
8393 grouping_type: u8,
8394 ) -> Result<()> {
8395 let runtime = build_io_runtime()?;
8396 runtime.block_on(async {
8397 let cx = Cx::current()
8398 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
8399 connection
8400 .subscribe_unregister(
8401 &cx,
8402 registration_id,
8403 client_id,
8404 namespace,
8405 name,
8406 public_qos,
8407 operations,
8408 timeout,
8409 grouping_class,
8410 grouping_value,
8411 grouping_type,
8412 )
8413 .await
8414 })
8415 }
8416
8417 pub fn notify_register(connection: &mut Connection, client_id: &[u8]) -> Result<()> {
8420 block_on_io(|cx| async move { connection.notify_register(&cx, client_id).await })
8421 }
8422
8423 pub fn recv_notification(
8425 connection: &mut Connection,
8426 namespace: u32,
8427 public_qos: u32,
8428 read_timeout: Duration,
8429 ) -> Result<NotificationOutcome> {
8430 block_on_io(|cx| async move {
8431 connection
8432 .recv_notification(&cx, namespace, public_qos, read_timeout)
8433 .await
8434 })
8435 }
8436
8437 pub fn register_query<'r>(
8439 connection: &mut Connection,
8440 registration: Registration<'r>,
8441 ) -> Result<RegistrationOutcome> {
8442 block_on_io(|cx| async move { connection.register_query(&cx, registration).await })
8443 }
8444
8445 pub fn rollback(connection: &mut Connection) -> Result<()> {
8446 let runtime = build_io_runtime()?;
8447 runtime.block_on(async {
8448 let cx = Cx::current()
8449 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
8450 connection.rollback(&cx).await
8451 })
8452 }
8453
8454 pub fn enable_dbms_output(
8456 connection: &mut Connection,
8457 buffer_bytes: Option<u32>,
8458 ) -> Result<()> {
8459 let runtime = build_io_runtime()?;
8460 runtime.block_on(async {
8461 let cx = Cx::current()
8462 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
8463 connection.enable_dbms_output(&cx, buffer_bytes).await
8464 })
8465 }
8466
8467 pub fn read_dbms_output(
8469 connection: &mut Connection,
8470 max_lines: usize,
8471 max_chars: usize,
8472 ) -> Result<DbmsOutput> {
8473 let runtime = build_io_runtime()?;
8474 runtime.block_on(async {
8475 let cx = Cx::current()
8476 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
8477 connection.read_dbms_output(&cx, max_lines, max_chars).await
8478 })
8479 }
8480
8481 pub fn fetch_cursor(
8483 connection: &mut Connection,
8484 cursor: &oracledb_protocol::thin::CursorValue,
8485 max_rows: usize,
8486 ) -> Result<QueryResult> {
8487 let runtime = build_io_runtime()?;
8488 runtime.block_on(async {
8489 let cx = Cx::current()
8490 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
8491 connection.fetch_cursor(&cx, cursor, max_rows).await
8492 })
8493 }
8494
8495 pub fn describe_object_type(
8497 connection: &mut Connection,
8498 schema: &str,
8499 type_name: &str,
8500 ) -> Result<ObjectType> {
8501 let runtime = build_io_runtime()?;
8502 runtime.block_on(async {
8503 let cx = Cx::current()
8504 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
8505 connection
8506 .describe_object_type(&cx, schema, type_name)
8507 .await
8508 })
8509 }
8510
8511 pub fn begin_sessionless_transaction(
8512 connection: &mut Connection,
8513 transaction_id: &[u8],
8514 timeout: u32,
8515 defer_round_trip: bool,
8516 ) -> Result<()> {
8517 let runtime = build_io_runtime()?;
8518 runtime.block_on(async {
8519 let cx = Cx::current()
8520 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
8521 connection
8522 .begin_sessionless_transaction(&cx, transaction_id, timeout, defer_round_trip)
8523 .await
8524 })
8525 }
8526
8527 pub fn resume_sessionless_transaction(
8528 connection: &mut Connection,
8529 transaction_id: &[u8],
8530 timeout: u32,
8531 defer_round_trip: bool,
8532 ) -> Result<()> {
8533 let runtime = build_io_runtime()?;
8534 runtime.block_on(async {
8535 let cx = Cx::current()
8536 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
8537 connection
8538 .resume_sessionless_transaction(&cx, transaction_id, timeout, defer_round_trip)
8539 .await
8540 })
8541 }
8542
8543 pub fn suspend_sessionless_transaction(connection: &mut Connection) -> Result<()> {
8544 let runtime = build_io_runtime()?;
8545 runtime.block_on(async {
8546 let cx = Cx::current()
8547 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
8548 connection.suspend_sessionless_transaction(&cx).await
8549 })
8550 }
8551
8552 #[allow(clippy::too_many_arguments)]
8553 pub fn tpc_begin(
8554 connection: &mut Connection,
8555 format_id: u32,
8556 global_transaction_id: &[u8],
8557 branch_qualifier: &[u8],
8558 flags: u32,
8559 timeout: u32,
8560 ) -> Result<()> {
8561 let runtime = build_io_runtime()?;
8562 runtime.block_on(async {
8563 let cx = Cx::current()
8564 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
8565 connection
8566 .tpc_begin(
8567 &cx,
8568 format_id,
8569 global_transaction_id,
8570 branch_qualifier,
8571 flags,
8572 timeout,
8573 )
8574 .await
8575 })
8576 }
8577
8578 pub fn tpc_end(
8579 connection: &mut Connection,
8580 xid: Option<(u32, &[u8], &[u8])>,
8581 flags: u32,
8582 ) -> Result<()> {
8583 let runtime = build_io_runtime()?;
8584 runtime.block_on(async {
8585 let cx = Cx::current()
8586 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
8587 connection.tpc_end(&cx, xid, flags).await
8588 })
8589 }
8590
8591 pub fn tpc_prepare(
8592 connection: &mut Connection,
8593 xid: Option<(u32, &[u8], &[u8])>,
8594 ) -> Result<bool> {
8595 let runtime = build_io_runtime()?;
8596 runtime.block_on(async {
8597 let cx = Cx::current()
8598 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
8599 connection.tpc_prepare(&cx, xid).await
8600 })
8601 }
8602
8603 pub fn tpc_commit(
8604 connection: &mut Connection,
8605 xid: Option<(u32, &[u8], &[u8])>,
8606 one_phase: bool,
8607 ) -> Result<()> {
8608 let runtime = build_io_runtime()?;
8609 runtime.block_on(async {
8610 let cx = Cx::current()
8611 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
8612 connection.tpc_commit(&cx, xid, one_phase).await
8613 })
8614 }
8615
8616 pub fn tpc_rollback(
8617 connection: &mut Connection,
8618 xid: Option<(u32, &[u8], &[u8])>,
8619 ) -> Result<()> {
8620 let runtime = build_io_runtime()?;
8621 runtime.block_on(async {
8622 let cx = Cx::current()
8623 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
8624 connection.tpc_rollback(&cx, xid).await
8625 })
8626 }
8627
8628 pub fn query<'conn, 'p>(
8631 connection: &'conn mut Connection,
8632 sql: &str,
8633 params: impl Into<crate::Params<'p>>,
8634 ) -> Result<BlockingRows<'conn>> {
8635 block_on_io(|cx| async move {
8636 connection
8637 .query(&cx, sql, params)
8638 .await
8639 .map(BlockingRows::new)
8640 })
8641 }
8642
8643 pub fn query_one<'p>(
8645 connection: &mut Connection,
8646 sql: &str,
8647 params: impl Into<crate::Params<'p>>,
8648 ) -> Result<Row> {
8649 block_on_io(|cx| async move { connection.query_one(&cx, sql, params).await })
8650 }
8651
8652 pub fn query_opt<'p>(
8654 connection: &mut Connection,
8655 sql: &str,
8656 params: impl Into<crate::Params<'p>>,
8657 ) -> Result<Option<Row>> {
8658 block_on_io(|cx| async move { connection.query_opt(&cx, sql, params).await })
8659 }
8660
8661 pub fn query_all<'p>(
8663 connection: &mut Connection,
8664 sql: &str,
8665 params: impl Into<crate::Params<'p>>,
8666 ) -> Result<Vec<Row>> {
8667 block_on_io(|cx| async move { connection.query_all(&cx, sql, params).await })
8668 }
8669
8670 pub fn query_with<'conn, 'q>(
8672 connection: &'conn mut Connection,
8673 query: Query<'q>,
8674 ) -> Result<BlockingRows<'conn>> {
8675 block_on_io(|cx| async move {
8676 connection
8677 .query_with(&cx, query)
8678 .await
8679 .map(BlockingRows::new)
8680 })
8681 }
8682
8683 pub fn execute<'p>(
8685 connection: &mut Connection,
8686 sql: &str,
8687 params: impl Into<crate::Params<'p>>,
8688 ) -> Result<ExecuteOutcome> {
8689 block_on_io(|cx| async move { connection.execute(&cx, sql, params).await })
8690 }
8691
8692 pub fn execute_with<'e>(
8694 connection: &mut Connection,
8695 execute: Execute<'e>,
8696 ) -> Result<ExecuteOutcome> {
8697 block_on_io(|cx| async move { connection.execute_with(&cx, execute).await })
8698 }
8699
8700 pub fn execute_many<'b>(
8702 connection: &mut Connection,
8703 sql: &str,
8704 rows: impl Into<crate::BatchRows<'b>>,
8705 ) -> Result<BatchOutcome> {
8706 block_on_io(|cx| async move { connection.execute_many(&cx, sql, rows).await })
8707 }
8708
8709 pub fn execute_many_with<'b>(
8711 connection: &mut Connection,
8712 batch: Batch<'b>,
8713 ) -> Result<BatchOutcome> {
8714 block_on_io(|cx| async move { connection.execute_many_with(&cx, batch).await })
8715 }
8716
8717 pub fn execute_raw(
8727 connection: &mut Connection,
8728 sql: &str,
8729 prefetch_rows: u32,
8730 bind_rows: &[Vec<BindValue>],
8731 exec_options: ExecuteOptions,
8732 timeout_ms: Option<u32>,
8733 ) -> Result<QueryResult> {
8734 let runtime = build_io_runtime()?;
8735 runtime.block_on(async {
8736 let cx = Cx::current()
8737 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
8738 connection
8739 .execute_raw(&cx, sql, prefetch_rows, bind_rows, exec_options, timeout_ms)
8740 .await
8741 })
8742 }
8743
8744 pub fn fetch_rows(
8745 connection: &mut Connection,
8746 cursor_id: u32,
8747 arraysize: u32,
8748 previous_row: Option<&[Option<oracledb_protocol::thin::QueryValue>]>,
8749 ) -> Result<QueryResult> {
8750 let runtime = build_io_runtime()?;
8751 runtime.block_on(async {
8752 let cx = Cx::current()
8753 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
8754 connection
8755 .fetch_rows(&cx, cursor_id, arraysize, previous_row)
8756 .await
8757 })
8758 }
8759
8760 pub fn fetch_rows_with_columns(
8761 connection: &mut Connection,
8762 cursor_id: u32,
8763 arraysize: u32,
8764 known_columns: &[ColumnMetadata],
8765 previous_row: Option<&[Option<oracledb_protocol::thin::QueryValue>]>,
8766 ) -> Result<QueryResult> {
8767 let runtime = build_io_runtime()?;
8768 runtime.block_on(async {
8769 let cx = Cx::current()
8770 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
8771 connection
8772 .fetch_rows_with_columns(&cx, cursor_id, arraysize, known_columns, previous_row)
8773 .await
8774 })
8775 }
8776
8777 pub fn define_and_fetch_rows_with_columns(
8778 connection: &mut Connection,
8779 cursor_id: u32,
8780 arraysize: u32,
8781 define_columns: &[ColumnMetadata],
8782 previous_row: Option<&[Option<oracledb_protocol::thin::QueryValue>]>,
8783 ) -> Result<QueryResult> {
8784 let runtime = build_io_runtime()?;
8785 runtime.block_on(async {
8786 let cx = Cx::current()
8787 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
8788 connection
8789 .define_and_fetch_rows_with_columns(
8790 &cx,
8791 cursor_id,
8792 arraysize,
8793 define_columns,
8794 previous_row,
8795 )
8796 .await
8797 })
8798 }
8799
8800 pub fn scroll_cursor(
8801 connection: &mut Connection,
8802 sql: &str,
8803 cursor_id: u32,
8804 arraysize: u32,
8805 fetch_orientation: u32,
8806 fetch_pos: u32,
8807 ) -> Result<QueryResult> {
8808 let runtime = build_io_runtime()?;
8809 runtime.block_on(async {
8810 let cx = Cx::current()
8811 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
8812 connection
8813 .scroll_cursor(&cx, sql, cursor_id, arraysize, fetch_orientation, fetch_pos)
8814 .await
8815 })
8816 }
8817
8818 pub fn read_lob(
8819 connection: &mut Connection,
8820 locator: &[u8],
8821 offset: u64,
8822 amount: u64,
8823 ) -> Result<LobReadResult> {
8824 let runtime = build_io_runtime()?;
8825 runtime.block_on(async {
8826 let cx = Cx::current()
8827 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
8828 connection.read_lob(&cx, locator, offset, amount).await
8829 })
8830 }
8831
8832 pub fn read_lob_with_timeout(
8833 connection: &mut Connection,
8834 locator: &[u8],
8835 offset: u64,
8836 amount: u64,
8837 timeout_ms: Option<u32>,
8838 ) -> Result<LobReadResult> {
8839 let runtime = build_io_runtime()?;
8840 runtime.block_on(async {
8841 let cx = Cx::current()
8842 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
8843 connection
8844 .read_lob_call_timeout(&cx, locator, offset, amount, timeout_ms)
8845 .await
8846 })
8847 }
8848
8849 pub fn aq_enq_one(
8850 connection: &mut Connection,
8851 queue: &AqQueueDesc,
8852 props: &AqMsgProps,
8853 enq_options: &AqEnqOptions,
8854 ) -> Result<Option<Vec<u8>>> {
8855 let runtime = build_io_runtime()?;
8856 runtime.block_on(async {
8857 let cx = Cx::current()
8858 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
8859 connection.aq_enq_one(&cx, queue, props, enq_options).await
8860 })
8861 }
8862
8863 pub fn aq_deq_one(
8864 connection: &mut Connection,
8865 queue: &AqQueueDesc,
8866 deq_options: &AqDeqOptions,
8867 ) -> Result<AqDeqResult> {
8868 let runtime = build_io_runtime()?;
8869 runtime.block_on(async {
8870 let cx = Cx::current()
8871 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
8872 connection.aq_deq_one(&cx, queue, deq_options).await
8873 })
8874 }
8875
8876 pub fn aq_enq_many(
8877 connection: &mut Connection,
8878 queue: &AqQueueDesc,
8879 props_list: &[AqMsgProps],
8880 enq_options: &AqEnqOptions,
8881 ) -> Result<Vec<Vec<u8>>> {
8882 let runtime = build_io_runtime()?;
8883 runtime.block_on(async {
8884 let cx = Cx::current()
8885 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
8886 connection
8887 .aq_enq_many(&cx, queue, props_list, enq_options)
8888 .await
8889 })
8890 }
8891
8892 pub fn aq_deq_many(
8893 connection: &mut Connection,
8894 queue: &AqQueueDesc,
8895 deq_options: &AqDeqOptions,
8896 max_num_messages: u32,
8897 ) -> Result<Vec<oracledb_protocol::thin::aq::AqDeqMessage>> {
8898 let runtime = build_io_runtime()?;
8899 runtime.block_on(async {
8900 let cx = Cx::current()
8901 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
8902 connection
8903 .aq_deq_many(&cx, queue, deq_options, max_num_messages)
8904 .await
8905 })
8906 }
8907
8908 pub fn create_temp_lob(
8909 connection: &mut Connection,
8910 ora_type_num: u8,
8911 csfrm: u8,
8912 ) -> Result<LobReadResult> {
8913 let runtime = build_io_runtime()?;
8914 runtime.block_on(async {
8915 let cx = Cx::current()
8916 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
8917 connection.create_temp_lob(&cx, ora_type_num, csfrm).await
8918 })
8919 }
8920
8921 pub fn write_lob(
8922 connection: &mut Connection,
8923 locator: &[u8],
8924 offset: u64,
8925 data: &[u8],
8926 ) -> Result<LobReadResult> {
8927 let runtime = build_io_runtime()?;
8928 runtime.block_on(async {
8929 let cx = Cx::current()
8930 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
8931 connection.write_lob(&cx, locator, offset, data).await
8932 })
8933 }
8934
8935 pub fn trim_lob(
8936 connection: &mut Connection,
8937 locator: &[u8],
8938 new_size: u64,
8939 ) -> Result<LobReadResult> {
8940 let runtime = build_io_runtime()?;
8941 runtime.block_on(async {
8942 let cx = Cx::current()
8943 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
8944 connection.trim_lob(&cx, locator, new_size).await
8945 })
8946 }
8947
8948 pub fn write_lob_with_timeout(
8949 connection: &mut Connection,
8950 locator: &[u8],
8951 offset: u64,
8952 data: &[u8],
8953 timeout_ms: Option<u32>,
8954 ) -> Result<LobReadResult> {
8955 let runtime = build_io_runtime()?;
8956 runtime.block_on(async {
8957 let cx = Cx::current()
8958 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
8959 connection
8960 .write_lob_call_timeout(&cx, locator, offset, data, timeout_ms)
8961 .await
8962 })
8963 }
8964
8965 pub fn trim_lob_with_timeout(
8966 connection: &mut Connection,
8967 locator: &[u8],
8968 new_size: u64,
8969 timeout_ms: Option<u32>,
8970 ) -> Result<LobReadResult> {
8971 let runtime = build_io_runtime()?;
8972 runtime.block_on(async {
8973 let cx = Cx::current()
8974 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
8975 connection
8976 .trim_lob_call_timeout(&cx, locator, new_size, timeout_ms)
8977 .await
8978 })
8979 }
8980
8981 pub fn free_temp_lobs(connection: &mut Connection, locators: &[Vec<u8>]) -> Result<()> {
8982 let runtime = build_io_runtime()?;
8983 runtime.block_on(async {
8984 let cx = Cx::current()
8985 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
8986 connection.free_temp_lobs(&cx, locators).await
8987 })
8988 }
8989
8990 pub fn free_temp_lobs_with_timeout(
8991 connection: &mut Connection,
8992 locators: &[Vec<u8>],
8993 timeout_ms: Option<u32>,
8994 ) -> Result<()> {
8995 let runtime = build_io_runtime()?;
8996 runtime.block_on(async {
8997 let cx = Cx::current()
8998 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
8999 connection
9000 .free_temp_lobs_call_timeout(&cx, locators, timeout_ms)
9001 .await
9002 })
9003 }
9004
9005 pub fn direct_path_load(
9006 connection: &mut Connection,
9007 schema_name: &str,
9008 table_name: &str,
9009 column_names: &[String],
9010 rows: &[Vec<oracledb_protocol::dpl::DirectPathColumnValue>],
9011 batch_size: u32,
9012 ) -> Result<()> {
9013 let runtime = build_io_runtime()?;
9014 runtime.block_on(async {
9015 let cx = Cx::current()
9016 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
9017 connection
9018 .direct_path_load(&cx, schema_name, table_name, column_names, rows, batch_size)
9019 .await
9020 })
9021 }
9022
9023 pub fn run_pipeline(
9024 connection: &mut Connection,
9025 requests: &[PipelineRequest],
9026 continue_on_error: bool,
9027 ) -> Result<Vec<Vec<u8>>> {
9028 let runtime = build_io_runtime()?;
9029 runtime.block_on(async {
9030 let cx = Cx::current()
9031 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
9032 connection
9033 .run_pipeline(&cx, requests, continue_on_error)
9034 .await
9035 })
9036 }
9037
9038 pub fn run_pipeline_decoded(
9039 connection: &mut Connection,
9040 requests: &[PipelineRequest],
9041 continue_on_error: bool,
9042 ) -> Result<Vec<Result<QueryResult>>> {
9043 let runtime = build_io_runtime()?;
9044 runtime.block_on(async {
9045 let cx = Cx::current()
9046 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
9047 connection
9048 .run_pipeline_decoded(&cx, requests, continue_on_error)
9049 .await
9050 })
9051 }
9052
9053 pub fn direct_path_prepare(
9054 connection: &mut Connection,
9055 schema_name: &str,
9056 table_name: &str,
9057 column_names: &[String],
9058 ) -> Result<oracledb_protocol::dpl::DirectPathPrepareResult> {
9059 let runtime = build_io_runtime()?;
9060 runtime.block_on(async {
9061 let cx = Cx::current()
9062 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
9063 connection
9064 .direct_path_prepare(&cx, schema_name, table_name, column_names)
9065 .await
9066 })
9067 }
9068
9069 pub fn direct_path_load_prepared(
9070 connection: &mut Connection,
9071 prepare: &oracledb_protocol::dpl::DirectPathPrepareResult,
9072 rows: &[Vec<oracledb_protocol::dpl::DirectPathColumnValue>],
9073 batch_size: u32,
9074 ) -> Result<()> {
9075 let runtime = build_io_runtime()?;
9076 runtime.block_on(async {
9077 let cx = Cx::current()
9078 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
9079 connection
9080 .direct_path_load_prepared(&cx, prepare, rows, batch_size)
9081 .await
9082 })
9083 }
9084
9085 #[doc(hidden)]
9086 pub fn __pyshim_drain_cancel_response(connection: &mut Connection) -> Result<()> {
9087 let runtime = build_io_runtime()?;
9088 runtime.block_on(async { connection.drain_cancel_response().await })
9089 }
9090
9091 #[doc(hidden)]
9100 pub fn __pyshim_reconcile_completed_cancel_response(connection: &mut Connection) -> Result<()> {
9101 match connection.core.recovery.phase() {
9102 SessionRecoveryPhase::Ready => Ok(()),
9103 SessionRecoveryPhase::BreakSent => {
9104 connection.core.recovery.finish_drain_ready();
9105 Ok(())
9106 }
9107 SessionRecoveryPhase::InFlight | SessionRecoveryPhase::Draining => Err(
9108 Error::ConnectionClosed("cancel response is not complete".into()),
9109 ),
9110 SessionRecoveryPhase::Dead => {
9111 Err(Error::ConnectionClosed("connection is closed".into()))
9112 }
9113 }
9114 }
9115
9116 pub fn supplement_json_column_metadata(
9118 connection: &mut Connection,
9119 columns: &mut [ColumnMetadata],
9120 timeout_ms: Option<u32>,
9121 ) -> Result<()> {
9122 let runtime = build_io_runtime()?;
9123 runtime.block_on(async {
9124 let cx = Cx::current()
9125 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
9126 connection
9127 .supplement_json_column_metadata(&cx, columns, timeout_ms)
9128 .await
9129 })
9130 }
9131
9132 pub fn close(connection: Connection) -> Result<()> {
9133 let runtime = build_io_runtime()?;
9134 runtime.block_on(async {
9135 let cx = Cx::current()
9136 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
9137 connection.close(&cx).await
9138 })
9139 }
9140}
9141
9142fn new_io_runtime() -> Result<Runtime> {
9149 let reactor = reactor::create_reactor()?;
9150 RuntimeBuilder::current_thread()
9151 .with_reactor(reactor)
9152 .build()
9153 .map_err(|err| Error::Runtime(err.to_string()))
9154}
9155
9156pub(crate) fn new_pool_runtime() -> Result<Runtime> {
9168 let reactor = reactor::create_reactor()?;
9169 RuntimeBuilder::current_thread()
9170 .with_reactor(reactor)
9171 .thread_name_prefix("oracledb-pool-bg")
9172 .build()
9173 .map_err(|err| Error::Runtime(err.to_string()))
9174}
9175
9176thread_local! {
9177 static IO_RUNTIME: std::cell::RefCell<Option<Runtime>> =
9197 const { std::cell::RefCell::new(None) };
9198}
9199
9200fn build_io_runtime() -> Result<Runtime> {
9205 IO_RUNTIME.with(|slot| {
9206 if let Some(runtime) = slot.borrow().as_ref() {
9207 return Ok(runtime.clone());
9208 }
9209 let runtime = new_io_runtime()?;
9210 *slot.borrow_mut() = Some(runtime.clone());
9211 Ok(runtime)
9212 })
9213}
9214
9215pub(crate) fn block_on_io<F, Fut, T>(operation: F) -> Result<T>
9218where
9219 F: FnOnce(Cx) -> Fut,
9220 Fut: std::future::Future<Output = Result<T>>,
9221{
9222 let runtime = build_io_runtime()?;
9223 runtime.block_on(async {
9224 let cx = Cx::current()
9225 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
9226 operation(cx).await
9227 })
9228}
9229
9230#[cfg(feature = "arrow")]
9233pub(crate) fn block_on_connection<F, Fut, T>(operation: F) -> Result<T>
9234where
9235 F: FnOnce(Cx) -> Fut,
9236 Fut: std::future::Future<Output = Result<T>>,
9237{
9238 block_on_io(operation)
9239}
9240
9241#[derive(Clone, Debug, Eq, PartialEq)]
9242struct IncomingPacket {
9243 packet_type: u8,
9244 packet_flags: u8,
9245 payload: Vec<u8>,
9246}
9247
9248async fn lock_write<W>(
9249 cx: &Cx,
9250 write: &Arc<AsyncMutex<W>>,
9251) -> Result<asupersync::sync::OwnedMutexGuard<W>>
9252where
9253 W: AsyncWrite + std::fmt::Debug + Unpin,
9254{
9255 asupersync::sync::OwnedMutexGuard::lock(Arc::clone(write), cx)
9260 .await
9261 .map_err(|err| Error::Runtime(err.to_string()))
9262}
9263
9264async fn write_all_shared<W>(cx: &Cx, write: &Arc<AsyncMutex<W>>, packet: &[u8]) -> Result<()>
9265where
9266 W: AsyncWrite + std::fmt::Debug + Unpin,
9267{
9268 let mut guard = lock_write(cx, write).await?;
9269 guard.write_all(packet).await?;
9270 guard.flush().await?;
9271 Ok(())
9272}
9273
9274async fn shutdown_write_shared<W>(cx: &Cx, write: &Arc<AsyncMutex<W>>) -> Result<()>
9275where
9276 W: AsyncWrite + std::fmt::Debug + Unpin,
9277{
9278 let mut guard = lock_write(cx, write).await?;
9279 guard.shutdown().await?;
9280 Ok(())
9281}
9282
9283async fn send_data_packet_shared<W>(
9284 cx: &Cx,
9285 write: &Arc<AsyncMutex<W>>,
9286 payload: &[u8],
9287 sdu: usize,
9288) -> Result<()>
9289where
9290 W: AsyncWrite + std::fmt::Debug + Unpin,
9291{
9292 let mut guard = lock_write(cx, write).await?;
9293 send_data_packet(&mut *guard, payload, sdu).await
9294}
9295
9296async fn send_data_packet_shared_with_width<W>(
9297 cx: &Cx,
9298 write: &Arc<AsyncMutex<W>>,
9299 payload: &[u8],
9300 sdu: usize,
9301 width: PacketLengthWidth,
9302) -> Result<()>
9303where
9304 W: AsyncWrite + std::fmt::Debug + Unpin,
9305{
9306 let mut guard = lock_write(cx, write).await?;
9307 send_data_packet_with_flags_and_width(&mut *guard, payload, sdu, 0, 0, width).await
9308}
9309
9310async fn send_data_packet_shared_with_flags<W>(
9311 cx: &Cx,
9312 write: &Arc<AsyncMutex<W>>,
9313 payload: &[u8],
9314 sdu: usize,
9315 first_packet_flags: u16,
9316 last_packet_flags: u16,
9317) -> Result<()>
9318where
9319 W: AsyncWrite + std::fmt::Debug + Unpin,
9320{
9321 let mut guard = lock_write(cx, write).await?;
9322 send_data_packet_with_flags(
9323 &mut *guard,
9324 payload,
9325 sdu,
9326 first_packet_flags,
9327 last_packet_flags,
9328 )
9329 .await
9330}
9331
9332async fn send_marker_shared<W>(cx: &Cx, write: &Arc<AsyncMutex<W>>, marker_type: u8) -> Result<()>
9333where
9334 W: AsyncWrite + std::fmt::Debug + Unpin,
9335{
9336 let mut guard = lock_write(cx, write).await?;
9337 send_marker(&mut *guard, marker_type).await
9338}
9339
9340fn lock_write_for_recovery<W>(
9341 write: &Arc<AsyncMutex<W>>,
9342) -> Result<asupersync::sync::OwnedMutexGuard<W>>
9343where
9344 W: AsyncWrite + std::fmt::Debug + Unpin,
9345{
9346 asupersync::sync::OwnedMutexGuard::try_lock(Arc::clone(write)).map_err(|err| match err {
9347 asupersync::sync::TryLockError::Locked => Error::ConnectionClosed(
9348 "write lock unavailable while recovering from cancellation".into(),
9349 ),
9350 asupersync::sync::TryLockError::Poisoned => {
9351 Error::ConnectionClosed("write lock poisoned while recovering from cancellation".into())
9352 }
9353 })
9354}
9355
9356pub(crate) async fn send_marker_recovery<W>(
9357 write: &Arc<AsyncMutex<W>>,
9358 marker_type: u8,
9359) -> Result<()>
9360where
9361 W: AsyncWrite + std::fmt::Debug + Unpin,
9362{
9363 let mut guard = lock_write_for_recovery(write)?;
9364 send_marker(&mut *guard, marker_type).await
9365}
9366
9367async fn send_data_packet<W>(stream: &mut W, payload: &[u8], sdu: usize) -> Result<()>
9368where
9369 W: AsyncWrite + Unpin,
9370{
9371 send_data_packet_with_flags(stream, payload, sdu, 0, 0).await
9372}
9373
9374async fn send_data_packet_with_flags<W>(
9381 stream: &mut W,
9382 payload: &[u8],
9383 sdu: usize,
9384 first_packet_flags: u16,
9385 last_packet_flags: u16,
9386) -> Result<()>
9387where
9388 W: AsyncWrite + Unpin,
9389{
9390 send_data_packet_with_flags_and_width(
9391 stream,
9392 payload,
9393 sdu,
9394 first_packet_flags,
9395 last_packet_flags,
9396 PacketLengthWidth::Large32,
9397 )
9398 .await
9399}
9400
9401async fn send_data_packet_with_flags_and_width<W>(
9402 stream: &mut W,
9403 payload: &[u8],
9404 sdu: usize,
9405 first_packet_flags: u16,
9406 last_packet_flags: u16,
9407 width: PacketLengthWidth,
9408) -> Result<()>
9409where
9410 W: AsyncWrite + Unpin,
9411{
9412 let max_payload = sdu.saturating_sub(TNS_DATA_PACKET_OVERHEAD).max(1);
9413 let chunk_count = payload.chunks(max_payload).len();
9414 for (index, chunk) in payload.chunks(max_payload).enumerate() {
9415 let mut flags = 0u16;
9416 if index == 0 {
9417 flags |= first_packet_flags;
9418 }
9419 if index + 1 == chunk_count {
9420 flags |= last_packet_flags;
9421 }
9422 let packet = encode_packet(TNS_PACKET_TYPE_DATA, 0, Some(flags), chunk, width)?;
9423 stream.write_all(&packet).await?;
9424 }
9425 match stream.flush().await {
9435 Ok(()) => {}
9436 Err(err) if is_asupersync_cancellation(&err) => {}
9437 Err(err) => return Err(err.into()),
9438 }
9439 Ok(())
9440}
9441
9442struct DataResponse {
9443 payload: Vec<u8>,
9444 flush_out_binds: bool,
9445}
9446
9447#[cfg(test)]
9448async fn read_data_response<R, W>(
9449 read: &mut R,
9450 cx: &Cx,
9451 write: &Arc<AsyncMutex<W>>,
9452) -> Result<Vec<u8>>
9453where
9454 R: AsyncRead + Unpin,
9455 W: AsyncWrite + std::fmt::Debug + Unpin,
9456{
9457 read_data_response_with_limits(read, cx, write, ProtocolLimits::DEFAULT).await
9458}
9459
9460async fn read_data_response_with_limits<R, W>(
9461 read: &mut R,
9462 cx: &Cx,
9463 write: &Arc<AsyncMutex<W>>,
9464 limits: ProtocolLimits,
9465) -> Result<Vec<u8>>
9466where
9467 R: AsyncRead + Unpin,
9468 W: AsyncWrite + std::fmt::Debug + Unpin,
9469{
9470 Ok(
9471 read_data_response_boundary_with_limits(read, cx, write, false, limits)
9472 .await?
9473 .payload,
9474 )
9475}
9476
9477const ASUPERSYNC_TLS_MISSING_CLOSE_NOTIFY: &str = "tls connection closed without close_notify";
9478
9479#[derive(Clone, Copy, Debug, Eq, PartialEq)]
9480enum SessionCloseDisposition {
9481 ResponseComplete,
9482 PeerHardClosed,
9483}
9484
9485fn is_missing_tls_close_notify(error: &std::io::Error) -> bool {
9486 error.kind() == std::io::ErrorKind::UnexpectedEof
9487 && error.to_string() == ASUPERSYNC_TLS_MISSING_CLOSE_NOTIFY
9488}
9489
9490struct SessionCloseRead<'a, R> {
9500 inner: &'a mut R,
9501 application_bytes: usize,
9502 missing_close_notify_at_boundary: bool,
9503}
9504
9505impl<'a, R> SessionCloseRead<'a, R> {
9506 fn new(inner: &'a mut R) -> Self {
9507 Self {
9508 inner,
9509 application_bytes: 0,
9510 missing_close_notify_at_boundary: false,
9511 }
9512 }
9513}
9514
9515impl<R> AsyncRead for SessionCloseRead<'_, R>
9516where
9517 R: AsyncRead + Unpin,
9518{
9519 fn poll_read(
9520 self: std::pin::Pin<&mut Self>,
9521 context: &mut std::task::Context<'_>,
9522 buffer: &mut asupersync::io::ReadBuf<'_>,
9523 ) -> std::task::Poll<std::io::Result<()>> {
9524 let this = self.get_mut();
9525 let filled_before = buffer.filled().len();
9526 match std::pin::Pin::new(&mut *this.inner).poll_read(context, buffer) {
9527 std::task::Poll::Ready(Ok(())) => {
9528 this.application_bytes += buffer.filled().len() - filled_before;
9529 std::task::Poll::Ready(Ok(()))
9530 }
9531 std::task::Poll::Ready(Err(error))
9532 if this.application_bytes == 0 && is_missing_tls_close_notify(&error) =>
9533 {
9534 this.missing_close_notify_at_boundary = true;
9535 std::task::Poll::Ready(Ok(()))
9536 }
9537 other => other,
9538 }
9539 }
9540}
9541
9542async fn read_session_close_response_with_limits<R, W, P>(
9546 read: &mut R,
9547 cx: &Cx,
9548 write: &Arc<AsyncMutex<W>>,
9549 classic: bool,
9550 probe: &P,
9551 limits: ProtocolLimits,
9552) -> Result<SessionCloseDisposition>
9553where
9554 R: AsyncRead + Unpin,
9555 W: AsyncWrite + std::fmt::Debug + Unpin,
9556 P: Fn(&[u8]) -> bool,
9557{
9558 let mut close_read = SessionCloseRead::new(read);
9559 let result = if classic {
9560 read_classic_data_response_probed_with_limits(&mut close_read, cx, write, probe, limits)
9561 .await
9562 .map(|_| SessionCloseDisposition::ResponseComplete)
9563 } else {
9564 read_data_response_with_limits(&mut close_read, cx, write, limits)
9565 .await
9566 .map(|_| SessionCloseDisposition::ResponseComplete)
9567 };
9568 match result {
9569 Err(_) if close_read.missing_close_notify_at_boundary => {
9570 Ok(SessionCloseDisposition::PeerHardClosed)
9571 }
9572 other => other,
9573 }
9574}
9575
9576async fn read_classic_data_response_with_limits<R, W>(
9589 read: &mut R,
9590 cx: &Cx,
9591 write: &Arc<AsyncMutex<W>>,
9592 limits: ProtocolLimits,
9593) -> Result<Vec<u8>>
9594where
9595 R: AsyncRead + Unpin,
9596 W: AsyncWrite + std::fmt::Debug + Unpin,
9597{
9598 let mut response = Vec::new();
9599 let mut pending_packet: Option<IncomingPacket> = None;
9600 let mut after_reset = false;
9601 loop {
9602 let packet = match pending_packet.take() {
9603 Some(packet) => packet,
9604 None => read_packet_with_limits(read, PacketLengthWidth::Large32, limits).await?,
9605 };
9606 if packet.packet_type == TNS_PACKET_TYPE_MARKER {
9607 pending_packet =
9608 reset_after_marker_with_limits(read, cx, write, &packet, limits).await?;
9609 after_reset = true;
9610 continue;
9611 }
9612 if packet.packet_type != TNS_PACKET_TYPE_DATA {
9613 return Err(Error::UnexpectedPacket(packet.packet_type));
9614 }
9615 let payload =
9616 packet
9617 .payload
9618 .get(2..)
9619 .ok_or(oracledb_protocol::ProtocolError::TtcDecode(
9620 "missing data packet flags",
9621 ))?;
9622 let combined = response.len().checked_add(payload.len()).ok_or(
9623 oracledb_protocol::ProtocolError::ResourceLimit {
9624 limit: "response_bytes",
9625 observed: usize::MAX,
9626 maximum: limits.max_response_bytes,
9627 },
9628 )?;
9629 limits.check_response_bytes(combined)?;
9630 response.extend_from_slice(payload);
9631 if (after_reset && post_reset_packet_ends_response(payload))
9632 || classic_connect_response_is_complete(&response, limits)?
9633 {
9634 return Ok(response);
9635 }
9636 }
9637}
9638
9639fn response_complete<T>(result: &oracledb_protocol::Result<T>) -> bool {
9649 !matches!(result, Err(oracledb_protocol::ProtocolError::TtcDecode(_)))
9650}
9651
9652async fn read_classic_data_response_probed_with_limits<R, W, P>(
9660 read: &mut R,
9661 cx: &Cx,
9662 write: &Arc<AsyncMutex<W>>,
9663 probe: &P,
9664 limits: ProtocolLimits,
9665) -> Result<Vec<u8>>
9666where
9667 R: AsyncRead + Unpin,
9668 W: AsyncWrite + std::fmt::Debug + Unpin,
9669 P: Fn(&[u8]) -> bool,
9670{
9671 let mut response = Vec::new();
9672 read_classic_data_response_probed_into(read, cx, write, probe, limits, &mut response).await?;
9673 Ok(response)
9674}
9675
9676async fn read_classic_data_response_probed_into<R, W, P>(
9681 read: &mut R,
9682 cx: &Cx,
9683 write: &Arc<AsyncMutex<W>>,
9684 probe: &P,
9685 limits: ProtocolLimits,
9686 response: &mut Vec<u8>,
9687) -> Result<()>
9688where
9689 R: AsyncRead + Unpin,
9690 W: AsyncWrite + std::fmt::Debug + Unpin,
9691 P: Fn(&[u8]) -> bool,
9692{
9693 let mut pending_packet: Option<IncomingPacket> = None;
9694 let mut after_reset = false;
9695 loop {
9696 let packet = match pending_packet.take() {
9697 Some(packet) => packet,
9698 None => read_packet_with_limits(read, PacketLengthWidth::Large32, limits).await?,
9699 };
9700 if packet.packet_type == TNS_PACKET_TYPE_MARKER {
9701 pending_packet =
9702 reset_after_marker_with_limits(read, cx, write, &packet, limits).await?;
9703 after_reset = true;
9704 continue;
9705 }
9706 if packet.packet_type != TNS_PACKET_TYPE_DATA {
9707 return Err(Error::UnexpectedPacket(packet.packet_type));
9708 }
9709 let payload =
9710 packet
9711 .payload
9712 .get(2..)
9713 .ok_or(oracledb_protocol::ProtocolError::TtcDecode(
9714 "missing data packet flags",
9715 ))?;
9716 let combined = response.len().checked_add(payload.len()).ok_or(
9717 oracledb_protocol::ProtocolError::ResourceLimit {
9718 limit: "response_bytes",
9719 observed: usize::MAX,
9720 maximum: limits.max_response_bytes,
9721 },
9722 )?;
9723 limits.check_response_bytes(combined)?;
9724 response.extend_from_slice(payload);
9725 if (after_reset && post_reset_packet_ends_response(payload)) || probe(response) {
9726 return Ok(());
9727 }
9728 }
9729}
9730
9731async fn read_classic_data_response_flushing_out_binds_probed_with_limits<R, W, P>(
9738 read: &mut R,
9739 cx: &Cx,
9740 write: &Arc<AsyncMutex<W>>,
9741 sdu: usize,
9742 probe: &P,
9743 limits: ProtocolLimits,
9744) -> Result<Vec<u8>>
9745where
9746 R: AsyncRead + Unpin,
9747 W: AsyncWrite + std::fmt::Debug + Unpin,
9748 P: Fn(&[u8]) -> bool,
9749{
9750 let mut payload = Vec::new();
9751 read_classic_data_response_probed_into(read, cx, write, probe, limits, &mut payload).await?;
9752 while matches!(payload.last(), Some(&TNS_MSG_TYPE_FLUSH_OUT_BINDS)) {
9753 observe_cancellation_between_round_trips(cx)?;
9754 payload.pop();
9755 send_data_packet_shared(cx, write, &[TNS_MSG_TYPE_FLUSH_OUT_BINDS], sdu).await?;
9756 read_classic_data_response_probed_into(read, cx, write, probe, limits, &mut payload)
9757 .await?;
9758 }
9759 Ok(payload)
9760}
9761
9762const BREAK_DRAIN_RECOVERY_TIMEOUT: Duration = Duration::from_secs(5);
9769
9770#[allow(dead_code)]
9800async fn break_and_drain_wire<R, W>(
9801 read: &mut R,
9802 write: &Arc<AsyncMutex<W>>,
9803 recovery_timeout: Duration,
9804) -> Result<()>
9805where
9806 R: AsyncRead + Unpin,
9807 W: AsyncWrite + std::fmt::Debug + Unpin,
9808{
9809 let result = time::timeout(
9810 time::wall_now(),
9811 recovery_timeout,
9812 break_and_drain_wire_unbounded(read, write),
9813 )
9814 .await
9815 .ok();
9816 classify_recovery_result(RecoveryWireAction::BreakAndDrain, result)
9817}
9818
9819async fn break_and_drain_wire_unbounded<R, W>(
9820 read: &mut R,
9821 write: &Arc<AsyncMutex<W>>,
9822) -> Result<()>
9823where
9824 R: AsyncRead + Unpin,
9825 W: AsyncWrite + std::fmt::Debug + Unpin,
9826{
9827 break_and_drain_wire_unbounded_with_limits(read, write, ProtocolLimits::DEFAULT, false).await
9828}
9829
9830async fn break_and_drain_wire_unbounded_with_limits<R, W>(
9831 read: &mut R,
9832 write: &Arc<AsyncMutex<W>>,
9833 limits: ProtocolLimits,
9834 classic: bool,
9835) -> Result<()>
9836where
9837 R: AsyncRead + Unpin,
9838 W: AsyncWrite + std::fmt::Debug + Unpin,
9839{
9840 send_marker_recovery(write, TNS_MARKER_TYPE_BREAK)
9842 .await
9843 .map_err(|err| {
9844 Error::ConnectionClosed(format!(
9845 "failed to send break marker on call timeout: {err}"
9846 ))
9847 })?;
9848 drain_break_response_recovery_with_limits(read, write, limits, classic).await
9850}
9851
9852#[allow(dead_code)]
9875async fn cancel_and_drain_wire<R, W>(
9876 read: &mut R,
9877 write: &Arc<AsyncMutex<W>>,
9878 recovery_timeout: Duration,
9879) -> Result<()>
9880where
9881 R: AsyncRead + Unpin,
9882 W: AsyncWrite + std::fmt::Debug + Unpin,
9883{
9884 break_and_drain_wire(read, write, recovery_timeout).await
9885}
9886
9887#[allow(dead_code)]
9904async fn drain_cancel_wire<R, W>(
9905 read: &mut R,
9906 write: &Arc<AsyncMutex<W>>,
9907 recovery_timeout: Duration,
9908) -> Result<()>
9909where
9910 R: AsyncRead + Unpin,
9911 W: AsyncWrite + std::fmt::Debug + Unpin,
9912{
9913 let result = time::timeout(
9914 time::wall_now(),
9915 recovery_timeout,
9916 drain_cancel_wire_unbounded(read, write),
9917 )
9918 .await
9919 .ok();
9920 classify_recovery_result(RecoveryWireAction::DrainCancel, result)
9921}
9922
9923async fn drain_cancel_wire_unbounded<R, W>(read: &mut R, write: &Arc<AsyncMutex<W>>) -> Result<()>
9924where
9925 R: AsyncRead + Unpin,
9926 W: AsyncWrite + std::fmt::Debug + Unpin,
9927{
9928 drain_cancel_wire_unbounded_with_limits(read, write, ProtocolLimits::DEFAULT, false).await
9929}
9930
9931async fn drain_cancel_wire_unbounded_with_limits<R, W>(
9932 read: &mut R,
9933 write: &Arc<AsyncMutex<W>>,
9934 limits: ProtocolLimits,
9935 classic: bool,
9936) -> Result<()>
9937where
9938 R: AsyncRead + Unpin,
9939 W: AsyncWrite + std::fmt::Debug + Unpin,
9940{
9941 drain_break_response_recovery_with_limits(read, write, limits, classic).await
9942}
9943
9944struct CancelDrainGuard {
9960 recovery: Arc<SessionRecovery>,
9961 armed: bool,
9962}
9963
9964impl CancelDrainGuard {
9965 fn arm(recovery: Arc<SessionRecovery>) -> Result<Self> {
9967 recovery.begin_or_adopt_operation()?;
9968 Ok(Self {
9969 recovery,
9970 armed: true,
9971 })
9972 }
9973
9974 fn disarm(&mut self) {
9977 self.recovery.complete_operation();
9978 self.armed = false;
9979 }
9980}
9981
9982impl Drop for CancelDrainGuard {
9983 fn drop(&mut self) {
9984 if self.armed {
9985 self.recovery.mark_break_required();
9988 }
9989 }
9990}
9991
9992#[cfg(test)]
9998#[allow(dead_code)]
9999async fn drain_break_response_recovery<R, W>(read: &mut R, write: &Arc<AsyncMutex<W>>) -> Result<()>
10000where
10001 R: AsyncRead + Unpin,
10002 W: AsyncWrite + std::fmt::Debug + Unpin,
10003{
10004 drain_break_response_recovery_with_limits(read, write, ProtocolLimits::DEFAULT, false).await
10005}
10006
10007async fn drain_break_response_recovery_with_limits<R, W>(
10008 read: &mut R,
10009 write: &Arc<AsyncMutex<W>>,
10010 limits: ProtocolLimits,
10011 classic: bool,
10012) -> Result<()>
10013where
10014 R: AsyncRead + Unpin,
10015 W: AsyncWrite + std::fmt::Debug + Unpin,
10016{
10017 let initial_marker = loop {
10022 let packet = read_packet_with_limits(read, PacketLengthWidth::Large32, limits).await?;
10023 match packet.packet_type {
10024 TNS_PACKET_TYPE_MARKER => break packet,
10025 TNS_PACKET_TYPE_DATA => {
10026 trace_connect_bytes("BREAK drain: discarded in-flight packet", &packet.payload);
10027 continue;
10028 }
10029 other => {
10030 return Err(Error::UnexpectedPacket(other));
10035 }
10036 }
10037 };
10038
10039 let pending =
10044 reset_after_marker_recovery_with_limits(read, write, &initial_marker, limits).await?;
10045
10046 let trailing = read_data_response_boundary_from_recovery_with_limits(
10050 read, write, pending, limits, classic,
10051 )
10052 .await?;
10053 trace_connect_bytes("BREAK drain: trailing error response", &trailing.payload);
10054 Ok(())
10055}
10056
10057#[cfg(test)]
10058async fn read_data_response_flushing_out_binds<R, W>(
10059 read: &mut R,
10060 cx: &Cx,
10061 write: &Arc<AsyncMutex<W>>,
10062 sdu: usize,
10063) -> Result<Vec<u8>>
10064where
10065 R: AsyncRead + Unpin,
10066 W: AsyncWrite + std::fmt::Debug + Unpin,
10067{
10068 read_data_response_flushing_out_binds_with_limits(read, cx, write, sdu, ProtocolLimits::DEFAULT)
10069 .await
10070}
10071
10072async fn read_data_response_flushing_out_binds_with_limits<R, W>(
10073 read: &mut R,
10074 cx: &Cx,
10075 write: &Arc<AsyncMutex<W>>,
10076 sdu: usize,
10077 limits: ProtocolLimits,
10078) -> Result<Vec<u8>>
10079where
10080 R: AsyncRead + Unpin,
10081 W: AsyncWrite + std::fmt::Debug + Unpin,
10082{
10083 let mut response =
10084 read_data_response_boundary_with_limits(read, cx, write, false, limits).await?;
10085 let mut payload = response.payload;
10086 while response.flush_out_binds {
10087 observe_cancellation_between_round_trips(cx)?;
10088 if matches!(payload.last(), Some(&TNS_MSG_TYPE_FLUSH_OUT_BINDS)) {
10089 payload.pop();
10090 }
10091 send_data_packet_shared(cx, write, &[TNS_MSG_TYPE_FLUSH_OUT_BINDS], sdu).await?;
10092 response = read_data_response_boundary_with_limits(read, cx, write, false, limits).await?;
10093 let combined = payload.len().checked_add(response.payload.len()).ok_or(
10094 oracledb_protocol::ProtocolError::ResourceLimit {
10095 limit: "response_bytes",
10096 observed: usize::MAX,
10097 maximum: limits.max_response_bytes,
10098 },
10099 )?;
10100 limits.check_response_bytes(combined)?;
10101 payload.extend_from_slice(&response.payload);
10102 }
10103 Ok(payload)
10104}
10105
10106fn data_packet_ends_response(flags: u16, payload: &[u8]) -> bool {
10126 if flags
10127 & (oracledb_protocol::thin::TNS_DATA_FLAGS_END_OF_RESPONSE
10128 | oracledb_protocol::thin::TNS_DATA_FLAGS_EOF)
10129 != 0
10130 {
10131 return true;
10132 }
10133 payload == [TNS_MSG_TYPE_END_OF_RESPONSE] || payload == [TNS_MSG_TYPE_FLUSH_OUT_BINDS]
10141}
10142
10143fn post_reset_packet_ends_response(payload: &[u8]) -> bool {
10165 matches!(
10166 payload.last(),
10167 Some(&TNS_MSG_TYPE_FLUSH_OUT_BINDS) | Some(&TNS_MSG_TYPE_END_OF_RESPONSE)
10168 )
10169}
10170
10171#[cfg(test)]
10178#[allow(dead_code)]
10179async fn read_data_response_boundary<R, W>(
10180 read: &mut R,
10181 cx: &Cx,
10182 write: &Arc<AsyncMutex<W>>,
10183 in_pipeline: bool,
10184) -> Result<DataResponse>
10185where
10186 R: AsyncRead + Unpin,
10187 W: AsyncWrite + std::fmt::Debug + Unpin,
10188{
10189 read_data_response_boundary_with_limits(read, cx, write, in_pipeline, ProtocolLimits::DEFAULT)
10190 .await
10191}
10192
10193async fn read_data_response_boundary_with_limits<R, W>(
10194 read: &mut R,
10195 cx: &Cx,
10196 write: &Arc<AsyncMutex<W>>,
10197 in_pipeline: bool,
10198 limits: ProtocolLimits,
10199) -> Result<DataResponse>
10200where
10201 R: AsyncRead + Unpin,
10202 W: AsyncWrite + std::fmt::Debug + Unpin,
10203{
10204 read_data_response_boundary_seeded(read, Some(cx), write, in_pipeline, None, limits, false)
10206 .await
10207}
10208
10209#[cfg(test)]
10215#[allow(dead_code)]
10216async fn read_data_response_boundary_from_recovery<R, W>(
10217 read: &mut R,
10218 write: &Arc<AsyncMutex<W>>,
10219 seed: Option<IncomingPacket>,
10220) -> Result<DataResponse>
10221where
10222 R: AsyncRead + Unpin,
10223 W: AsyncWrite + std::fmt::Debug + Unpin,
10224{
10225 read_data_response_boundary_from_recovery_with_limits(
10226 read,
10227 write,
10228 seed,
10229 ProtocolLimits::DEFAULT,
10230 false,
10231 )
10232 .await
10233}
10234
10235async fn read_data_response_boundary_from_recovery_with_limits<R, W>(
10236 read: &mut R,
10237 write: &Arc<AsyncMutex<W>>,
10238 seed: Option<IncomingPacket>,
10239 limits: ProtocolLimits,
10240 classic: bool,
10241) -> Result<DataResponse>
10242where
10243 R: AsyncRead + Unpin,
10244 W: AsyncWrite + std::fmt::Debug + Unpin,
10245{
10246 read_data_response_boundary_seeded(read, None, write, false, seed, limits, classic).await
10247}
10248
10249async fn read_data_response_boundary_seeded<R, W>(
10250 read: &mut R,
10251 cx: Option<&Cx>,
10252 write: &Arc<AsyncMutex<W>>,
10253 in_pipeline: bool,
10254 seed: Option<IncomingPacket>,
10255 limits: ProtocolLimits,
10256 classic: bool,
10257) -> Result<DataResponse>
10258where
10259 R: AsyncRead + Unpin,
10260 W: AsyncWrite + std::fmt::Debug + Unpin,
10261{
10262 let mut response = Vec::new();
10263 let mut pending_packet = seed;
10264 let mut after_reset = false;
10275 loop {
10276 let packet = match pending_packet.take() {
10277 Some(packet) => packet,
10278 None => read_packet_with_limits(read, PacketLengthWidth::Large32, limits).await?,
10279 };
10280 if packet.packet_type == TNS_PACKET_TYPE_MARKER {
10281 if in_pipeline {
10282 trace_connect_bytes("MARKER packet skipped in pipeline", &packet.payload);
10283 continue;
10284 }
10285 pending_packet = match cx {
10286 Some(cx) => {
10287 reset_after_marker_with_limits(read, cx, write, &packet, limits).await?
10288 }
10289 None => {
10290 reset_after_marker_recovery_with_limits(read, write, &packet, limits).await?
10291 }
10292 };
10293 after_reset = true;
10294 continue;
10295 }
10296 if packet.packet_type != TNS_PACKET_TYPE_DATA {
10297 return Err(Error::UnexpectedPacket(packet.packet_type));
10301 }
10302 let (data_flags, payload) = packet.payload.split_at_checked(2).ok_or(
10303 oracledb_protocol::ProtocolError::TtcDecode("missing data packet flags"),
10304 )?;
10305 let flags = u16::from_be_bytes(
10306 data_flags
10307 .try_into()
10308 .map_err(|_| oracledb_protocol::ProtocolError::TtcDecode("invalid flags"))?,
10309 );
10310 let ends = data_packet_ends_response(flags, payload)
10311 || (after_reset && post_reset_packet_ends_response(payload));
10312 if ends && response.is_empty() {
10322 limits.check_response_bytes(payload.len())?;
10323 response = packet.payload;
10324 response.drain(..2);
10325 break;
10326 }
10327 let combined = response.len().checked_add(payload.len()).ok_or(
10328 oracledb_protocol::ProtocolError::ResourceLimit {
10329 limit: "response_bytes",
10330 observed: usize::MAX,
10331 maximum: limits.max_response_bytes,
10332 },
10333 )?;
10334 limits.check_response_bytes(combined)?;
10335 response.extend_from_slice(payload);
10336 if ends {
10337 break;
10338 }
10339 if classic && classic_connect_response_is_complete(&response, limits).unwrap_or(false) {
10350 break;
10351 }
10352 }
10353 let flush_out_binds = matches!(response.last(), Some(&TNS_MSG_TYPE_FLUSH_OUT_BINDS));
10359 Ok(DataResponse {
10360 payload: response,
10361 flush_out_binds,
10362 })
10363}
10364
10365const TNS_PACKET_TYPE_MARKER: u8 = 12;
10366const TNS_MARKER_TYPE_BREAK: u8 = 1;
10367const TNS_MARKER_TYPE_RESET: u8 = 2;
10368
10369#[cfg(test)]
10370#[allow(dead_code)]
10371async fn reset_after_marker_recovery<R, W>(
10372 read: &mut R,
10373 write: &Arc<AsyncMutex<W>>,
10374 initial_marker: &IncomingPacket,
10375) -> Result<Option<IncomingPacket>>
10376where
10377 R: AsyncRead + Unpin,
10378 W: AsyncWrite + std::fmt::Debug + Unpin,
10379{
10380 reset_after_marker_recovery_with_limits(read, write, initial_marker, ProtocolLimits::DEFAULT)
10381 .await
10382}
10383
10384async fn reset_after_marker_recovery_with_limits<R, W>(
10385 read: &mut R,
10386 write: &Arc<AsyncMutex<W>>,
10387 initial_marker: &IncomingPacket,
10388 limits: ProtocolLimits,
10389) -> Result<Option<IncomingPacket>>
10390where
10391 R: AsyncRead + Unpin,
10392 W: AsyncWrite + std::fmt::Debug + Unpin,
10393{
10394 trace_connect_bytes("MARKER packet", &initial_marker.payload);
10395 send_marker_recovery(write, TNS_MARKER_TYPE_RESET).await?;
10396 drain_reset_markers_with_limits(read, limits).await
10397}
10398
10399#[cfg(test)]
10400#[allow(dead_code)]
10401async fn reset_after_marker<R, W>(
10402 read: &mut R,
10403 cx: &Cx,
10404 write: &Arc<AsyncMutex<W>>,
10405 initial_marker: &IncomingPacket,
10406) -> Result<Option<IncomingPacket>>
10407where
10408 R: AsyncRead + Unpin,
10409 W: AsyncWrite + std::fmt::Debug + Unpin,
10410{
10411 reset_after_marker_with_limits(read, cx, write, initial_marker, ProtocolLimits::DEFAULT).await
10412}
10413
10414async fn reset_after_marker_with_limits<R, W>(
10415 read: &mut R,
10416 cx: &Cx,
10417 write: &Arc<AsyncMutex<W>>,
10418 initial_marker: &IncomingPacket,
10419 limits: ProtocolLimits,
10420) -> Result<Option<IncomingPacket>>
10421where
10422 R: AsyncRead + Unpin,
10423 W: AsyncWrite + std::fmt::Debug + Unpin,
10424{
10425 trace_connect_bytes("MARKER packet", &initial_marker.payload);
10426 send_marker_shared(cx, write, TNS_MARKER_TYPE_RESET).await?;
10427 drain_reset_markers_with_limits(read, limits).await
10428}
10429
10430#[cfg(test)]
10431#[allow(dead_code)]
10432async fn drain_reset_markers<R>(read: &mut R) -> Result<Option<IncomingPacket>>
10433where
10434 R: AsyncRead + Unpin,
10435{
10436 drain_reset_markers_with_limits(read, ProtocolLimits::DEFAULT).await
10437}
10438
10439async fn drain_reset_markers_with_limits<R>(
10440 read: &mut R,
10441 limits: ProtocolLimits,
10442) -> Result<Option<IncomingPacket>>
10443where
10444 R: AsyncRead + Unpin,
10445{
10446 loop {
10456 let packet = read_packet_with_limits(read, PacketLengthWidth::Large32, limits).await?;
10457 if packet.packet_type != TNS_PACKET_TYPE_MARKER {
10458 return Ok(Some(packet));
10459 }
10460 trace_connect_bytes("MARKER reset response", &packet.payload);
10461 }
10462}
10463
10464async fn send_marker<W>(stream: &mut W, marker_type: u8) -> Result<()>
10465where
10466 W: AsyncWrite + Unpin,
10467{
10468 let packet = encode_packet(
10469 TNS_PACKET_TYPE_MARKER,
10470 0,
10471 None,
10472 &[1, 0, marker_type],
10473 PacketLengthWidth::Large32,
10474 )?;
10475 trace_connect_bytes("send MARKER", &packet);
10476 stream.write_all(&packet).await?;
10477 stream.flush().await?;
10478 Ok(())
10479}
10480
10481#[cfg(test)]
10482#[allow(dead_code)]
10483async fn read_packet<R>(stream: &mut R, width: PacketLengthWidth) -> Result<IncomingPacket>
10484where
10485 R: AsyncRead + Unpin,
10486{
10487 read_packet_with_limits(stream, width, ProtocolLimits::DEFAULT).await
10488}
10489
10490fn keepalive_idle_from_expire_time(expire_time_minutes: u32) -> Option<Duration> {
10495 (expire_time_minutes > 0).then(|| Duration::from_secs(u64::from(expire_time_minutes) * 60))
10496}
10497
10498#[derive(Debug)]
10501struct InactivityReadExpired(u32);
10502
10503impl std::fmt::Display for InactivityReadExpired {
10504 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
10505 write!(formatter, "read made no progress for {} ms", self.0)
10506 }
10507}
10508
10509impl std::error::Error for InactivityReadExpired {}
10510
10511struct InactivityRead<'a, R> {
10515 inner: &'a mut R,
10516 timeout: Option<Duration>,
10517 idle_sleep: Option<std::pin::Pin<Box<time::Sleep>>>,
10518}
10519
10520impl<'a, R> InactivityRead<'a, R> {
10521 fn new(inner: &'a mut R, timeout: Option<Duration>) -> Self {
10522 Self {
10523 inner,
10524 timeout,
10525 idle_sleep: None,
10526 }
10527 }
10528}
10529
10530impl<R> AsyncRead for InactivityRead<'_, R>
10531where
10532 R: AsyncRead + Unpin,
10533{
10534 fn poll_read(
10535 self: std::pin::Pin<&mut Self>,
10536 context: &mut std::task::Context<'_>,
10537 buffer: &mut asupersync::io::ReadBuf<'_>,
10538 ) -> std::task::Poll<std::io::Result<()>> {
10539 let this = self.get_mut();
10540 let filled_before = buffer.filled().len();
10541 match std::pin::Pin::new(&mut *this.inner).poll_read(context, buffer) {
10542 std::task::Poll::Ready(Ok(())) => {
10543 if buffer.filled().len() > filled_before {
10544 this.idle_sleep = None;
10545 }
10546 std::task::Poll::Ready(Ok(()))
10547 }
10548 std::task::Poll::Ready(Err(error)) => std::task::Poll::Ready(Err(error)),
10549 std::task::Poll::Pending => {
10550 let Some(timeout) = this.timeout else {
10551 return std::task::Poll::Pending;
10552 };
10553 let sleep = this
10554 .idle_sleep
10555 .get_or_insert_with(|| Box::pin(time::sleep(time::wall_now(), timeout)));
10556 if sleep.as_mut().poll(context).is_ready() {
10557 std::task::Poll::Ready(Err(std::io::Error::new(
10558 std::io::ErrorKind::TimedOut,
10559 InactivityReadExpired(duration_to_millis_saturating(timeout)),
10560 )))
10561 } else {
10562 std::task::Poll::Pending
10563 }
10564 }
10565 }
10566 }
10567}
10568
10569fn map_inactivity_timeout<U>(result: Result<U>) -> Result<U> {
10570 match result {
10571 Err(Error::Io(error)) => {
10572 let timeout = error
10573 .get_ref()
10574 .and_then(|source| source.downcast_ref::<InactivityReadExpired>())
10575 .map(|marker| marker.0);
10576 match timeout {
10577 Some(timeout) => Err(Error::CallTimeout(timeout)),
10578 None => Err(Error::Io(error)),
10579 }
10580 }
10581 other => other,
10582 }
10583}
10584
10585async fn read_packet_with_limits<R>(
10586 stream: &mut R,
10587 width: PacketLengthWidth,
10588 limits: ProtocolLimits,
10589) -> Result<IncomingPacket>
10590where
10591 R: AsyncRead + Unpin,
10592{
10593 let mut header = [0u8; 8];
10594 stream.read_exact(&mut header).await?;
10595 let [len0, len1, len2, len3, packet_type, packet_flags, _, _] = header;
10596 let declared = match width {
10597 PacketLengthWidth::Legacy16 => usize::from(u16::from_be_bytes([len0, len1])),
10598 PacketLengthWidth::Large32 => {
10599 usize::try_from(u32::from_be_bytes([len0, len1, len2, len3])).unwrap_or(usize::MAX)
10600 }
10601 };
10602 if declared < header.len() {
10603 return Err(oracledb_protocol::ProtocolError::InvalidPacketLength {
10604 length: declared,
10605 minimum: header.len(),
10606 }
10607 .into());
10608 }
10609 limits.check_packet_bytes(declared)?;
10610 let mut payload = vec![0u8; declared - header.len()];
10611 stream.read_exact(&mut payload).await?;
10612 Ok(IncomingPacket {
10613 packet_type,
10614 packet_flags,
10615 payload,
10616 })
10617}
10618
10619#[derive(Clone, Debug, Eq, PartialEq)]
10622struct RedirectTarget {
10623 host: String,
10625 port: u16,
10627 connect_data: String,
10630}
10631
10632fn redirect_payload_prefix(payload: &[u8]) -> Result<(usize, &[u8])> {
10639 let Some((length_bytes, inline)) = payload.split_first_chunk::<2>() else {
10640 return Err(Error::InvalidRedirectData(format!(
10641 "REDIRECT packet payload of {} byte(s) is too short for the u16 redirect-data length",
10642 payload.len()
10643 )));
10644 };
10645 let declared = usize::from(u16::from_be_bytes(*length_bytes));
10646 let take = inline.len().min(declared);
10647 Ok((declared, &inline[..take]))
10648}
10649
10650async fn read_redirect_data(core: &mut DriverCore, first_payload: &[u8]) -> Result<String> {
10656 let (declared, inline) = redirect_payload_prefix(first_payload)?;
10657 let mut data = inline.to_vec();
10658 while data.len() < declared {
10659 let packet = core.read_packet(PacketLengthWidth::Legacy16).await?;
10660 if packet.packet_type != TNS_PACKET_TYPE_REDIRECT
10661 && packet.packet_type != TNS_PACKET_TYPE_DATA
10662 {
10663 return Err(Error::UnexpectedPacket(packet.packet_type));
10664 }
10665 if packet.payload.is_empty() {
10666 return Err(Error::InvalidRedirectData(format!(
10667 "listener stopped short of the declared redirect data \
10668 ({} of {declared} byte(s) received)",
10669 data.len()
10670 )));
10671 }
10672 data.extend_from_slice(&packet.payload);
10673 }
10674 data.truncate(declared);
10675 String::from_utf8(data)
10676 .map_err(|_| Error::InvalidRedirectData("redirect data is not valid UTF-8".to_string()))
10677}
10678
10679fn parse_redirect_target(
10691 redirect_data: &str,
10692 original_protocol: NetProtocol,
10693) -> Result<RedirectTarget> {
10694 let Some((address_part, connect_data)) = redirect_data.split_once('\0') else {
10695 return Err(Error::InvalidRedirectData(
10696 "missing NUL separator between the redirect address and its connect data".to_string(),
10697 ));
10698 };
10699 let descriptor = connectstring_parse(address_part).map_err(|err| {
10700 Error::InvalidRedirectData(format!("unparseable redirect address: {err}"))
10701 })?;
10702 let address = descriptor
10703 .as_ref()
10704 .and_then(|descriptor| descriptor.first_address())
10705 .ok_or_else(|| {
10706 Error::InvalidRedirectData("redirect address defines no usable endpoint".to_string())
10707 })?;
10708 let host = address
10709 .host
10710 .clone()
10711 .ok_or_else(|| Error::InvalidRedirectData("redirect address has no HOST".to_string()))?;
10712 let target_protocol: NetProtocol = address.protocol.into();
10713 if target_protocol.is_tls() != original_protocol.is_tls() {
10714 return Err(Error::RedirectUnsupported);
10715 }
10716 Ok(RedirectTarget {
10717 host,
10718 port: address.port,
10719 connect_data: connect_data.to_string(),
10720 })
10721}
10722
10723fn connectstring_parse(
10726 input: &str,
10727) -> std::result::Result<
10728 Option<oracledb_protocol::net::connectstring::Descriptor>,
10729 oracledb_protocol::ProtocolError,
10730> {
10731 oracledb_protocol::net::connectstring::parse(input)
10732}
10733
10734fn transport_connect_timeout_duration(seconds: f64) -> Duration {
10735 let seconds = if seconds.is_finite() && seconds > 0.0 {
10736 seconds
10737 } else {
10738 20.0
10739 };
10740 Duration::from_secs_f64(seconds.max(0.001))
10741}
10742
10743fn effective_ssl_server_dn_match(description: &Description, options: &ConnectOptions) -> bool {
10747 description.security.ssl_server_dn_match && options.ssl_server_dn_match
10748}
10749
10750fn resolve_effective_sdu(options_sdu: u16, description: &Description) -> u32 {
10759 const BUILDER_DEFAULT_SDU: u16 = 8192;
10760 if options_sdu != BUILDER_DEFAULT_SDU {
10761 u32::from(options_sdu)
10762 } else if description.sdu != DSN_DEFAULT_SDU {
10763 description.sdu
10764 } else {
10765 u32::from(BUILDER_DEFAULT_SDU)
10766 }
10767}
10768
10769#[derive(Clone, Debug, PartialEq, Eq)]
10771struct ConnectAddress {
10772 host: String,
10773 port: u16,
10774}
10775
10776#[derive(Debug)]
10785enum TransportEstablishmentError {
10786 Io(std::io::Error),
10787 TlsHandshake(tls::TlsHandshakeError),
10788}
10789
10790impl TransportEstablishmentError {
10791 fn is_terminal(&self) -> bool {
10792 match self {
10793 Self::Io(_) => false,
10794 Self::TlsHandshake(error) => error.is_terminal(),
10795 }
10796 }
10797
10798 fn into_driver_error(self) -> Error {
10799 match self {
10800 Self::Io(error) => Error::Io(error),
10801 Self::TlsHandshake(error) => error.into_driver_error(),
10802 }
10803 }
10804}
10805
10806impl std::fmt::Display for TransportEstablishmentError {
10807 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
10808 match self {
10809 Self::Io(error) => write!(f, "I/O error: {error}"),
10810 Self::TlsHandshake(error) => write!(f, "{error}"),
10811 }
10812 }
10813}
10814
10815impl From<std::io::Error> for TransportEstablishmentError {
10816 fn from(error: std::io::Error) -> Self {
10817 Self::Io(error)
10818 }
10819}
10820
10821impl From<tls::TlsHandshakeError> for TransportEstablishmentError {
10822 fn from(error: tls::TlsHandshakeError) -> Self {
10823 Self::TlsHandshake(error)
10824 }
10825}
10826
10827fn resolve_connect_addresses(
10836 descriptor: &Descriptor,
10837 protocol: NetProtocol,
10838) -> Vec<ConnectAddress> {
10839 let mut out: Vec<ConnectAddress> = Vec::new();
10840 let mut descriptions: Vec<&Description> = descriptor.descriptions.iter().collect();
10841 if descriptor.load_balance {
10842 shuffle_in_place(&mut descriptions);
10843 }
10844 for description in descriptions {
10845 let mut lists: Vec<&AddressList> = description.address_lists.iter().collect();
10846 if description.load_balance {
10847 shuffle_in_place(&mut lists);
10848 }
10849 for list in lists {
10850 let mut addresses: Vec<&Address> = list
10851 .addresses
10852 .iter()
10853 .filter(|address| {
10854 address.host.is_some() && NetProtocol::from(address.protocol) == protocol
10855 })
10856 .collect();
10857 if list.load_balance || description.load_balance {
10858 shuffle_in_place(&mut addresses);
10859 }
10860 let limit = if list.failover {
10861 addresses.len()
10862 } else {
10863 addresses.len().min(1)
10864 };
10865 for address in addresses.into_iter().take(limit) {
10866 if let Some(host) = &address.host {
10867 out.push(ConnectAddress {
10868 host: host.clone(),
10869 port: address.port,
10870 });
10871 }
10872 }
10873 }
10874 }
10875 out
10876}
10877
10878fn shuffle_in_place<T>(items: &mut [T]) {
10882 if items.len() < 2 {
10883 return;
10884 }
10885 let mut state = shuffle_seed();
10886 for i in (1..items.len()).rev() {
10887 state ^= state << 13;
10889 state ^= state >> 7;
10890 state ^= state << 17;
10891 let j = (state % (i as u64 + 1)) as usize;
10892 items.swap(i, j);
10893 }
10894}
10895
10896fn shuffle_seed() -> u64 {
10897 use std::time::{SystemTime, UNIX_EPOCH};
10898 let nanos = SystemTime::now()
10899 .duration_since(UNIX_EPOCH)
10900 .map(|d| d.as_nanos() as u64)
10901 .unwrap_or(0x9E37_79B9_7F4A_7C15);
10902 (nanos ^ nanos.rotate_left(32).wrapping_mul(0x2545_F491_4F6C_DD1D)) | 1
10904}
10905
10906fn listener_connect_descriptor_with_server(
10911 descriptor: &EasyConnect,
10912 description: &Description,
10913 identity: &ClientIdentity,
10914 server_type_emon: bool,
10915 token_auth: bool,
10916 ssl_server_dn_match: bool,
10917 ssl_server_cert_dn: Option<&str>,
10918) -> String {
10919 let address = descriptor_address_clause(descriptor);
10920 let connect_data = listener_connect_data_clause(description, identity, server_type_emon);
10921 let security = descriptor_security_clause(
10922 descriptor.protocol,
10923 description,
10924 token_auth,
10925 ssl_server_dn_match,
10926 ssl_server_cert_dn,
10927 );
10928 format!("(DESCRIPTION={}{}{})", address, connect_data, security)
10929}
10930
10931fn auth_connect_descriptor(
10932 descriptor: &EasyConnect,
10933 description: &Description,
10934 token_auth: bool,
10935 ssl_server_dn_match: bool,
10936 ssl_server_cert_dn: Option<&str>,
10937) -> String {
10938 let address = descriptor_address_clause(descriptor);
10939 let connect_data = auth_connect_data_clause(description, descriptor);
10940 let security = descriptor_security_clause(
10941 descriptor.protocol,
10942 description,
10943 token_auth,
10944 ssl_server_dn_match,
10945 ssl_server_cert_dn,
10946 );
10947 format!("(DESCRIPTION={}{}{})", address, connect_data, security)
10948}
10949
10950fn descriptor_address_clause(descriptor: &EasyConnect) -> String {
10951 let protocol = match descriptor.protocol {
10952 NetProtocol::Tcp => "tcp",
10953 NetProtocol::Tcps => "tcps",
10954 };
10955 format!(
10956 "(ADDRESS=(PROTOCOL={})(HOST={})(PORT={}))",
10957 protocol, descriptor.host, descriptor.port
10958 )
10959}
10960
10961fn listener_connect_data_clause(
10962 description: &Description,
10963 identity: &ClientIdentity,
10964 server_type_emon: bool,
10965) -> String {
10966 let mut out = auth_connect_data_clause_from_service(description, None);
10967 if server_type_emon {
10968 out.push_str("(SERVER=emon)");
10969 } else if let Some(server_type) = description.connect_data.server_type {
10970 out.push_str("(SERVER=");
10971 out.push_str(server_type.as_str());
10972 out.push(')');
10973 }
10974 for (key, value) in &description.connect_data.extra {
10975 out.push('(');
10976 out.push_str(key);
10977 out.push('=');
10978 out.push_str(value);
10979 out.push(')');
10980 }
10981 out.push_str("(CID=(PROGRAM=");
10982 out.push_str(&identity.program);
10983 out.push_str(")(HOST=");
10984 out.push_str(&identity.machine);
10985 out.push_str(")(USER=");
10986 out.push_str(&identity.osuser);
10987 out.push_str(")))");
10988 out
10989}
10990
10991fn auth_connect_data_clause(description: &Description, descriptor: &EasyConnect) -> String {
10992 let mut out =
10993 auth_connect_data_clause_from_service(description, Some(&descriptor.service_name));
10994 if let Some(server_type) = description.connect_data.server_type {
10995 out.push_str("(SERVER=");
10996 out.push_str(server_type.as_str());
10997 out.push(')');
10998 }
10999 for (key, value) in &description.connect_data.extra {
11000 out.push('(');
11001 out.push_str(key);
11002 out.push('=');
11003 out.push_str(value);
11004 out.push(')');
11005 }
11006 out.push(')');
11007 out
11008}
11009
11010fn auth_connect_data_clause_from_service(
11011 description: &Description,
11012 fallback_service_name: Option<&str>,
11013) -> String {
11014 let service_name = description
11015 .connect_data
11016 .service_name
11017 .as_deref()
11018 .or(fallback_service_name)
11019 .unwrap_or("");
11020 format!("(CONNECT_DATA=(SERVICE_NAME={service_name})")
11021}
11022
11023fn descriptor_security_clause(
11024 protocol: NetProtocol,
11025 description: &Description,
11026 token_auth: bool,
11027 ssl_server_dn_match: bool,
11028 ssl_server_cert_dn: Option<&str>,
11029) -> String {
11030 let security = &description.security;
11031 if !protocol.is_tls()
11032 && !token_auth
11033 && ssl_server_dn_match
11034 && ssl_server_cert_dn.is_none()
11035 && security.extra.is_empty()
11036 {
11037 return String::new();
11038 }
11039
11040 let mut out = String::from("(SECURITY=");
11041 if protocol.is_tls() || !ssl_server_dn_match {
11042 out.push_str("(SSL_SERVER_DN_MATCH=");
11043 out.push_str(if ssl_server_dn_match { "ON" } else { "OFF" });
11044 out.push(')');
11045 }
11046 if let Some(cert_dn) = ssl_server_cert_dn {
11047 out.push_str("(SSL_SERVER_CERT_DN=");
11048 out.push_str(cert_dn);
11049 out.push(')');
11050 }
11051 for (key, value) in &security.extra {
11052 if key.eq_ignore_ascii_case("TOKEN_AUTH") {
11053 continue;
11054 }
11055 out.push('(');
11056 out.push_str(key);
11057 out.push('=');
11058 out.push_str(value);
11059 out.push(')');
11060 }
11061 if token_auth {
11062 out.push_str("(TOKEN_AUTH=OCI_TOKEN)");
11063 }
11064 out.push(')');
11065 out
11066}
11067
11068fn parse_session_u32(
11069 data: &std::collections::BTreeMap<String, String>,
11070 key: &'static str,
11071) -> Result<u32> {
11072 data.get(key)
11073 .ok_or(Error::MissingSessionField(key))?
11074 .parse::<u32>()
11075 .map_err(|_| Error::MissingSessionField(key))
11076}
11077
11078fn parse_session_u16(
11079 data: &std::collections::BTreeMap<String, String>,
11080 key: &'static str,
11081) -> Result<u16> {
11082 data.get(key)
11088 .ok_or(Error::MissingSessionField(key))?
11089 .parse::<u16>()
11090 .map_err(|_| Error::MissingSessionField(key))
11091}
11092
11093fn parse_db_unique_name(data: &std::collections::BTreeMap<String, String>) -> Option<String> {
11098 data.get("AUTH_SC_REAL_DBUNIQUE_NAME").cloned()
11099}
11100
11101fn next_ttc_sequence(seq_num: &mut u8) -> u8 {
11102 *seq_num = seq_num.wrapping_add(1);
11103 if *seq_num == 0 {
11104 *seq_num = 1;
11105 }
11106 *seq_num
11107}
11108
11109fn statement_cache_insert(
11118 cache: &mut Vec<CachedStatement>,
11119 capacity: usize,
11120 sql: &str,
11121 cursor_id: u32,
11122 mut bind_shape: Vec<BindShapeSlot>,
11123) -> Vec<u32> {
11124 let mut to_close = Vec::new();
11125 if cursor_id == 0 {
11126 return to_close;
11127 }
11128 if let Some(index) = cache.iter().position(|entry| entry.sql == sql) {
11129 let old = cache.remove(index);
11130 if old.cursor_id != 0 && old.cursor_id != cursor_id {
11131 to_close.push(old.cursor_id);
11132 } else if old.cursor_id == cursor_id && old.bind_shape.len() == bind_shape.len() {
11133 for (slot, old_slot) in bind_shape.iter_mut().zip(old.bind_shape) {
11138 if slot.untyped_null {
11139 *slot = old_slot;
11140 }
11141 }
11142 }
11143 }
11144 cache.push(CachedStatement {
11145 sql: sql.to_string(),
11146 cursor_id,
11147 bind_shape,
11148 });
11149 while cache.len() > capacity {
11150 let evicted = cache.remove(0);
11151 if evicted.cursor_id != 0 {
11152 to_close.push(evicted.cursor_id);
11153 }
11154 }
11155 to_close
11156}
11157
11158#[derive(Clone, Debug, PartialEq, Eq)]
11162struct CachedStatement {
11163 sql: String,
11164 cursor_id: u32,
11165 bind_shape: Vec<BindShapeSlot>,
11166}
11167
11168#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11176struct BindShapeSlot {
11177 family: u8,
11182 csfrm: u8,
11185 untyped_null: bool,
11189}
11190
11191fn bind_type_family(ora_type_num: u8) -> u8 {
11194 use oracledb_protocol::thin::{
11195 ORA_TYPE_NUM_CHAR, ORA_TYPE_NUM_LONG, ORA_TYPE_NUM_LONG_RAW, ORA_TYPE_NUM_RAW,
11196 ORA_TYPE_NUM_VARCHAR,
11197 };
11198 match ora_type_num {
11199 ORA_TYPE_NUM_CHAR | ORA_TYPE_NUM_LONG => ORA_TYPE_NUM_VARCHAR,
11200 ORA_TYPE_NUM_LONG_RAW => ORA_TYPE_NUM_RAW,
11201 other => other,
11202 }
11203}
11204
11205fn bind_type_shape(bind_rows: &[Vec<BindValue>]) -> Vec<BindShapeSlot> {
11210 use oracledb_protocol::thin::{bind_value_type_info, CS_FORM_IMPLICIT, ORA_TYPE_NUM_VARCHAR};
11211 let Some(first_row) = bind_rows.first() else {
11212 return Vec::new();
11213 };
11214 (0..first_row.len())
11215 .map(|index| {
11216 let info = bind_rows
11217 .iter()
11218 .find_map(|row| row.get(index).and_then(bind_value_type_info));
11219 match info {
11220 Some(info) => BindShapeSlot {
11221 family: bind_type_family(info.ora_type_num),
11222 csfrm: info.csfrm,
11223 untyped_null: false,
11224 },
11225 None => BindShapeSlot {
11226 family: ORA_TYPE_NUM_VARCHAR,
11227 csfrm: CS_FORM_IMPLICIT,
11228 untyped_null: true,
11229 },
11230 }
11231 })
11232 .collect()
11233}
11234
11235fn bind_shape_is_compatible(cached: &[BindShapeSlot], new: &[BindShapeSlot]) -> bool {
11240 cached.len() == new.len()
11241 && cached.iter().zip(new).all(|(cached, new)| {
11242 new.untyped_null || (cached.family == new.family && cached.csfrm == new.csfrm)
11243 })
11244}
11245
11246fn statement_is_query(sql: &str) -> bool {
11253 let mut cursor = SqlStatementCursor::new(sql);
11254 match cursor.next_keyword() {
11255 Some(keyword) if keyword.eq_ignore_ascii_case("select") => true,
11256 Some(keyword) if keyword.eq_ignore_ascii_case("with") => cursor
11257 .cte_statement_keyword()
11258 .is_some_and(|keyword| keyword.eq_ignore_ascii_case("select")),
11259 _ => false,
11260 }
11261}
11262
11263struct SqlStatementCursor<'a> {
11267 bytes: &'a [u8],
11268 index: usize,
11269}
11270
11271impl<'a> SqlStatementCursor<'a> {
11272 fn new(sql: &'a str) -> Self {
11273 Self {
11274 bytes: sql.as_bytes(),
11275 index: 0,
11276 }
11277 }
11278
11279 fn next_keyword(&mut self) -> Option<&'a str> {
11280 self.skip_trivia();
11281 let start = self.index;
11282 while self
11283 .bytes
11284 .get(self.index)
11285 .is_some_and(u8::is_ascii_alphabetic)
11286 {
11287 self.index += 1;
11288 }
11289 (start != self.index)
11290 .then(|| std::str::from_utf8(&self.bytes[start..self.index]).ok())
11291 .flatten()
11292 }
11293
11294 fn cte_statement_keyword(&mut self) -> Option<&'a str> {
11301 loop {
11302 if !self.consume_identifier() {
11303 return None;
11304 }
11305 self.skip_trivia();
11306 if self.peek() == Some(b'(') && !self.consume_parentheses() {
11307 return None;
11308 }
11309 if !self.consume_keyword("as") || !self.consume_parentheses() {
11310 return None;
11311 }
11312 self.skip_trivia();
11313 if self.peek() == Some(b',') {
11314 self.index += 1;
11315 continue;
11316 }
11317 return self.next_keyword();
11318 }
11319 }
11320
11321 fn consume_keyword(&mut self, expected: &str) -> bool {
11322 self.next_keyword()
11323 .is_some_and(|keyword| keyword.eq_ignore_ascii_case(expected))
11324 }
11325
11326 fn consume_identifier(&mut self) -> bool {
11327 self.skip_trivia();
11328 if self.peek() == Some(b'\"') {
11329 return self.consume_quoted_identifier();
11330 }
11331 let start = self.index;
11332 if !self.peek().is_some_and(|byte| byte.is_ascii_alphabetic()) {
11333 return false;
11334 }
11335 while self
11336 .bytes
11337 .get(self.index)
11338 .is_some_and(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'$' | b'#'))
11339 {
11340 self.index += 1;
11341 }
11342 start != self.index
11343 }
11344
11345 fn consume_parentheses(&mut self) -> bool {
11346 self.skip_trivia();
11347 if self.peek() != Some(b'(') {
11348 return false;
11349 }
11350 let mut depth = 0_u32;
11351 while let Some(byte) = self.peek() {
11352 match byte {
11353 b'\'' => {
11354 if !self.consume_single_quoted() {
11355 return false;
11356 }
11357 }
11358 b'\"' => {
11359 if !self.consume_quoted_identifier() {
11360 return false;
11361 }
11362 }
11363 b'q' | b'Q' if self.bytes.get(self.index + 1) == Some(&b'\'') => {
11364 if !self.consume_q_quoted() {
11365 return false;
11366 }
11367 }
11368 b'-' if self.bytes.get(self.index + 1) == Some(&b'-') => {
11369 self.skip_line_comment();
11370 }
11371 b'/' if self.bytes.get(self.index + 1) == Some(&b'*') => {
11372 if !self.skip_block_comment() {
11373 return false;
11374 }
11375 }
11376 b'(' => {
11377 depth += 1;
11378 self.index += 1;
11379 }
11380 b')' => {
11381 if depth == 0 {
11382 return false;
11383 }
11384 depth -= 1;
11385 self.index += 1;
11386 if depth == 0 {
11387 return true;
11388 }
11389 }
11390 _ => self.index += 1,
11391 }
11392 }
11393 false
11394 }
11395
11396 fn skip_trivia(&mut self) {
11397 loop {
11398 while self.peek().is_some_and(|byte| byte.is_ascii_whitespace()) {
11399 self.index += 1;
11400 }
11401 match (self.peek(), self.bytes.get(self.index + 1)) {
11402 (Some(b'-'), Some(b'-')) => self.skip_line_comment(),
11403 (Some(b'/'), Some(b'*')) => {
11404 if !self.skip_block_comment() {
11405 return;
11406 }
11407 }
11408 _ => return,
11409 }
11410 }
11411 }
11412
11413 fn consume_single_quoted(&mut self) -> bool {
11414 debug_assert_eq!(self.peek(), Some(b'\''));
11415 self.index += 1;
11416 while let Some(byte) = self.peek() {
11417 self.index += 1;
11418 if byte == b'\'' {
11419 if self.peek() == Some(b'\'') {
11420 self.index += 1;
11421 } else {
11422 return true;
11423 }
11424 }
11425 }
11426 false
11427 }
11428
11429 fn consume_quoted_identifier(&mut self) -> bool {
11430 debug_assert_eq!(self.peek(), Some(b'\"'));
11431 self.index += 1;
11432 while let Some(byte) = self.peek() {
11433 self.index += 1;
11434 if byte == b'\"' {
11435 if self.peek() == Some(b'\"') {
11436 self.index += 1;
11437 } else {
11438 return true;
11439 }
11440 }
11441 }
11442 false
11443 }
11444
11445 fn consume_q_quoted(&mut self) -> bool {
11446 debug_assert!(matches!(self.peek(), Some(b'q' | b'Q')));
11447 let Some(&opening) = self.bytes.get(self.index + 2) else {
11448 return false;
11449 };
11450 let closing = match opening {
11451 b'[' => b']',
11452 b'{' => b'}',
11453 b'(' => b')',
11454 b'<' => b'>',
11455 other => other,
11456 };
11457 self.index += 3;
11458 while self.index + 1 < self.bytes.len() {
11459 if self.bytes[self.index] == closing && self.bytes[self.index + 1] == b'\'' {
11460 self.index += 2;
11461 return true;
11462 }
11463 self.index += 1;
11464 }
11465 false
11466 }
11467
11468 fn skip_line_comment(&mut self) {
11469 debug_assert_eq!(
11470 self.bytes.get(self.index..self.index + 2),
11471 Some(b"--".as_slice())
11472 );
11473 self.index += 2;
11474 while self
11475 .peek()
11476 .is_some_and(|byte| !matches!(byte, b'\n' | b'\r'))
11477 {
11478 self.index += 1;
11479 }
11480 }
11481
11482 fn skip_block_comment(&mut self) -> bool {
11483 debug_assert_eq!(
11484 self.bytes.get(self.index..self.index + 2),
11485 Some(b"/*".as_slice())
11486 );
11487 self.index += 2;
11488 while self.index + 1 < self.bytes.len() {
11489 if self.bytes[self.index] == b'*' && self.bytes[self.index + 1] == b'/' {
11490 self.index += 2;
11491 return true;
11492 }
11493 self.index += 1;
11494 }
11495 false
11496 }
11497
11498 fn peek(&self) -> Option<u8> {
11499 self.bytes.get(self.index).copied()
11500 }
11501}
11502
11503fn columns_require_define(columns: &[ColumnMetadata]) -> bool {
11508 use oracledb_protocol::thin::{
11509 ORA_TYPE_NUM_BLOB, ORA_TYPE_NUM_CLOB, ORA_TYPE_NUM_JSON, ORA_TYPE_NUM_VECTOR,
11510 };
11511 columns.iter().any(|column| {
11512 matches!(
11513 column.ora_type_num(),
11514 ORA_TYPE_NUM_CLOB | ORA_TYPE_NUM_BLOB | ORA_TYPE_NUM_VECTOR | ORA_TYPE_NUM_JSON
11515 )
11516 })
11517}
11518
11519fn columns_have_lob_prefetch_fields(columns: &[ColumnMetadata]) -> bool {
11520 use oracledb_protocol::thin::{ORA_TYPE_NUM_BLOB, ORA_TYPE_NUM_CLOB};
11521 columns
11522 .iter()
11523 .any(|column| matches!(column.ora_type_num(), ORA_TYPE_NUM_CLOB | ORA_TYPE_NUM_BLOB))
11524}
11525
11526fn json_lob_probe_candidates(columns: &[ColumnMetadata]) -> Vec<(usize, String)> {
11532 use oracledb_protocol::thin::{ORA_TYPE_NUM_BLOB, ORA_TYPE_NUM_CLOB};
11533 columns
11534 .iter()
11535 .enumerate()
11536 .filter(|(_, metadata)| {
11537 !metadata.is_json()
11538 && matches!(
11539 metadata.ora_type_num(),
11540 ORA_TYPE_NUM_CLOB | ORA_TYPE_NUM_BLOB
11541 )
11542 && !metadata.name().is_empty()
11543 })
11544 .map(|(index, metadata)| (index, metadata.name().to_ascii_uppercase()))
11545 .collect()
11546}
11547
11548fn trace_connect_step(step: &'static str) {
11549 if std::env::var_os("ORACLEDB_TRACE_CONNECT").is_some() {
11550 eprintln!("oracledb::connect: {step}");
11551 }
11552}
11553
11554fn trace_connect_value(label: &'static str, value: &str) {
11555 if std::env::var_os("ORACLEDB_TRACE_CONNECT").is_some() {
11556 eprintln!("oracledb::connect: {label}: {value}");
11557 }
11558}
11559
11560fn trace_connect_bytes(label: &'static str, bytes: &[u8]) {
11561 if std::env::var_os("ORACLEDB_TRACE_CONNECT").is_some() {
11562 let mut hex = String::with_capacity(bytes.len() * 2);
11563 for byte in bytes {
11564 use std::fmt::Write as _;
11565 let _ = write!(&mut hex, "{byte:02x}");
11566 }
11567 eprintln!("oracledb::connect: {label} len={} hex={hex}", bytes.len());
11568 }
11569}
11570
11571fn trace_query_bytes(label: &'static str, bytes: &[u8]) {
11572 if std::env::var_os("ORACLEDB_TRACE_QUERY").is_some() {
11573 let mut hex = String::with_capacity(bytes.len() * 2);
11574 for byte in bytes {
11575 use std::fmt::Write as _;
11576 let _ = write!(&mut hex, "{byte:02x}");
11577 }
11578 eprintln!("oracledb::query: {label} len={} hex={hex}", bytes.len());
11579 }
11580}
11581
11582#[cfg(test)]
11583mod tests {
11584 use super::*;
11585 use asupersync::lab::{DporExplorer, ExplorerConfig, LabRuntime};
11586 use asupersync::types::{Budget, CancelKind, Time};
11587 use oracledb_protocol::thin::QueryValue;
11588 use std::future::{poll_fn, Future};
11589 use std::io::Read;
11590 use std::net::TcpListener;
11591 use std::pin::pin;
11592 use std::sync::atomic::{AtomicBool, Ordering};
11593 use std::task::{Poll, Waker};
11594 use std::thread;
11595 use std::time::{Duration, Instant};
11596
11597 #[test]
11598 fn statement_is_query_recognizes_select_after_cte_list() {
11599 for sql in [
11600 "WITH x AS (SELECT 1 AS id FROM dual) SELECT id FROM x",
11601 concat!(
11602 "WITH first_cte AS (SELECT 1 AS id FROM dual), ",
11603 "second_cte AS (SELECT id + 1 AS id FROM first_cte) ",
11604 "SELECT id FROM second_cte"
11605 ),
11606 concat!(
11607 "WITH outer_cte AS (",
11608 "WITH inner_cte AS (SELECT q'[)]' AS marker FROM dual) ",
11609 "SELECT marker FROM inner_cte",
11610 ") SELECT marker FROM outer_cte"
11611 ),
11612 ] {
11613 assert!(
11614 statement_is_query(sql),
11615 "CTE SELECT must use query path: {sql}"
11616 );
11617 }
11618 }
11619
11620 #[test]
11621 fn statement_is_query_rejects_cte_prefixed_dml_and_plsql() {
11622 for sql in [
11623 "WITH x AS (SELECT 1 AS id FROM dual) INSERT INTO cte_target (id) SELECT id FROM x",
11624 "WITH x AS (SELECT 1 AS id FROM dual) UPDATE cte_target SET id = id + 1",
11625 "WITH x AS (SELECT 1 AS id FROM dual) DELETE FROM cte_target WHERE id IN (SELECT id FROM x)",
11626 concat!(
11627 "WITH x AS (SELECT 1 AS id FROM dual) ",
11628 "MERGE INTO cte_target target USING x ON (target.id = x.id) ",
11629 "WHEN MATCHED THEN UPDATE SET target.id = x.id"
11630 ),
11631 concat!(
11632 "WITH FUNCTION answer RETURN NUMBER IS BEGIN RETURN 42; END; ",
11633 "SELECT answer FROM dual"
11634 ),
11635 ] {
11636 assert!(
11637 !statement_is_query(sql),
11638 "only SELECT after a conventional CTE list may use query path: {sql}"
11639 );
11640 }
11641 }
11642
11643 #[test]
11644 fn statement_is_query_skips_leading_whitespace_and_comments() {
11645 for sql in [
11646 " \n\t-- leading line comment\nWITH x AS (SELECT 1 FROM dual) SELECT * FROM x",
11647 "/* leading block comment */ WITH x AS (SELECT 1 FROM dual) SELECT * FROM x",
11648 "/* leading block comment */\nSELECT 1 FROM dual",
11649 ] {
11650 assert!(
11651 statement_is_query(sql),
11652 "comments must not hide a query: {sql}"
11653 );
11654 }
11655 }
11656
11657 fn connect_packet_sdu(payload: &[u8]) -> u16 {
11662 u16::from_be_bytes([payload[6], payload[7]])
11663 }
11664
11665 #[test]
11666 fn f1_dsn_sdu_reaches_the_connect_config() {
11667 let desc = EasyConnect::parse_descriptor(
11670 "(DESCRIPTION=(SDU=32768)(ADDRESS=(PROTOCOL=tcp)(HOST=h)(PORT=1521))\
11671 (CONNECT_DATA=(SERVICE_NAME=svc)))",
11672 )
11673 .expect("descriptor parses");
11674 let primary = desc.first_description();
11675 assert_eq!(primary.sdu, 32768, "parser must capture DSN SDU");
11676 let advertised =
11678 u16::try_from(resolve_effective_sdu(8192, primary)).expect("32768 fits u16");
11679 assert_eq!(advertised, 32768);
11680 let payload = build_connect_packet_payload(
11682 "(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=h)(PORT=1521))\
11683 (CONNECT_DATA=(SERVICE_NAME=svc)))",
11684 advertised,
11685 )
11686 .expect("payload builds");
11687 assert_eq!(connect_packet_sdu(&payload), 32768);
11688 }
11689
11690 #[test]
11691 fn f1_effective_sdu_precedence() {
11692 let dsn = EasyConnect::parse_descriptor(
11693 "(DESCRIPTION=(SDU=16384)(ADDRESS=(PROTOCOL=tcp)(HOST=h)(PORT=1))\
11694 (CONNECT_DATA=(SERVICE_NAME=s)))",
11695 )
11696 .expect("parse");
11697 let desc = dsn.first_description();
11698 assert_eq!(resolve_effective_sdu(4096, desc), 4096);
11700 assert_eq!(resolve_effective_sdu(8192, desc), 16384);
11702 let plain = EasyConnect::parse_descriptor(
11704 "(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=h)(PORT=1))(CONNECT_DATA=(SERVICE_NAME=s)))",
11705 )
11706 .expect("parse");
11707 assert_eq!(resolve_effective_sdu(8192, plain.first_description()), 8192);
11708 }
11709
11710 #[test]
11711 fn f1_dsn_transport_connect_timeout_reaches_the_deadline() {
11712 let desc = EasyConnect::parse_descriptor("h:1521/svc?transport_connect_timeout=3.5")
11715 .expect("EZConnect-Plus parses");
11716 let secs = desc.first_description().tcp_connect_timeout;
11717 assert!((secs - 3.5).abs() < 1e-9, "parser captured {secs}");
11718 let deadline = transport_connect_timeout_duration(secs);
11719 assert_eq!(deadline, Duration::from_secs_f64(3.5));
11720 let default_desc = EasyConnect::parse_descriptor("h:1521/svc").expect("parse");
11722 assert_eq!(
11723 transport_connect_timeout_duration(
11724 default_desc.first_description().tcp_connect_timeout
11725 ),
11726 Duration::from_secs(20)
11727 );
11728 }
11729
11730 #[test]
11734 fn a11_keepalive_idle_derives_from_expire_time_minutes() {
11735 assert_eq!(keepalive_idle_from_expire_time(0), None);
11736 assert_eq!(
11737 keepalive_idle_from_expire_time(1),
11738 Some(Duration::from_secs(60))
11739 );
11740 assert_eq!(
11741 keepalive_idle_from_expire_time(5),
11742 Some(Duration::from_secs(300))
11743 );
11744 let with_expire =
11747 EasyConnect::parse_descriptor("h:1521/svc?expire_time=2").expect("parse expire_time");
11748 assert_eq!(
11749 keepalive_idle_from_expire_time(with_expire.first_description().expire_time),
11750 Some(Duration::from_secs(120))
11751 );
11752 let default_desc = EasyConnect::parse_descriptor("h:1521/svc").expect("parse default");
11753 assert_eq!(
11754 keepalive_idle_from_expire_time(default_desc.first_description().expire_time),
11755 None
11756 );
11757 }
11758
11759 #[derive(Debug)]
11763 struct SilentRead;
11764
11765 impl asupersync::io::AsyncRead for SilentRead {
11766 fn poll_read(
11767 self: std::pin::Pin<&mut Self>,
11768 _cx: &mut std::task::Context<'_>,
11769 _buf: &mut asupersync::io::ReadBuf<'_>,
11770 ) -> std::task::Poll<std::io::Result<()>> {
11771 std::task::Poll::Pending
11772 }
11773 }
11774
11775 struct ProgressRead {
11778 bytes: VecDeque<u8>,
11779 delay: Duration,
11780 sleep: Option<std::pin::Pin<Box<time::Sleep>>>,
11781 }
11782
11783 impl ProgressRead {
11784 fn new(bytes: impl IntoIterator<Item = u8>, delay: Duration) -> Self {
11785 Self {
11786 bytes: bytes.into_iter().collect(),
11787 delay,
11788 sleep: None,
11789 }
11790 }
11791 }
11792
11793 impl asupersync::io::AsyncRead for ProgressRead {
11794 fn poll_read(
11795 mut self: std::pin::Pin<&mut Self>,
11796 context: &mut std::task::Context<'_>,
11797 buffer: &mut asupersync::io::ReadBuf<'_>,
11798 ) -> std::task::Poll<std::io::Result<()>> {
11799 if self.bytes.is_empty() {
11800 return std::task::Poll::Ready(Ok(()));
11801 }
11802 let delay = self.delay;
11803 let sleep = self
11804 .sleep
11805 .get_or_insert_with(|| Box::pin(time::sleep(time::wall_now(), delay)));
11806 if sleep.as_mut().poll(context).is_pending() {
11807 return std::task::Poll::Pending;
11808 }
11809 self.sleep = None;
11810 buffer.put_slice(&[self.bytes.pop_front().expect("checked non-empty")]);
11811 std::task::Poll::Ready(Ok(()))
11812 }
11813 }
11814
11815 #[test]
11819 fn a11_inactivity_deadline_fires_on_a_silent_server() {
11820 let runtime = build_io_runtime().expect("io runtime");
11821 let elapsed = runtime.block_on(async {
11822 let mut source = SilentRead;
11823 let mut reader = InactivityRead::new(&mut source, Some(Duration::from_millis(200)));
11824 let started = std::time::Instant::now();
11825 let result = map_inactivity_timeout(
11826 read_packet_with_limits(
11827 &mut reader,
11828 PacketLengthWidth::Large32,
11829 ProtocolLimits::DEFAULT,
11830 )
11831 .await,
11832 );
11833 let elapsed = started.elapsed();
11834 assert!(
11835 matches!(result, Err(Error::CallTimeout(_))),
11836 "expected CallTimeout, got {result:?}"
11837 );
11838 elapsed
11839 });
11840 assert!(
11843 elapsed >= Duration::from_millis(150),
11844 "deadline fired too early ({elapsed:?}); it must honour the configured 200ms"
11845 );
11846 assert!(
11847 elapsed < Duration::from_secs(5),
11848 "deadline should fire promptly, took {elapsed:?}"
11849 );
11850 }
11851
11852 #[test]
11857 fn a11_inactivity_deadline_resets_after_every_read_progress() {
11858 let runtime = build_io_runtime().expect("io runtime");
11859 runtime.block_on(async {
11860 let packet = encode_packet(
11861 TNS_PACKET_TYPE_DATA,
11862 0,
11863 None,
11864 &[0x01, 0x02],
11865 PacketLengthWidth::Large32,
11866 )
11867 .expect("packet");
11868 let mut source = ProgressRead::new(packet, Duration::from_millis(35));
11869 let mut reader = InactivityRead::new(&mut source, Some(Duration::from_millis(100)));
11870 let started = std::time::Instant::now();
11871 let packet = map_inactivity_timeout(
11872 read_packet_with_limits(
11873 &mut reader,
11874 PacketLengthWidth::Large32,
11875 ProtocolLimits::DEFAULT,
11876 )
11877 .await,
11878 )
11879 .expect("each byte arrived before the idle deadline");
11880 assert_eq!(packet.payload, vec![0x01, 0x02]);
11881 assert!(
11882 started.elapsed() > Duration::from_millis(250),
11883 "the whole packet must outlast one 100 ms inactivity window"
11884 );
11885 });
11886 }
11887
11888 #[test]
11889 fn f2_address_list_yields_all_addresses_in_order() {
11890 let desc = EasyConnect::parse_descriptor(
11891 "(DESCRIPTION=(ADDRESS_LIST=\
11892 (ADDRESS=(PROTOCOL=tcp)(HOST=primary)(PORT=1521))\
11893 (ADDRESS=(PROTOCOL=tcp)(HOST=standby)(PORT=1522)))\
11894 (CONNECT_DATA=(SERVICE_NAME=svc)))",
11895 )
11896 .expect("parse");
11897 let addrs = resolve_connect_addresses(&desc, NetProtocol::Tcp);
11898 assert_eq!(
11899 addrs,
11900 vec![
11901 ConnectAddress {
11902 host: "primary".into(),
11903 port: 1521
11904 },
11905 ConnectAddress {
11906 host: "standby".into(),
11907 port: 1522
11908 },
11909 ]
11910 );
11911 }
11912
11913 #[test]
11914 fn f2_failover_off_keeps_only_first_address() {
11915 let desc = EasyConnect::parse_descriptor(
11916 "(DESCRIPTION=(ADDRESS_LIST=(FAILOVER=OFF)\
11917 (ADDRESS=(PROTOCOL=tcp)(HOST=only)(PORT=1521))\
11918 (ADDRESS=(PROTOCOL=tcp)(HOST=nope)(PORT=1522)))\
11919 (CONNECT_DATA=(SERVICE_NAME=svc)))",
11920 )
11921 .expect("parse");
11922 let addrs = resolve_connect_addresses(&desc, NetProtocol::Tcp);
11923 assert_eq!(
11924 addrs,
11925 vec![ConnectAddress {
11926 host: "only".into(),
11927 port: 1521
11928 }]
11929 );
11930 }
11931
11932 #[test]
11933 fn f2_load_balance_preserves_address_set() {
11934 let desc = EasyConnect::parse_descriptor(
11936 "(DESCRIPTION=(ADDRESS_LIST=(LOAD_BALANCE=ON)\
11937 (ADDRESS=(PROTOCOL=tcp)(HOST=a)(PORT=1))\
11938 (ADDRESS=(PROTOCOL=tcp)(HOST=b)(PORT=2))\
11939 (ADDRESS=(PROTOCOL=tcp)(HOST=c)(PORT=3)))\
11940 (CONNECT_DATA=(SERVICE_NAME=svc)))",
11941 )
11942 .expect("parse");
11943 let mut addrs = resolve_connect_addresses(&desc, NetProtocol::Tcp);
11944 addrs.sort_by_key(|address| address.port);
11945 assert_eq!(
11946 addrs,
11947 vec![
11948 ConnectAddress {
11949 host: "a".into(),
11950 port: 1
11951 },
11952 ConnectAddress {
11953 host: "b".into(),
11954 port: 2
11955 },
11956 ConnectAddress {
11957 host: "c".into(),
11958 port: 3
11959 },
11960 ]
11961 );
11962 }
11963
11964 #[test]
11965 fn f2_only_primary_protocol_addresses_are_candidates() {
11966 let desc = EasyConnect::parse_descriptor(
11969 "(DESCRIPTION=(ADDRESS_LIST=\
11970 (ADDRESS=(PROTOCOL=tcp)(HOST=plain)(PORT=1521))\
11971 (ADDRESS=(PROTOCOL=tcps)(HOST=secure)(PORT=2484)))\
11972 (CONNECT_DATA=(SERVICE_NAME=svc)))",
11973 )
11974 .expect("parse");
11975 assert_eq!(
11976 resolve_connect_addresses(&desc, NetProtocol::Tcp),
11977 vec![ConnectAddress {
11978 host: "plain".into(),
11979 port: 1521
11980 }]
11981 );
11982 assert_eq!(
11983 resolve_connect_addresses(&desc, NetProtocol::Tcps),
11984 vec![ConnectAddress {
11985 host: "secure".into(),
11986 port: 2484
11987 }]
11988 );
11989 }
11990
11991 #[test]
11992 fn f2_transport_attempt_type_only_covers_io_and_tls_handshake() {
11993 let io_failure = TransportEstablishmentError::from(std::io::Error::new(
11997 std::io::ErrorKind::ConnectionRefused,
11998 "refused",
11999 ));
12000 assert!(matches!(
12001 io_failure.into_driver_error(),
12002 Error::Io(error) if error.kind() == std::io::ErrorKind::ConnectionRefused
12003 ));
12004
12005 let tls_failure = TransportEstablishmentError::from(tls::TlsHandshakeError::new(
12006 "certificate rejected".to_string(),
12007 ));
12008 assert!(matches!(
12009 tls_failure.into_driver_error(),
12010 Error::Tls(detail) if detail == "TCPS handshake failed: certificate rejected"
12011 ));
12012 }
12013
12014 #[test]
12015 fn api_design_nothing_lost_map_covers_current_surface() {
12016 assert_eq!(BindValue::Null.variant_name(), "Null");
12023 assert_eq!(QueryValue::Text(String::new()).variant_name(), "Text");
12024
12025 let design = include_str!("../../../docs/API_DESIGN.md");
12026 for method in [
12027 "execute_query_for_registration",
12028 "execute_query",
12029 "execute_query_collect",
12030 "execute_query_with_timeout",
12031 "execute_query_with_binds",
12032 "execute_query_with_binds_and_timeout",
12033 "query",
12034 "query_named",
12035 "query_named_with_timeout",
12036 "execute_query_with_bind_rows",
12037 "execute_query_with_bind_rows_and_options",
12038 "execute_query_with_bind_rows_and_timeout",
12039 "execute_query_with_bind_rows_options_and_timeout",
12040 ] {
12041 assert!(design.contains(method), "API_DESIGN.md missing {method}");
12042 }
12043
12044 for group in 1..=24 {
12045 let marker = format!("C{group:02}");
12046 assert!(
12047 design.contains(&marker),
12048 "API_DESIGN.md missing capability group {marker}"
12049 );
12050 }
12051
12052 for field in [
12053 "batcherrors",
12054 "arraydmlrowcounts",
12055 "parse_only",
12056 "token_num",
12057 "cursor_id",
12058 "cache_statement",
12059 "scrollable",
12060 "fetch_orientation",
12061 "fetch_pos",
12062 "scroll_operation",
12063 "suspend_on_success",
12064 "no_prefetch",
12065 "registration_id",
12066 "max_string_size",
12067 ] {
12068 assert!(
12069 design.contains(field),
12070 "API_DESIGN.md missing ExecuteOptions field {field}"
12071 );
12072 }
12073
12074 let bind_variants = [
12075 "Null",
12076 "TypedNull",
12077 "Output",
12078 "ReturnOutput",
12079 "ObjectOutput",
12080 "ObjectInput",
12081 "Text",
12082 "Raw",
12083 "Lob",
12084 "Number",
12085 "BinaryInteger",
12086 "BinaryDouble",
12087 "BinaryFloat",
12088 "Boolean",
12089 "IntervalDS",
12090 "IntervalYM",
12091 "DateTime",
12092 "Timestamp",
12093 "TimestampTz",
12094 "Array",
12095 "Vector",
12096 "Json",
12097 "Cursor",
12098 ];
12099 assert_eq!(bind_variants.len(), 23);
12100 for variant in bind_variants {
12101 assert!(
12102 design.contains(&format!("`{variant}`")),
12103 "API_DESIGN.md missing BindValue::{variant}"
12104 );
12105 }
12106
12107 let query_variants = [
12108 "Text",
12109 "TextRaw",
12110 "Raw",
12111 "Rowid",
12112 "BinaryDouble",
12113 "IntervalDS",
12114 "IntervalYM",
12115 "Number",
12116 "Boolean",
12117 "Cursor",
12118 "DateTime",
12119 "TimestampTz",
12120 "Object",
12121 "Lob",
12122 "Vector",
12123 "Json",
12124 "Array",
12125 ];
12126 assert_eq!(query_variants.len(), 17);
12127 for variant in query_variants {
12128 assert!(
12129 design.contains(&format!("`{variant}`")),
12130 "API_DESIGN.md missing QueryValue::{variant}"
12131 );
12132 }
12133 }
12134
12135 #[test]
12136 fn migration_guide_covers_every_deprecated_method() {
12137 let guide = include_str!("../../../docs/MIGRATING-0.3.md");
12143 for method in [
12144 "execute_query",
12145 "execute_query_collect",
12146 "execute_query_with_timeout",
12147 "execute_query_with_binds",
12148 "execute_query_with_binds_and_timeout",
12149 "query_named",
12150 "query_named_with_timeout",
12151 "execute_query_with_bind_rows",
12152 "execute_query_with_bind_rows_and_options",
12153 "execute_query_with_bind_rows_and_timeout",
12154 "execute_query_with_bind_rows_options_and_timeout",
12155 "execute_query_for_registration",
12156 ] {
12157 assert!(
12158 guide.contains(method),
12159 "MIGRATING-0.3.md missing deprecated method {method}"
12160 );
12161 }
12162 for replacement in [
12164 "query_with",
12165 "execute_with",
12166 "execute_many",
12167 "execute_many_with",
12168 "register_query",
12169 "query_one",
12170 "query_opt",
12171 "query_all",
12172 "Query::timeout",
12173 "Execute::raw_options",
12174 "Batch::raw_options",
12175 "Registration::new",
12176 "execute_raw",
12177 ] {
12178 assert!(
12179 guide.contains(replacement),
12180 "MIGRATING-0.3.md missing replacement {replacement}"
12181 );
12182 }
12183 assert!(
12186 guide.contains("0.5.0"),
12187 "MIGRATING-0.3.md must state the shims are removed in 0.5.0"
12188 );
12189 }
12190
12191 fn cache_entry(sql: &str, cursor_id: u32) -> CachedStatement {
12192 CachedStatement {
12193 sql: sql.into(),
12194 cursor_id,
12195 bind_shape: Vec::new(),
12196 }
12197 }
12198
12199 #[test]
12200 fn statement_cache_evicts_lru_past_capacity() {
12201 let mut cache = Vec::new();
12202 assert!(statement_cache_insert(&mut cache, 2, "a", 10, Vec::new()).is_empty());
12204 assert!(statement_cache_insert(&mut cache, 2, "b", 11, Vec::new()).is_empty());
12205 assert_eq!(
12206 statement_cache_insert(&mut cache, 2, "c", 12, Vec::new()),
12207 vec![10]
12208 );
12209 assert_eq!(
12210 cache,
12211 vec![cache_entry("b", 11), cache_entry("c", 12)],
12212 "LRU order retained"
12213 );
12214 assert_eq!(
12217 statement_cache_insert(&mut cache, 2, "b", 99, Vec::new()),
12218 vec![11]
12219 );
12220 assert_eq!(cache, vec![cache_entry("c", 12), cache_entry("b", 99)]);
12221 }
12222
12223 #[test]
12224 fn statement_cache_size_zero_disables_caching() {
12225 let mut cache = Vec::new();
12226 assert_eq!(
12229 statement_cache_insert(&mut cache, 0, "a", 10, Vec::new()),
12230 vec![10]
12231 );
12232 assert!(cache.is_empty(), "size 0 must never retain a statement");
12233 assert!(statement_cache_insert(&mut cache, 5, "a", 0, Vec::new()).is_empty());
12235 assert!(cache.is_empty());
12236 }
12237
12238 fn shape_of(values: &[BindValue]) -> Vec<BindShapeSlot> {
12239 bind_type_shape(&[values.to_vec()])
12240 }
12241
12242 #[test]
12243 fn bind_type_shape_change_is_incompatible_but_size_change_is_not() {
12244 use oracledb_protocol::thin::ORA_TYPE_NUM_LONG;
12245 let number = shape_of(&[BindValue::Number("42".into())]);
12246 let short_text = shape_of(&[BindValue::Text("a".into())]);
12247 let long_text = shape_of(&[BindValue::Text("x".repeat(40_000))]);
12248 let raw = shape_of(&[BindValue::Raw(vec![1, 2, 3])]);
12249 assert!(!bind_shape_is_compatible(&number, &short_text));
12252 assert!(!bind_shape_is_compatible(&short_text, &number));
12253 assert!(!bind_shape_is_compatible(&short_text, &raw));
12254 assert!(!bind_shape_is_compatible(&raw, &number));
12255 assert!(bind_shape_is_compatible(&number, &number));
12258 assert!(bind_shape_is_compatible(&short_text, &long_text));
12259 assert!(bind_shape_is_compatible(&long_text, &short_text));
12260 let long_typed = shape_of(&[BindValue::TypedNull {
12263 ora_type_num: ORA_TYPE_NUM_LONG,
12264 csfrm: 1,
12265 buffer_size: 10,
12266 }]);
12267 assert!(bind_shape_is_compatible(&short_text, &long_typed));
12268 assert!(!bind_shape_is_compatible(
12270 &number,
12271 &shape_of(&[BindValue::Number("1".into()), BindValue::Number("2".into())])
12272 ));
12273 }
12274
12275 #[test]
12276 fn bind_type_shape_untyped_null_matches_any_cached_slot() {
12277 let number = shape_of(&[BindValue::Number("42".into())]);
12278 let null = shape_of(&[BindValue::Null]);
12279 assert!(null[0].untyped_null);
12280 assert!(bind_shape_is_compatible(&number, &null));
12283 assert!(!bind_shape_is_compatible(&null, &number));
12286 assert!(bind_shape_is_compatible(
12288 &null,
12289 &shape_of(&[BindValue::Text("x".into())])
12290 ));
12291 }
12292
12293 #[test]
12294 fn bind_type_shape_infers_column_type_from_first_non_null_row() {
12295 let rows = vec![
12298 vec![BindValue::Null],
12299 vec![BindValue::Number("7".into())],
12300 vec![BindValue::Null],
12301 ];
12302 let shape = bind_type_shape(&rows);
12303 assert!(!shape[0].untyped_null);
12304 assert_eq!(
12305 shape,
12306 shape_of(&[BindValue::Number("7".into())]),
12307 "inferred NUMBER column"
12308 );
12309 let all_null = bind_type_shape(&[vec![BindValue::Null], vec![BindValue::Null]]);
12311 assert!(all_null[0].untyped_null);
12312 }
12313
12314 #[test]
12315 fn statement_cache_insert_inherits_concrete_slot_over_untyped_null() {
12316 let number = shape_of(&[BindValue::Number("42".into())]);
12320 let null = shape_of(&[BindValue::Null]);
12321 let mut cache = Vec::new();
12322 assert!(statement_cache_insert(&mut cache, 5, "q", 10, number.clone()).is_empty());
12323 assert!(statement_cache_insert(&mut cache, 5, "q", 10, null).is_empty());
12324 assert_eq!(cache[0].bind_shape, number, "concrete slot inherited");
12325 let text = shape_of(&[BindValue::Text("x".into())]);
12327 assert_eq!(
12328 statement_cache_insert(&mut cache, 5, "q", 11, text.clone()),
12329 vec![10]
12330 );
12331 assert_eq!(cache[0].bind_shape, text);
12332 }
12333
12334 #[test]
12339 fn auth_serial_num_out_of_u16_range_is_rejected_not_wrapped() {
12340 for out_of_range in ["65536", "70000", "4294967296", "-1"] {
12341 let mut data = BTreeMap::new();
12342 data.insert("AUTH_SERIAL_NUM".to_string(), out_of_range.to_string());
12343 let error = parse_session_u16(&data, "AUTH_SERIAL_NUM")
12344 .expect_err("a serial outside u16 must not be silently wrapped");
12345 assert!(
12346 matches!(error, Error::MissingSessionField("AUTH_SERIAL_NUM")),
12347 "out-of-range {out_of_range} must use the existing typed session-field error, got {error:?}"
12348 );
12349 }
12350 }
12351
12352 #[test]
12355 fn auth_serial_num_parses_the_full_u16_domain_exactly() {
12356 for (text, expected) in [("0", 0_u16), ("1", 1), ("4464", 4464), ("65535", 65535)] {
12357 let mut data = BTreeMap::new();
12358 data.insert("AUTH_SERIAL_NUM".to_string(), text.to_string());
12359 assert_eq!(
12360 parse_session_u16(&data, "AUTH_SERIAL_NUM").expect("in-range serial parses"),
12361 expected
12362 );
12363 }
12364 }
12365
12366 #[test]
12367 fn db_unique_name_reads_real_key_not_confused_with_dbunique_name() {
12368 let mut data = BTreeMap::new();
12372 data.insert(
12373 "AUTH_SC_REAL_DBUNIQUE_NAME".to_string(),
12374 "MYCDB_UNIQUE".to_string(),
12375 );
12376 data.insert(
12377 "AUTH_SC_DBUNIQUE_NAME".to_string(),
12378 "MYCDB_SHOULD_NOT_WIN".to_string(),
12379 );
12380 assert_eq!(parse_db_unique_name(&data).as_deref(), Some("MYCDB_UNIQUE"));
12381
12382 let mut only_non_real = BTreeMap::new();
12384 only_non_real.insert("AUTH_SC_DBUNIQUE_NAME".to_string(), "MYCDB".to_string());
12385 assert_eq!(parse_db_unique_name(&only_non_real), None);
12386
12387 assert_eq!(parse_db_unique_name(&BTreeMap::new()), None);
12389 }
12390
12391 fn char_col(line: &str, needle: char) -> usize {
12394 line.chars()
12395 .position(|c| c == needle)
12396 .expect("char present")
12397 }
12398
12399 #[test]
12400 fn caret_points_at_the_flagged_char() {
12401 let out = render_caret("select x from t", 8, "ORA-00904: invalid identifier");
12403 let lines: Vec<&str> = out.lines().collect();
12404 assert_eq!(lines[0], "ORA-00904: invalid identifier");
12405 assert!(lines[2].ends_with("select x from t"), "{:?}", lines[2]);
12406 assert_eq!(
12407 char_col(lines[3], '^'),
12408 char_col(lines[2], 'x'),
12409 "caret column must align under the flagged char"
12410 );
12411 }
12412
12413 #[test]
12414 fn caret_handles_multiline_sql() {
12415 let out = render_caret("select *\nfrom no_such", 15, "ORA-00942");
12418 let lines: Vec<&str> = out.lines().collect();
12419 assert!(lines[2].starts_with("2 | from no_such"), "{:?}", lines[2]);
12420 assert_eq!(char_col(lines[3], '^'), char_col(lines[2], 'n'));
12421 }
12422
12423 #[test]
12424 fn caret_counts_unicode_scalar_values() {
12425 let sql = "select 'café' x from t";
12428 let x_idx = sql.chars().position(|c| c == 'x').unwrap();
12429 let out = render_caret(sql, x_idx + 1, "h");
12430 let lines: Vec<&str> = out.lines().collect();
12431 assert_eq!(char_col(lines[3], '^'), char_col(lines[2], 'x'));
12432 }
12433
12434 #[test]
12435 fn caret_clamps_and_never_panics() {
12436 let _ = render_caret("select 1", 999, "h");
12438 let _ = render_caret("select 1", 0, "h");
12439 let _ = render_caret("", 5, "h");
12440 assert!(render_caret("abc", 4, "h").ends_with('^'));
12441 }
12442
12443 fn identity() -> ClientIdentity {
12444 ClientIdentity::new("program", "machine", "osuser", "terminal", "driver")
12445 .expect("test identity should be valid")
12446 }
12447
12448 pub(crate) fn loopback_connection(
12449 read: transport::OracleReadHalf,
12450 write: transport::OracleWriteHalf,
12451 ) -> Connection {
12452 loopback_connection_from_core(ConnectionCore::<DriverTransport>::from_halves(
12453 read,
12454 write,
12455 "loopback_test_write",
12456 ))
12457 }
12458
12459 fn loopback_connection_from_core(core: DriverCore) -> Connection {
12460 Connection {
12461 descriptor: EasyConnect::parse("127.0.0.1:1521/FREEPDB1")
12462 .expect("test connect string should parse"),
12463 identity: identity(),
12464 core,
12465 session_id: 0,
12466 serial_num: 0,
12467 server_version: None,
12468 server_version_tuple: None,
12469 db_unique_name: None,
12470 capabilities: ClientCapabilities::default(),
12471 protocol_limits: ProtocolLimits::DEFAULT,
12472 ttc_seq_num: 0,
12473 sdu: 8192,
12474 protocol_version: 0,
12475 supports_fast_auth: false,
12476 supports_end_of_response: true,
12477 supports_oob: false,
12478 cursor_columns: BTreeMap::new(),
12479 fetch_metadata_by_sql: HashMap::new(),
12480 fetch_metadata_order: VecDeque::new(),
12481 shape_cache: Arc::new(StatementShapeCache::new()),
12482 dead: false,
12483 user: "test_user".into(),
12484 combo_key: Vec::new(),
12485 statement_cache: Vec::new(),
12486 statement_cache_size: STATEMENT_CACHE_SIZE,
12487 in_use_cursors: HashSet::new(),
12488 lob_prefetch_cursors: BTreeSet::new(),
12489 copied_cursors: HashSet::new(),
12490 cursors_to_close: Vec::new(),
12491 sessionless_data: None,
12492 notification_buffer: Vec::new(),
12493 notification_header_consumed: false,
12494 transaction_context: None,
12495 txn_in_progress: false,
12496 #[cfg(feature = "cassette")]
12497 capture_guard: None,
12498 }
12499 }
12500
12501 #[cfg(feature = "cassette")]
12502 fn synthetic_number_columns() -> Vec<ColumnMetadata> {
12503 vec![
12504 ColumnMetadata::new("INTCOL", oracledb_protocol::thin::ORA_TYPE_NUM_NUMBER)
12505 .with_csfrm(oracledb_protocol::thin::CS_FORM_IMPLICIT)
12506 .with_buffer_size(22)
12507 .with_max_size(22)
12508 .with_nulls_allowed(true),
12509 ColumnMetadata::new("NUMBERCOL", oracledb_protocol::thin::ORA_TYPE_NUM_NUMBER)
12510 .with_csfrm(oracledb_protocol::thin::CS_FORM_IMPLICIT)
12511 .with_buffer_size(22)
12512 .with_max_size(22)
12513 .with_nulls_allowed(true),
12514 ]
12515 }
12516
12517 #[cfg(feature = "cassette")]
12518 fn synthetic_connect_packet() -> Result<Vec<u8>> {
12519 let payload = build_connect_packet_payload(
12520 "(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=fixture-host)(PORT=0))\
12521 (CONNECT_DATA=(SERVICE_NAME=SYNTHETIC)(CID=(PROGRAM=rust-oracledb)\
12522 (HOST=fixture-host)(USER=fixture-user))))",
12523 8192,
12524 )?;
12525 Ok(encode_packet(
12526 TNS_PACKET_TYPE_CONNECT,
12527 0,
12528 None,
12529 &payload,
12530 PacketLengthWidth::Legacy16,
12531 )?)
12532 }
12533
12534 #[cfg(feature = "cassette")]
12535 fn synthetic_accept_packet() -> Result<Vec<u8>> {
12536 Ok(encode_packet(
12537 TNS_PACKET_TYPE_ACCEPT,
12538 0,
12539 None,
12540 b"SYNTHETIC-ACCEPT",
12541 PacketLengthWidth::Legacy16,
12542 )?)
12543 }
12544
12545 #[cfg(feature = "cassette")]
12546 fn synthetic_execute_packet() -> Result<Vec<u8>> {
12547 let payload = build_execute_payload_with_bind_rows_and_options_with_seq(
12548 "select value from synthetic_fixture",
12549 2,
12550 1,
12551 true,
12552 &[],
12553 ExecuteOptions::default(),
12554 ClientCapabilities::default().ttc_field_version,
12555 )?;
12556 Ok(encode_packet(
12557 TNS_PACKET_TYPE_DATA,
12558 0,
12559 Some(0),
12560 &payload,
12561 PacketLengthWidth::Large32,
12562 )?)
12563 }
12564
12565 #[cfg(feature = "cassette")]
12566 fn synthetic_fetch_packet() -> Result<Vec<u8>> {
12567 let payload =
12568 build_fetch_payload_with_seq(42, 2, 2, ClientCapabilities::default().ttc_field_version);
12569 Ok(encode_packet(
12570 TNS_PACKET_TYPE_DATA,
12571 0,
12572 Some(0),
12573 &payload,
12574 PacketLengthWidth::Large32,
12575 )?)
12576 }
12577
12578 #[cfg(feature = "cassette")]
12579 fn synthetic_function_packet(function_code: u8, seq_num: u8) -> Result<Vec<u8>> {
12580 let payload = build_function_payload_with_seq(
12581 function_code,
12582 seq_num,
12583 ClientCapabilities::default().ttc_field_version,
12584 );
12585 Ok(encode_packet(
12586 TNS_PACKET_TYPE_DATA,
12587 0,
12588 Some(0),
12589 &payload,
12590 PacketLengthWidth::Large32,
12591 )?)
12592 }
12593
12594 #[cfg(feature = "cassette")]
12595 fn hex_value(byte: u8) -> Result<u8> {
12596 match byte {
12597 b'0'..=b'9' => Ok(byte - b'0'),
12598 b'a'..=b'f' => Ok(byte - b'a' + 10),
12599 b'A'..=b'F' => Ok(byte - b'A' + 10),
12600 _ => Err(Error::Runtime(format!(
12601 "invalid synthetic fixture hex byte {byte:#04x}"
12602 ))),
12603 }
12604 }
12605
12606 #[cfg(feature = "cassette")]
12607 fn decode_hex_fixture(hex: &str) -> Result<Vec<u8>> {
12608 let clean = hex
12609 .bytes()
12610 .filter(|byte| !byte.is_ascii_whitespace())
12611 .collect::<Vec<_>>();
12612 if clean.len() % 2 != 0 {
12613 return Err(Error::Runtime(
12614 "synthetic fixture hex must contain an even number of digits".into(),
12615 ));
12616 }
12617 let mut out = Vec::with_capacity(clean.len() / 2);
12618 for pair in clean.chunks_exact(2) {
12619 out.push((hex_value(pair[0])? << 4) | hex_value(pair[1])?);
12620 }
12621 Ok(out)
12622 }
12623
12624 #[cfg(feature = "cassette")]
12625 fn synthetic_execute_response_payload() -> Result<Vec<u8>> {
12626 decode_hex_fixture(concat!(
12627 "101710740fb986350b6010fbcb6e06a74ed0787e060a110328014001018201800000",
12628 "014000000000020369010140023ffe010501050556414c554500000000000000000000",
12629 "010707787e060a110b1000021fe8010a010a00062201010001020000000708414c33",
12630 "32555446380801060323a4d500010100000000000004010102013b010102057b0000",
12631 "01010003000000000000000000000000030001010000000002057b0101010300194f",
12632 "52412d30313430333a206e6f206461746120666f756e640a1d",
12633 ))
12634 }
12635
12636 #[cfg(feature = "cassette")]
12637 fn synthetic_fetch_response_payload() -> Result<Vec<u8>> {
12638 decode_hex_fixture("06020101000205dc0001010101000702c1041d")
12639 }
12640
12641 #[cfg(feature = "cassette")]
12642 fn synthetic_plain_function_response_payload() -> [u8; 1] {
12643 [TNS_MSG_TYPE_END_OF_RESPONSE]
12644 }
12645
12646 #[cfg(feature = "cassette")]
12647 #[test]
12648 fn synthetic_cassette_replays_connect_execute_fetch_close_offline() -> Result<()> {
12649 let cassette = include_bytes!("../tests/fixtures/cassettes/select_7_plus_5.tns-cassette");
12650 let (read, write, audit) =
12651 transport::replay_split_with_audit(cassette, transport::ReplayWriteMode::Check)
12652 .map_err(|err| Error::Runtime(format!("invalid replay cassette: {err}")))?;
12653 let mut core = ConnectionCore::<DriverTransport>::from_halves(
12654 read,
12655 write,
12656 "synthetic_fixture_replay_write",
12657 );
12658 let runtime = build_io_runtime()?;
12659
12660 runtime.block_on(async {
12661 let cx = test_cx()?;
12662 let connect_packet = synthetic_connect_packet()?;
12663 core.write_all(&cx, &connect_packet).await?;
12664 let accept = core.read_packet(PacketLengthWidth::Legacy16).await?;
12665 assert_eq!(accept.packet_type, TNS_PACKET_TYPE_ACCEPT);
12666 assert_eq!(accept.payload, b"SYNTHETIC-ACCEPT");
12667
12668 let mut conn = loopback_connection_from_core(core);
12669 let execute = conn
12670 .execute_raw(
12671 &cx,
12672 "select value from synthetic_fixture",
12673 2,
12674 &[],
12675 ExecuteOptions::default(),
12676 None,
12677 )
12678 .await?;
12679 assert_eq!(execute.columns.len(), 1);
12680 assert_eq!(execute.rows.len(), 1);
12681 assert_eq!(
12682 execute.cell(0, 0).and_then(QueryValue::as_text),
12683 Some("AL32UTF8")
12684 );
12685
12686 let previous_row = vec![
12687 Some(QueryValue::number_from_text("2", true)),
12688 Some(QueryValue::number_from_text("0.5", false)),
12689 ];
12690 let columns = synthetic_number_columns();
12691 let fetched = conn
12692 .fetch_rows_with_columns(&cx, 42, 2, &columns, Some(&previous_row))
12693 .await?;
12694 assert_eq!(fetched.rows.len(), 1);
12695 assert_eq!(
12696 fetched
12697 .cell(0, 0)
12698 .and_then(QueryValue::as_number_text)
12699 .as_deref(),
12700 Some("3")
12701 );
12702 assert_eq!(
12703 fetched
12704 .cell(0, 1)
12705 .and_then(QueryValue::as_number_text)
12706 .as_deref(),
12707 Some("0.5")
12708 );
12709
12710 conn.close(&cx).await?;
12711 Ok::<_, Error>(())
12712 })?;
12713
12714 audit
12715 .assert_finished()
12716 .map_err(|err| Error::Runtime(err.to_string()))?;
12717 let _ = (
12718 synthetic_accept_packet()?,
12719 synthetic_execute_packet()?,
12720 synthetic_fetch_packet()?,
12721 synthetic_function_packet(TNS_FUNC_ROLLBACK, 3)?,
12722 synthetic_function_packet(TNS_FUNC_LOGOFF, 4)?,
12723 synthetic_execute_response_payload()?,
12724 synthetic_fetch_response_payload()?,
12725 synthetic_plain_function_response_payload(),
12726 );
12727 Ok(())
12728 }
12729
12730 fn server_error(message: &str) -> Error {
12731 Error::Protocol(oracledb_protocol::ProtocolError::ServerError(
12732 message.to_string(),
12733 ))
12734 }
12735
12736 fn structured_error(code: u32, pos: i32) -> Error {
12737 Error::Protocol(oracledb_protocol::ProtocolError::ServerErrorInfo(Box::new(
12738 oracledb_protocol::ServerErrorDetails {
12739 message: format!("ORA-{code:05}: synthetic"),
12740 code,
12741 pos,
12742 ..Default::default()
12743 },
12744 )))
12745 }
12746
12747 fn column(name: &str, ora_type_num: u8, is_json: bool) -> ColumnMetadata {
12748 ColumnMetadata::new(name, ora_type_num).with_is_json(is_json)
12749 }
12750
12751 #[test]
12752 fn json_lob_probe_candidates_selects_named_non_json_lobs() {
12753 use oracledb_protocol::thin::{ORA_TYPE_NUM_BLOB, ORA_TYPE_NUM_CLOB, ORA_TYPE_NUM_VARCHAR};
12754 let columns = vec![
12755 column("doc", ORA_TYPE_NUM_CLOB, false), column("blob_doc", ORA_TYPE_NUM_BLOB, false), column("already_json", ORA_TYPE_NUM_CLOB, true), column("name", ORA_TYPE_NUM_VARCHAR, false), column("", ORA_TYPE_NUM_CLOB, false), ];
12761 assert_eq!(
12764 json_lob_probe_candidates(&columns),
12765 vec![(0, "DOC".to_string()), (1, "BLOB_DOC".to_string())]
12766 );
12767 }
12768
12769 #[test]
12770 fn json_lob_probe_candidates_empty_when_no_lobs() {
12771 use oracledb_protocol::thin::ORA_TYPE_NUM_VARCHAR;
12772 let columns = vec![column("name", ORA_TYPE_NUM_VARCHAR, false)];
12773 assert!(json_lob_probe_candidates(&columns).is_empty());
12774 }
12775
12776 #[test]
12777 fn ora_code_parses_from_message_and_struct() {
12778 assert_eq!(
12780 server_error("ORA-00060: deadlock detected").ora_code(),
12781 Some(60)
12782 );
12783 assert_eq!(structured_error(942, 0).ora_code(), Some(942));
12785 assert_eq!(server_error("listener problem").ora_code(), None);
12787 assert_eq!(Error::CallTimeout(500).ora_code(), None);
12789 }
12790
12791 #[test]
12792 fn stable_error_methods_classify_without_display_parsing() {
12793 let transient = server_error("ORA-00060: deadlock detected");
12794 assert_eq!(transient.kind(), ErrorKind::Database);
12795 assert_eq!(transient.oracle_code(), Some(60));
12796 assert_eq!(
12797 transient.connection_disposition(),
12798 ConnectionDisposition::Reusable
12799 );
12800 assert_eq!(
12801 transient.retry_hint(),
12802 RetryHint::RetrySameConnectionIfIdempotent
12803 );
12804
12805 let lost = server_error("ORA-03113: end-of-file on communication channel");
12806 assert_eq!(lost.kind(), ErrorKind::Database);
12807 assert_eq!(lost.connection_disposition(), ConnectionDisposition::Dead);
12808 assert_eq!(lost.retry_hint(), RetryHint::ReconnectThenRetryIfIdempotent);
12809
12810 let timeout = Error::CallTimeout(500);
12811 assert_eq!(timeout.kind(), ErrorKind::Timeout);
12812 assert_eq!(
12813 timeout.connection_disposition(),
12814 ConnectionDisposition::Reusable
12815 );
12816 assert_eq!(
12817 timeout.retry_hint(),
12818 RetryHint::RetrySameConnectionIfIdempotent
12819 );
12820
12821 let cancelled = Error::Cancelled;
12822 assert_eq!(cancelled.kind(), ErrorKind::Cancel);
12823 assert_eq!(cancelled.oracle_code(), Some(1013));
12824 assert_eq!(
12825 cancelled.retry_hint(),
12826 RetryHint::RetrySameConnectionIfIdempotent
12827 );
12828
12829 let bind = Error::Bind(BindError::PositionalCountMismatch {
12830 expected: 2,
12831 actual: 1,
12832 });
12833 assert_eq!(bind.kind(), ErrorKind::Conversion);
12834 assert_eq!(bind.retry_hint(), RetryHint::Never);
12835
12836 let resource = Error::Protocol(oracledb_protocol::ProtocolError::ResourceLimit {
12837 limit: "binds",
12838 observed: 4,
12839 maximum: 3,
12840 });
12841 assert_eq!(resource.kind(), ErrorKind::ResourceLimit);
12842 assert_eq!(
12843 resource.connection_disposition(),
12844 ConnectionDisposition::Reusable
12845 );
12846 assert_eq!(resource.retry_hint(), RetryHint::Never);
12847
12848 let session_dead = server_error("ORA-00600: internal error code, arguments: []");
12849 assert_eq!(
12850 session_dead.connection_disposition(),
12851 ConnectionDisposition::Dead
12852 );
12853 assert!(
12854 !session_dead.is_connection_lost(),
12855 "ORA-00600 kills the session but is not a connection-lost retry code"
12856 );
12857 assert_eq!(session_dead.retry_hint(), RetryHint::Never);
12858 }
12859
12860 #[test]
12868 fn cancel_kind_maps_to_disposition() {
12869 for kind in [
12872 CancelKind::Timeout,
12873 CancelKind::Deadline,
12874 CancelKind::PollQuota,
12875 CancelKind::CostBudget,
12876 ] {
12877 assert_eq!(
12878 CancelDisposition::from_kind(kind),
12879 CancelDisposition::Timeout,
12880 "{kind:?} must drain + stay reusable (timeout disposition)"
12881 );
12882 }
12883
12884 for kind in [
12886 CancelKind::Shutdown,
12887 CancelKind::ResourceUnavailable,
12888 CancelKind::LinkedExit,
12889 ] {
12890 assert_eq!(
12891 CancelDisposition::from_kind(kind),
12892 CancelDisposition::Close,
12893 "{kind:?} must close the connection"
12894 );
12895 }
12896
12897 for kind in [
12899 CancelKind::User,
12900 CancelKind::RaceLost,
12901 CancelKind::FailFast,
12902 CancelKind::ParentCancelled,
12903 ] {
12904 assert_eq!(
12905 CancelDisposition::from_kind(kind),
12906 CancelDisposition::Cancel,
12907 "{kind:?} must drain quietly + stay reusable (cancel disposition)"
12908 );
12909 }
12910 }
12911
12912 #[test]
12913 fn cancel_disposition_flattens_to_distinct_error_variants() {
12914 let timeout = CancelDisposition::Timeout.into_error(750);
12920 assert!(matches!(timeout, Error::CallTimeout(750)));
12921 assert_eq!(timeout.kind(), ErrorKind::Timeout);
12922 assert_eq!(
12923 timeout.connection_disposition(),
12924 ConnectionDisposition::Reusable
12925 );
12926 assert_eq!(
12927 timeout.retry_hint(),
12928 RetryHint::RetrySameConnectionIfIdempotent,
12929 "a drained timeout may be retried on the same connection"
12930 );
12931
12932 let cancelled = CancelDisposition::Cancel.into_error(750);
12935 assert!(matches!(cancelled, Error::Cancelled));
12936 assert!(
12937 !matches!(cancelled, Error::Runtime(_) | Error::Io(_)),
12938 "a cancel must be a distinct variant, never a generic runtime/io error"
12939 );
12940 assert_eq!(cancelled.kind(), ErrorKind::Cancel);
12941 assert_eq!(cancelled.oracle_code(), Some(1013));
12942 assert_eq!(
12943 cancelled.connection_disposition(),
12944 ConnectionDisposition::Reusable
12945 );
12946 assert_eq!(
12947 cancelled.retry_hint(),
12948 RetryHint::RetrySameConnectionIfIdempotent
12949 );
12950
12951 let closed = CancelDisposition::Close.into_error(750);
12953 assert!(matches!(closed, Error::ConnectionClosed(_)));
12954 assert_eq!(closed.kind(), ErrorKind::Network);
12955 assert_eq!(closed.connection_disposition(), ConnectionDisposition::Dead);
12956 assert_eq!(
12957 closed.retry_hint(),
12958 RetryHint::ReconnectThenRetryIfIdempotent
12959 );
12960 }
12961
12962 #[test]
12969 fn asupersync_cancellation_marker_is_detected_deterministically() {
12970 use std::io::{Error as IoError, ErrorKind};
12971 assert!(
12972 is_asupersync_cancellation(&IoError::new(ErrorKind::Interrupted, "cancelled")),
12973 "the asupersync cancellation marker must be recognized regardless of checkpoint timing"
12974 );
12975 assert!(
12976 !is_asupersync_cancellation(&IoError::new(
12977 ErrorKind::Interrupted,
12978 "Interrupted system call"
12979 )),
12980 "a real OS EINTR (different message) must NOT be treated as a cancellation"
12981 );
12982 assert!(!is_asupersync_cancellation(&IoError::from_raw_os_error(4)));
12984 assert!(
12985 !is_asupersync_cancellation(&IoError::new(ErrorKind::WouldBlock, "cancelled")),
12986 "only the Interrupted kind is the cancellation signal"
12987 );
12988 }
12989
12990 #[test]
12991 fn missing_cancel_reason_is_a_plain_cancel_at_checkpoint() {
12992 assert_eq!(cancel_disposition(None), CancelDisposition::Cancel);
12997 }
12998
12999 #[test]
13000 fn cancelled_cx_resolves_to_the_kind_mapped_distinct_error() {
13001 fn err_for(kind: CancelKind) -> Error {
13009 let cx = Cx::detached_cancel_context();
13010 cx.cancel_with(kind, Some("test cancel"));
13011 assert!(
13014 cx.checkpoint().is_err(),
13015 "{kind:?}: a cancelled context must fail its checkpoint"
13016 );
13017 let reason = cx.cancel_reason().unwrap_or_else(|| {
13018 panic!("{kind:?}: cancel_reason must carry the structured kind")
13019 });
13020 assert_eq!(reason.kind, kind, "cancel_reason must round-trip the kind");
13021 cancel_disposition(Some(reason)).into_error(750)
13022 }
13023
13024 for kind in [CancelKind::Timeout, CancelKind::Deadline] {
13026 let err = err_for(kind);
13027 assert!(
13028 matches!(err, Error::CallTimeout(_)),
13029 "{kind:?} should surface CallTimeout, got {err:?}"
13030 );
13031 assert_eq!(err.kind(), ErrorKind::Timeout);
13032 }
13033
13034 let shutdown = err_for(CancelKind::Shutdown);
13036 assert!(
13037 matches!(shutdown, Error::ConnectionClosed(_)),
13038 "Shutdown should surface ConnectionClosed, got {shutdown:?}"
13039 );
13040 assert_eq!(
13041 shutdown.connection_disposition(),
13042 ConnectionDisposition::Dead
13043 );
13044
13045 for kind in [CancelKind::User, CancelKind::RaceLost] {
13048 let err = err_for(kind);
13049 assert!(
13050 matches!(err, Error::Cancelled),
13051 "{kind:?} should surface Cancelled, got {err:?}"
13052 );
13053 assert!(
13054 !matches!(err, Error::Runtime(_)),
13055 "{kind:?} must NOT be flattened to a generic Error::Runtime"
13056 );
13057 assert_eq!(err.kind(), ErrorKind::Cancel);
13058 }
13059 }
13060
13061 #[test]
13062 fn uncancelled_checkpoint_is_ok() {
13063 let cx = Cx::detached_cancel_context();
13066 assert!(cx.checkpoint().is_ok());
13067 assert!(cx.cancel_reason().is_none());
13068 }
13069
13070 #[test]
13071 fn offset_only_from_structured_nonzero() {
13072 assert_eq!(structured_error(942, 14).offset(), Some(14));
13073 assert_eq!(structured_error(942, 0).offset(), None);
13074 assert_eq!(
13075 server_error("ORA-00942: table or view does not exist").offset(),
13076 None
13077 );
13078 }
13079
13080 #[test]
13081 fn transient_classification() {
13082 for &code in TRANSIENT_ORA_CODES {
13083 let err = server_error(&format!("ORA-{code:05}: transient"));
13084 assert!(err.is_transient(), "ORA-{code:05} should be transient");
13085 assert!(err.is_retryable(), "transient implies retryable");
13086 assert!(
13087 !err.is_connection_lost(),
13088 "ORA-{code:05} is not connection-lost"
13089 );
13090 }
13091 let perm = server_error("ORA-00942: table or view does not exist");
13093 assert!(!perm.is_transient());
13094 assert!(!perm.is_connection_lost());
13095 assert!(!perm.is_retryable());
13096 }
13097
13098 #[test]
13099 fn connection_lost_classification() {
13100 for &code in CONNECTION_LOST_ORA_CODES {
13101 let err = server_error(&format!("ORA-{code:05}: lost"));
13102 assert!(
13103 err.is_connection_lost(),
13104 "ORA-{code:05} should be connection-lost"
13105 );
13106 assert!(err.is_retryable(), "connection-lost implies retryable");
13107 assert!(
13108 !err.is_transient(),
13109 "ORA-{code:05} is not a transient (retry-in-place) code"
13110 );
13111 }
13112 let io = Error::Io(std::io::Error::new(
13114 std::io::ErrorKind::ConnectionReset,
13115 "reset",
13116 ));
13117 assert!(io.is_connection_lost());
13118 assert!(io.is_retryable());
13119
13120 let timeout = Error::CallTimeout(1000);
13126 assert!(
13127 !timeout.is_connection_lost(),
13128 "a call timeout leaves the connection usable after the drain"
13129 );
13130 assert!(
13131 timeout.is_transient(),
13132 "a call timeout is a retry-in-place (transient) condition"
13133 );
13134 assert!(
13135 timeout.is_retryable(),
13136 "transient implies retryable on the same connection"
13137 );
13138
13139 let recovery_failed =
13143 Error::ConnectionClosed("socket timed out while recovering".to_string());
13144 assert!(
13145 recovery_failed.is_connection_lost(),
13146 "a failed timeout-recovery drain marks the connection lost"
13147 );
13148 assert!(recovery_failed.is_retryable(), "reconnect, then retry");
13149 assert!(
13150 !recovery_failed.is_transient(),
13151 "ConnectionClosed needs a reconnect first, so it is not retry-in-place"
13152 );
13153 }
13154
13155 #[test]
13156 fn resource_limit_error_defines_pre_sync_and_post_sync_disposition() {
13157 let err = oracledb_protocol::ProtocolError::ResourceLimit {
13158 limit: "columns",
13159 observed: 3,
13160 maximum: 2,
13161 };
13162 assert_eq!(
13163 post_sync_protocol_error_disposition(&err),
13164 PostSyncProtocolDisposition::Dead,
13165 "a post-sync resource-limit decode failure leaves unread response bytes"
13166 );
13167
13168 let pre_sync = Error::Protocol(oracledb_protocol::ProtocolError::ResourceLimit {
13169 limit: "columns",
13170 observed: 3,
13171 maximum: 2,
13172 });
13173 assert_eq!(
13174 pre_sync.resource_limit(),
13175 Some(oracledb_protocol::ResourceLimit {
13176 limit: "columns",
13177 observed: 3,
13178 maximum: 2,
13179 })
13180 );
13181 assert!(
13182 !pre_sync.is_connection_lost(),
13183 "client-side/pre-sync resource-limit validation does not imply a lost connection"
13184 );
13185 assert!(
13186 !pre_sync.is_transient(),
13187 "raising the configured limit, not retrying in-place, is the remedy"
13188 );
13189 assert!(
13190 !pre_sync.is_retryable(),
13191 "resource limits are deterministic for the same input and policy"
13192 );
13193 assert!(matches!(
13194 pre_sync,
13195 Error::Protocol(oracledb_protocol::ProtocolError::ResourceLimit {
13196 limit: "columns",
13197 observed: 3,
13198 maximum: 2,
13199 })
13200 ));
13201 }
13202
13203 #[test]
13204 fn post_sync_resource_limit_marks_live_connection_dead() -> Result<()> {
13205 let listener = TcpListener::bind("127.0.0.1:0").expect("bind local listener");
13206 let addr = listener.local_addr().expect("listener address");
13207 let server = thread::spawn(move || {
13208 let (_socket, _) = listener.accept().expect("accept test client");
13209 });
13210
13211 let runtime = build_io_runtime().expect("asupersync runtime");
13212 let mut conn = runtime.block_on(async {
13213 let stream = TcpStream::connect(addr).await.expect("connect to listener");
13214 let (read, write) = transport::plain_split(stream);
13215 loopback_connection(read, write)
13216 });
13217
13218 let err = conn
13219 .note_parse::<()>(Err(oracledb_protocol::ProtocolError::ResourceLimit {
13220 limit: "response_bytes",
13221 observed: 33,
13222 maximum: 32,
13223 }))
13224 .expect_err("post-sync resource-limit violation must be surfaced");
13225
13226 assert!(matches!(
13227 err,
13228 Error::Protocol(oracledb_protocol::ProtocolError::ResourceLimit {
13229 limit: "response_bytes",
13230 observed: 33,
13231 maximum: 32,
13232 })
13233 ));
13234 assert!(
13235 conn.is_dead(),
13236 "post-sync resource-limit violation stops consuming a response and kills the session"
13237 );
13238
13239 drop(conn);
13240 server.join().expect("server thread joins");
13241 Ok(())
13242 }
13243
13244 #[test]
13245 fn protocol_version_and_fast_auth_accessors_report_negotiated_values() {
13246 let listener = TcpListener::bind("127.0.0.1:0").expect("bind local listener");
13252 let addr = listener.local_addr().expect("listener address");
13253 let server = thread::spawn(move || {
13254 let (_socket, _) = listener.accept().expect("accept test client");
13255 });
13256
13257 let runtime = build_io_runtime().expect("asupersync runtime");
13258 let mut conn = runtime.block_on(async {
13259 let stream = TcpStream::connect(addr).await.expect("connect to listener");
13260 let (read, write) = transport::plain_split(stream);
13261 loopback_connection(read, write)
13262 });
13263
13264 assert_eq!(conn.protocol_version(), 0);
13266 assert!(!conn.supports_fast_auth());
13267
13268 conn.protocol_version = 319;
13270 conn.supports_fast_auth = true;
13271 assert_eq!(conn.protocol_version(), 319);
13272 assert!(conn.supports_fast_auth());
13273
13274 drop(conn);
13275 server.join().expect("server thread joins");
13276 }
13277
13278 #[test]
13279 fn host_port_protocol_accessors_report_connected_descriptor() {
13280 let listener = TcpListener::bind("127.0.0.1:0").expect("bind local listener");
13285 let addr = listener.local_addr().expect("listener address");
13286 let server = thread::spawn(move || {
13287 let (_socket, _) = listener.accept().expect("accept test client");
13288 });
13289
13290 let runtime = build_io_runtime().expect("asupersync runtime");
13291 let mut conn = runtime.block_on(async {
13292 let stream = TcpStream::connect(addr).await.expect("connect to listener");
13293 let (read, write) = transport::plain_split(stream);
13294 loopback_connection(read, write)
13295 });
13296
13297 conn.descriptor = EasyConnect {
13298 host: "db.example.com".to_string(),
13299 port: 2484,
13300 service_name: "FREEPDB1".to_string(),
13301 protocol: NetProtocol::Tcps,
13302 };
13303
13304 assert_eq!(conn.host(), "db.example.com");
13305 assert_eq!(conn.port(), 2484);
13306 assert_eq!(conn.protocol(), NetProtocol::Tcps);
13307 assert_eq!(conn.host(), conn.descriptor().host);
13309 assert_eq!(conn.port(), conn.descriptor().port);
13310 assert_eq!(conn.protocol(), conn.descriptor().protocol);
13311
13312 drop(conn);
13313 server.join().expect("server thread joins");
13314 }
13315
13316 fn synthetic_pipeline_execute_response_payload() -> Vec<u8> {
13333 const HEX: &str = concat!(
13334 "101710740fb986350b6010fbcb6e06a74ed0787e060a110328014001018201800000",
13335 "014000000000020369010140023ffe010501050556414c554500000000000000000000",
13336 "010707787e060a110b1000021fe8010a010a00062201010001020000000708414c33",
13337 "32555446380801060323a4d500010100000000000004010102013b010102057b0000",
13338 "01010003000000000000000000000000030001010000000002057b0101010300194f",
13339 "52412d30313430333a206e6f206461746120666f756e640a1d",
13340 );
13341 let raw = HEX.as_bytes();
13342 let mut bytes = Vec::with_capacity(raw.len() / 2);
13343 let mut index = 0;
13344 while index < raw.len() {
13345 let hi = (raw[index] as char).to_digit(16).expect("hex digit");
13346 let lo = (raw[index + 1] as char).to_digit(16).expect("hex digit");
13347 bytes.push(((hi << 4) | lo) as u8);
13348 index += 2;
13349 }
13350 bytes
13351 }
13352
13353 fn synthetic_lob_read_response_payload(locator: &[u8], data: &[u8], amount: u64) -> Vec<u8> {
13354 let mut payload = oracledb_protocol::wire::TtcWriter::new();
13355 payload.write_u8(oracledb_protocol::thin::TNS_MSG_TYPE_LOB_DATA);
13356 payload
13357 .write_bytes_with_length(data)
13358 .expect("synthetic LOB payload is encodable");
13359 payload.write_u8(oracledb_protocol::thin::TNS_MSG_TYPE_PARAMETER);
13360 payload.write_raw(locator);
13361 payload.write_ub8(amount);
13362 payload.write_u8(TNS_MSG_TYPE_END_OF_RESPONSE);
13363 payload.into_bytes()
13364 }
13365
13366 fn synthetic_aq_enqueue_response_payload(msgid: &[u8; 16]) -> Vec<u8> {
13367 let mut payload = oracledb_protocol::wire::TtcWriter::new();
13368 payload.write_u8(oracledb_protocol::thin::TNS_MSG_TYPE_PARAMETER);
13369 payload.write_raw(msgid);
13370 payload.write_ub2(0);
13371 payload.write_u8(TNS_MSG_TYPE_END_OF_RESPONSE);
13372 payload.into_bytes()
13373 }
13374
13375 fn synthetic_direct_path_prepare_response_payload(cursor_id: u16) -> Vec<u8> {
13376 let mut payload = oracledb_protocol::wire::TtcWriter::new();
13377 payload.write_u8(oracledb_protocol::thin::TNS_MSG_TYPE_PARAMETER);
13378 payload.write_ub4(0); payload.write_ub2(0); payload.write_ub2(4); payload.write_ub4(0);
13382 payload.write_ub4(0);
13383 payload.write_ub4(0);
13384 payload.write_ub4(u32::from(cursor_id));
13385 payload.write_u8(TNS_MSG_TYPE_END_OF_RESPONSE);
13386 let payload = payload.into_bytes();
13387
13388 let decoded = oracledb_protocol::dpl::parse_direct_path_prepare_response(
13389 &payload,
13390 ClientCapabilities::default(),
13391 )
13392 .expect("synthetic direct path prepare response decodes");
13393 assert_eq!(decoded.cursor_id, cursor_id);
13394 assert!(decoded.column_metadata.is_empty());
13395 payload
13396 }
13397
13398 fn synthetic_direct_path_simple_response_payload() -> Vec<u8> {
13399 let mut payload = oracledb_protocol::wire::TtcWriter::new();
13400 payload.write_u8(oracledb_protocol::thin::TNS_MSG_TYPE_PARAMETER);
13401 payload.write_ub2(0); payload.write_u8(TNS_MSG_TYPE_END_OF_RESPONSE);
13403 let payload = payload.into_bytes();
13404 oracledb_protocol::dpl::parse_direct_path_simple_response(
13405 &payload,
13406 ClientCapabilities::default(),
13407 )
13408 .expect("synthetic direct path simple response decodes");
13409 payload
13410 }
13411
13412 fn synthetic_subscribe_register_response_payload() -> Vec<u8> {
13413 let payload = vec![
13416 0x08, 0x01, 0x01, 0x00, 0x02, 0x01, 0x2E, 0x01, 0x01, 0x02, 0x01, 0x2E, 0x00, 0x00,
13417 0x01, 0x01, 0x01, 0x36, 0x36, 0x28, 0x41, 0x44, 0x44, 0x52, 0x45, 0x53, 0x53, 0x3D,
13418 0x28, 0x50, 0x52, 0x4F, 0x54, 0x4F, 0x43, 0x4F, 0x4C, 0x3D, 0x54, 0x43, 0x50, 0x29,
13419 0x28, 0x48, 0x4F, 0x53, 0x54, 0x3D, 0x32, 0x39, 0x30, 0x61, 0x63, 0x30, 0x33, 0x30,
13420 0x30, 0x33, 0x38, 0x37, 0x29, 0x28, 0x50, 0x4F, 0x52, 0x54, 0x3D, 0x31, 0x35, 0x32,
13421 0x31, 0x29, 0x29, 0x01, 0x0A, 0x0A, 0x4F, 0x43, 0x49, 0x3A, 0x45, 0x50, 0x3A, 0x33,
13422 0x30, 0x31, 0x09, 0x01, 0x01, 0x02, 0xDD, 0x48, 0x1D,
13423 ];
13424 let decoded = parse_subscribe_response_with_limits(
13425 &payload,
13426 ClientCapabilities::default(),
13427 ProtocolLimits::DEFAULT,
13428 )
13429 .expect("captured CQN register response decodes");
13430 assert_eq!(decoded.registration_id, 302);
13431 assert_eq!(decoded.client_id.as_deref(), Some(&b"OCI:EP:301"[..]));
13432 payload
13433 }
13434
13435 fn serve_dropped_response_recovery(
13436 listener: TcpListener,
13437 expected_request: Vec<u8>,
13438 stranded_response: Vec<u8>,
13439 request_seen: std::sync::mpsc::Sender<()>,
13440 ) -> std::io::Result<bool> {
13441 const TRAILING_CANCEL_ERROR: &[u8] = &[0x04, 0x01, 0x0d];
13442
13443 use std::io::Write as _;
13444 let (mut socket, _) = listener.accept()?;
13445 socket.set_read_timeout(Some(Duration::from_secs(5)))?;
13446
13447 assert_eq!(
13448 read_one_wire_data_payload(&mut socket),
13449 expected_request,
13450 "request must preserve its exact payload"
13451 );
13452 request_seen
13453 .send(())
13454 .expect("client waits for request proof");
13455
13456 let (next_packet_type, next_body) = read_one_wire_packet_bytes(&mut socket);
13457 if next_packet_type == TNS_PACKET_TYPE_MARKER {
13458 assert_eq!(
13459 next_body,
13460 vec![1, 0, TNS_MARKER_TYPE_BREAK],
13461 "reuse must BREAK the stranded response before its request"
13462 );
13463 socket.write_all(&data_packet(&stranded_response, true))?;
13464 socket.write_all(&marker_packet(TNS_MARKER_TYPE_BREAK))?;
13465 assert_eq!(
13466 read_marker_type(&mut socket),
13467 TNS_MARKER_TYPE_RESET,
13468 "response drain must complete the RESET handshake"
13469 );
13470 socket.write_all(&marker_packet(TNS_MARKER_TYPE_RESET))?;
13471 socket.write_all(&data_packet(TRAILING_CANCEL_ERROR, true))?;
13472
13473 assert_eq!(
13474 read_one_wire_packet(&mut socket),
13475 TNS_PACKET_TYPE_DATA,
13476 "fresh execute follows the completed response drain"
13477 );
13478 socket.write_all(&data_packet(
13479 &synthetic_pipeline_execute_response_payload(),
13480 true,
13481 ))?;
13482 socket.flush()?;
13483 Ok(true)
13484 } else {
13485 assert_eq!(
13486 next_packet_type, TNS_PACKET_TYPE_DATA,
13487 "without recovery the next operation is sent directly"
13488 );
13489 socket.write_all(&data_packet(&stranded_response, true))?;
13490 socket.write_all(&data_packet(
13491 &synthetic_pipeline_execute_response_payload(),
13492 true,
13493 ))?;
13494 socket.flush()?;
13495 Ok(false)
13496 }
13497 }
13498
13499 fn synthetic_aq_enqueue_request() -> (AqQueueDesc, AqMsgProps, AqEnqOptions) {
13500 let queue = AqQueueDesc::new(
13501 "AQ_QUEUE".to_owned(),
13502 oracledb_protocol::thin::aq::AqPayloadKind::Raw,
13503 None,
13504 );
13505 let props = AqMsgProps {
13506 payload: Some(oracledb_protocol::thin::aq::AqPayloadValue::Raw(
13507 b"payload".to_vec(),
13508 )),
13509 ..AqMsgProps::default()
13510 };
13511 (queue, props, AqEnqOptions::default())
13512 }
13513
13514 fn synthetic_open_cursor_execute_response_payload() -> Vec<u8> {
13519 const TERMINAL_NO_DATA_PREFIX: &[u8] = &[
13520 0x02, 0x05, 0x7b, 0x01, 0x01, 0x01, 0x03, 0x00, 0x19, ];
13526
13527 let mut response = synthetic_pipeline_execute_response_payload();
13528 let terminal = response
13529 .windows(TERMINAL_NO_DATA_PREFIX.len())
13530 .rposition(|window| window == TERMINAL_NO_DATA_PREFIX)
13531 .expect("synthetic response must contain its terminal ORA-01403");
13532 response[terminal + 1] = 0;
13533 response[terminal + 2] = 0;
13534 response.truncate(terminal + 8);
13535 response.push(TNS_MSG_TYPE_END_OF_RESPONSE);
13536
13537 let decoded = sequential_op_decode(&response);
13538 assert_ne!(decoded.cursor_id, 0, "fixture must retain its cursor id");
13539 assert!(decoded.more_rows, "fixture must leave the cursor open");
13540 assert_eq!(decoded.rows.len(), 1, "fixture must retain its first row");
13541 response
13542 }
13543
13544 fn synthetic_open_borrowed_fetch_response(value: &str) -> Vec<u8> {
13548 use oracledb_protocol::thin::{
13549 encode_number_text, TNS_MSG_TYPE_ROW_DATA, TNS_MSG_TYPE_ROW_HEADER,
13550 };
13551
13552 let number = encode_number_text(value).expect("synthetic NUMBER encodes");
13553 let number_len = u8::try_from(number.len()).expect("NUMBER uses short TTC bytes");
13554 let mut response = vec![
13555 TNS_MSG_TYPE_ROW_HEADER,
13556 0, 1,
13558 1, 1,
13560 1, 1,
13562 1, 0, 0, 0, TNS_MSG_TYPE_ROW_DATA,
13567 number_len,
13568 ];
13569 response.extend_from_slice(&number);
13570 response.push(TNS_MSG_TYPE_END_OF_RESPONSE);
13571 response
13572 }
13573
13574 fn sequential_op_decode(payload: &[u8]) -> QueryResult {
13578 parse_query_response_with_binds_options_columns_and_limits(
13579 payload,
13580 ClientCapabilities::default(),
13581 &[],
13582 ExecuteOptions::default(),
13583 &[],
13584 ProtocolLimits::DEFAULT,
13585 )
13586 .expect("synthetic execute response decodes")
13587 }
13588
13589 fn read_one_wire_packet_bytes(socket: &mut std::net::TcpStream) -> (u8, Vec<u8>) {
13593 let mut header = [0u8; 8];
13594 socket.read_exact(&mut header).expect("read packet header");
13595 let declared = u32::from_be_bytes([header[0], header[1], header[2], header[3]]) as usize;
13596 let mut body = vec![0u8; declared - header.len()];
13597 socket.read_exact(&mut body).expect("read packet body");
13598 (header[4], body)
13599 }
13600
13601 fn read_one_wire_packet(socket: &mut std::net::TcpStream) -> u8 {
13602 read_one_wire_packet_bytes(socket).0
13603 }
13604
13605 fn read_one_wire_data_payload(socket: &mut std::net::TcpStream) -> Vec<u8> {
13606 let (packet_type, body) = read_one_wire_packet_bytes(socket);
13607 assert_eq!(
13608 packet_type, TNS_PACKET_TYPE_DATA,
13609 "expected a DATA request after recovery"
13610 );
13611 assert!(body.len() >= 2, "DATA packet must contain its flags");
13612 body[2..].to_vec()
13613 }
13614
13615 fn pipeline_batch_over_loopback(n: usize) -> (Vec<QueryResult>, usize) {
13622 let listener = TcpListener::bind("127.0.0.1:0").expect("bind local listener");
13623 let addr = listener.local_addr().expect("listener address");
13624 let server = thread::spawn(move || {
13625 use std::io::Write as _;
13626 let (mut socket, _) = listener.accept().expect("accept test client");
13627 let mut requests_before_first_response = 0usize;
13628 for _ in 0..=n {
13629 let _packet_type = read_one_wire_packet(&mut socket);
13630 requests_before_first_response += 1;
13631 }
13632 let execute_response =
13633 data_packet(&synthetic_pipeline_execute_response_payload(), true);
13634 for _ in 0..n {
13635 socket
13636 .write_all(&execute_response)
13637 .expect("write op response");
13638 }
13639 socket
13642 .write_all(&data_packet(
13643 &[oracledb_protocol::thin::TNS_MSG_TYPE_END_OF_RESPONSE],
13644 true,
13645 ))
13646 .expect("write end-pipeline response");
13647 socket.flush().expect("flush responses");
13648 requests_before_first_response
13649 });
13650
13651 let runtime = build_io_runtime().expect("asupersync runtime");
13652 let results = runtime.block_on(async {
13653 let cx = Cx::current().expect("ambient Cx");
13654 let stream = TcpStream::connect(addr).await.expect("connect to listener");
13655 let (read, write) = transport::plain_split(stream);
13656 let mut conn = loopback_connection(read, write);
13657 let requests: Vec<PipelineRequest> = (0..n)
13658 .map(|_| {
13659 PipelineRequest::execute("select value from synthetic_fixture", Vec::new(), 2)
13660 })
13661 .collect();
13662 conn.run_pipeline_decoded(&cx, &requests, false)
13663 .await
13664 .expect("pipelined batch runs as one round trip")
13665 .into_iter()
13666 .enumerate()
13667 .map(|(index, op)| op.unwrap_or_else(|err| panic!("op {index} decoded: {err:?}")))
13668 .collect::<Vec<_>>()
13669 });
13670
13671 let requests_before_first_response = server.join().expect("server thread joins");
13672 (results, requests_before_first_response)
13673 }
13674
13675 #[test]
13676 fn pipeline_batch_offline_collapses_to_one_round_trip() {
13677 const N: usize = 10;
13678 let (results, requests_before_first_response) = pipeline_batch_over_loopback(N);
13679
13680 assert_eq!(
13681 requests_before_first_response,
13682 N + 1,
13683 "all {N} ops + end-pipeline are written before any response is read == 1 round trip"
13684 );
13685 assert_eq!(results.len(), N, "one decoded result per pipelined op");
13686
13687 let reference = sequential_op_decode(&synthetic_pipeline_execute_response_payload());
13691 for (index, decoded) in results.into_iter().enumerate() {
13692 assert_eq!(
13693 decoded, reference,
13694 "pipelined op {index} decodes byte-identically to the sequential path"
13695 );
13696 }
13697 }
13698
13699 #[test]
13700 fn concurrent_pipeline_batches_do_not_serialize() {
13701 const N: usize = 10;
13707 let reference = sequential_op_decode(&synthetic_pipeline_execute_response_payload());
13708
13709 let worker_a = thread::spawn(|| pipeline_batch_over_loopback(N));
13710 let worker_b = thread::spawn(|| pipeline_batch_over_loopback(N));
13711 let (results_a, count_a) = worker_a.join().expect("worker a joins");
13712 let (results_b, count_b) = worker_b.join().expect("worker b joins");
13713
13714 assert_eq!(count_a, N + 1, "batch A collapsed to one round trip");
13715 assert_eq!(count_b, N + 1, "batch B collapsed to one round trip");
13716 assert_eq!(results_a.len(), N);
13717 assert_eq!(results_b.len(), N);
13718 for (index, decoded) in results_a.iter().chain(results_b.iter()).enumerate() {
13719 assert_eq!(
13720 *decoded, reference,
13721 "concurrent pipelined op {index} matches the sequential decode"
13722 );
13723 }
13724 }
13725
13726 #[test]
13727 fn pipeline_inactivity_timeout_breaks_and_drains_before_reuse() -> Result<()> {
13728 const TRAILING_CANCEL_ERROR: &[u8] = &[0x04, 0x01, 0x0d];
13735 const TIMEOUT: Duration = Duration::from_millis(100);
13736
13737 let listener = TcpListener::bind("127.0.0.1:0")?;
13738 let addr = listener.local_addr()?;
13739 let server = thread::spawn(move || -> std::io::Result<()> {
13740 use std::io::Write as _;
13741
13742 let (mut socket, _) = listener.accept()?;
13743 socket.set_read_timeout(Some(Duration::from_secs(5)))?;
13744
13745 for _ in 0..2 {
13746 assert_eq!(
13747 read_one_wire_packet(&mut socket),
13748 TNS_PACKET_TYPE_DATA,
13749 "pipeline must send its operation and end-pipeline packets before reading"
13750 );
13751 }
13752 assert_eq!(
13753 read_marker_type(&mut socket),
13754 TNS_MARKER_TYPE_BREAK,
13755 "inactivity timeout must BREAK the stranded pipeline response"
13756 );
13757 socket.write_all(&data_packet(b"stranded pipeline response", true))?;
13758 socket.write_all(&marker_packet(TNS_MARKER_TYPE_BREAK))?;
13759 assert_eq!(
13760 read_marker_type(&mut socket),
13761 TNS_MARKER_TYPE_RESET,
13762 "pipeline drain must complete the RESET handshake"
13763 );
13764 socket.write_all(&marker_packet(TNS_MARKER_TYPE_RESET))?;
13765 socket.write_all(&data_packet(TRAILING_CANCEL_ERROR, true))?;
13766
13767 assert_eq!(
13768 read_one_wire_packet(&mut socket),
13769 TNS_PACKET_TYPE_DATA,
13770 "only a cleanly recovered connection may issue the follow-up query"
13771 );
13772 socket.write_all(&data_packet(
13773 &synthetic_pipeline_execute_response_payload(),
13774 true,
13775 ))?;
13776 socket.flush()?;
13777 Ok(())
13778 });
13779
13780 let runtime = build_io_runtime()?;
13781 runtime.block_on(async {
13782 let cx = test_cx()?;
13783 let stream = TcpStream::connect(addr).await?;
13784 let (read, write) = transport::plain_split(stream);
13785 let mut connection = loopback_connection(read, write);
13786 connection.core.set_inactivity_timeout(Some(TIMEOUT));
13787
13788 let err = connection
13789 .run_pipeline(&cx, &[PipelineRequest::Commit], false)
13790 .await
13791 .expect_err("silent pipeline response must hit the inactivity timeout");
13792 assert!(
13793 matches!(err, Error::CallTimeout(ms) if ms == 100),
13794 "expected the configured inactivity timeout, got {err:?}"
13795 );
13796 assert_eq!(
13797 connection.core.recovery.phase(),
13798 SessionRecoveryPhase::Ready,
13799 "CallTimeout must return only after BREAK-and-drain leaves the wire clean"
13800 );
13801
13802 let reused = connection
13803 .execute_raw(
13804 &cx,
13805 "select value from pipeline_timeout_reuse_fixture",
13806 2,
13807 &[],
13808 ExecuteOptions::default(),
13809 None,
13810 )
13811 .await?;
13812 assert_eq!(
13813 reused,
13814 sequential_op_decode(&synthetic_pipeline_execute_response_payload()),
13815 "the follow-up query must not consume a stranded pipeline response"
13816 );
13817 assert_eq!(
13818 connection.core.recovery.phase(),
13819 SessionRecoveryPhase::Ready
13820 );
13821 Ok::<_, Error>(())
13822 })?;
13823
13824 server.join().expect("pipeline timeout server joins")?;
13825 Ok(())
13826 }
13827
13828 #[test]
13829 fn in_flight_packet_resource_limit_marks_connection_dead() -> Result<()> {
13830 let listener = TcpListener::bind("127.0.0.1:0").expect("bind local listener");
13831 let addr = listener.local_addr().expect("listener address");
13832 let server = thread::spawn(move || {
13833 let (mut socket, _) = listener.accept().expect("accept test client");
13834 use std::io::Write as _;
13835 socket
13836 .write_all(&data_packet(&[0x01, 0x02, 0x03], true))
13837 .expect("write oversized packet");
13838 });
13839
13840 let runtime = build_io_runtime().expect("asupersync runtime");
13841 let conn = runtime.block_on(async {
13842 let cx = Cx::current()
13843 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
13844 let stream = TcpStream::connect(addr).await.expect("connect to listener");
13845 let (read, write) = transport::plain_split(stream);
13846 let mut conn = loopback_connection(read, write);
13847 let limits = ProtocolLimits {
13848 max_packet_bytes: 12,
13849 max_frame_bytes: 16,
13850 max_response_bytes: 32,
13851 ..ProtocolLimits::DEFAULT
13852 }
13853 .validate()?;
13854 conn.protocol_limits = limits;
13855 conn.core.set_protocol_limits(limits)?;
13856
13857 let err = conn
13858 .core
13859 .read_data_response(&cx)
13860 .await
13861 .expect_err("oversized in-flight packet must fail closed");
13862 assert!(matches!(
13863 err,
13864 Error::Protocol(oracledb_protocol::ProtocolError::ResourceLimit {
13865 limit: "packet_bytes",
13866 observed: 13,
13867 maximum: 12,
13868 })
13869 ));
13870 Ok::<Connection, Error>(conn)
13871 })?;
13872
13873 assert!(
13874 conn.is_dead(),
13875 "in-flight resource-limit violations leave unread wire bytes"
13876 );
13877 assert_eq!(conn.core.recovery.phase(), SessionRecoveryPhase::Dead);
13878
13879 drop(conn);
13880 server.join().expect("server thread joins");
13881 Ok(())
13882 }
13883
13884 #[test]
13896 fn classic_probed_read_reassembles_until_probe_reports_complete() -> Result<()> {
13897 let listener = TcpListener::bind("127.0.0.1:0").expect("bind local listener");
13898 let addr = listener.local_addr().expect("listener address");
13899 let server = thread::spawn(move || {
13900 let (mut socket, _) = listener.accept().expect("accept test client");
13901 use std::io::Write as _;
13902 socket
13905 .write_all(&data_packet(&[0x01, 0x02], false))
13906 .expect("write first classic packet");
13907 socket
13908 .write_all(&data_packet(&[0x03, 0x04], false))
13909 .expect("write second classic packet");
13910 });
13911
13912 let runtime = build_io_runtime().expect("asupersync runtime");
13913 let probed = Arc::new(std::sync::Mutex::new(Vec::<Vec<u8>>::new()));
13914 let probed_in = Arc::clone(&probed);
13915 let response = runtime.block_on(async {
13916 let cx = Cx::current()
13917 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
13918 let stream = TcpStream::connect(addr).await.expect("connect to listener");
13919 let (read, write) = transport::plain_split(stream);
13920 let mut conn = loopback_connection(read, write);
13921 conn.core
13922 .read_data_response_probed(&cx, true, move |bytes| {
13923 probed_in
13924 .lock()
13925 .expect("probe snapshot lock")
13926 .push(bytes.to_vec());
13927 bytes.len() >= 4
13929 })
13930 .await
13931 })?;
13932
13933 assert_eq!(
13934 response,
13935 [0x01, 0x02, 0x03, 0x04],
13936 "classic read must reassemble both flag-stripped payloads"
13937 );
13938 let probed = probed.lock().expect("probe snapshot lock");
13939 assert_eq!(
13940 probed.as_slice(),
13941 &[vec![0x01, 0x02], vec![0x01, 0x02, 0x03, 0x04]],
13942 "the probe must run over the accumulated payload after every packet"
13943 );
13944 server.join().expect("server thread joins");
13945 Ok(())
13946 }
13947
13948 #[test]
13951 fn classic_probed_read_single_packet_happy_path() -> Result<()> {
13952 let listener = TcpListener::bind("127.0.0.1:0").expect("bind local listener");
13953 let addr = listener.local_addr().expect("listener address");
13954 let server = thread::spawn(move || {
13955 let (mut socket, _) = listener.accept().expect("accept test client");
13956 use std::io::Write as _;
13957 socket
13958 .write_all(&data_packet(
13959 &[0x09, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06],
13960 false,
13961 ))
13962 .expect("write classic packet");
13963 });
13964
13965 let runtime = build_io_runtime().expect("asupersync runtime");
13966 let response = runtime.block_on(async {
13967 let cx = Cx::current()
13968 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
13969 let stream = TcpStream::connect(addr).await.expect("connect to listener");
13970 let (read, write) = transport::plain_split(stream);
13971 let mut conn = loopback_connection(read, write);
13972 conn.core
13973 .read_data_response_probed(&cx, true, |bytes| !bytes.is_empty())
13974 .await
13975 })?;
13976
13977 assert_eq!(response, [0x09, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06]);
13978 server.join().expect("server thread joins");
13979 Ok(())
13980 }
13981
13982 #[test]
13987 fn probed_read_ignores_probe_when_not_classic() -> Result<()> {
13988 let listener = TcpListener::bind("127.0.0.1:0").expect("bind local listener");
13989 let addr = listener.local_addr().expect("listener address");
13990 let server = thread::spawn(move || {
13991 let (mut socket, _) = listener.accept().expect("accept test client");
13992 use std::io::Write as _;
13993 socket
13994 .write_all(&data_packet(&[0x01, 0x02, 0x03], true))
13995 .expect("write flag-framed packet");
13996 });
13997
13998 let runtime = build_io_runtime().expect("asupersync runtime");
13999 let response = runtime.block_on(async {
14000 let cx = Cx::current()
14001 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
14002 let stream = TcpStream::connect(addr).await.expect("connect to listener");
14003 let (read, write) = transport::plain_split(stream);
14004 let mut conn = loopback_connection(read, write);
14005 conn.core
14006 .read_data_response_probed(&cx, false, |_| false)
14007 .await
14008 })?;
14009
14010 assert_eq!(response, [0x01, 0x02, 0x03]);
14011 server.join().expect("server thread joins");
14012 Ok(())
14013 }
14014
14015 #[test]
14019 fn classic_probed_read_rejects_non_data_packet() -> Result<()> {
14020 let listener = TcpListener::bind("127.0.0.1:0").expect("bind local listener");
14021 let addr = listener.local_addr().expect("listener address");
14022 let server = thread::spawn(move || {
14023 let (mut socket, _) = listener.accept().expect("accept test client");
14024 use std::io::Write as _;
14025 let packet = encode_packet(
14026 TNS_PACKET_TYPE_ACCEPT,
14027 0,
14028 None,
14029 &[0x00],
14030 PacketLengthWidth::Large32,
14031 )
14032 .expect("encode unexpected packet");
14033 socket.write_all(&packet).expect("write unexpected packet");
14034 });
14035
14036 let runtime = build_io_runtime().expect("asupersync runtime");
14037 let err = runtime.block_on(async {
14038 let cx = Cx::current().expect("ambient Cx");
14039 let stream = TcpStream::connect(addr).await.expect("connect to listener");
14040 let (read, write) = transport::plain_split(stream);
14041 let mut conn = loopback_connection(read, write);
14042 conn.core
14043 .read_data_response_probed(&cx, true, |_| true)
14044 .await
14045 .expect_err("non-DATA packet must fail closed")
14046 });
14047
14048 assert!(matches!(
14049 err,
14050 Error::UnexpectedPacket(TNS_PACKET_TYPE_ACCEPT)
14051 ));
14052 assert_eq!(err.kind(), ErrorKind::Protocol);
14053 server.join().expect("server thread joins");
14054 Ok(())
14055 }
14056
14057 #[test]
14066 fn data_packet_ends_response_requires_flag_or_lone_marker_byte() {
14067 const EOR: u8 = TNS_MSG_TYPE_END_OF_RESPONSE; const FOB: u8 = TNS_MSG_TYPE_FLUSH_OUT_BINDS; let eor_flag = oracledb_protocol::thin::TNS_DATA_FLAGS_END_OF_RESPONSE;
14070 let eof_flag = oracledb_protocol::thin::TNS_DATA_FLAGS_EOF;
14071
14072 assert!(data_packet_ends_response(eor_flag, &[0x01, 0x02, EOR]));
14074 assert!(data_packet_ends_response(eor_flag, &[]));
14075 assert!(data_packet_ends_response(eof_flag, &[0x01, 0x02, 0x03]));
14077
14078 assert!(data_packet_ends_response(0x0000, &[EOR]));
14082 assert!(data_packet_ends_response(0x0000, &[FOB]));
14083
14084 assert!(!data_packet_ends_response(0x0000, &[0xc1, 0x02, EOR]));
14089 assert!(!data_packet_ends_response(0x0000, &[0x00, EOR]));
14090 assert!(!data_packet_ends_response(0x0000, &[EOR, 0x05, 0x06, EOR]));
14091 assert!(!data_packet_ends_response(0x0000, &[0xc1, 0x02, FOB]));
14092 assert!(!data_packet_ends_response(0x0000, &[0x00, FOB]));
14093 assert!(!data_packet_ends_response(0x0000, &[0x01, 0x02, 0x03]));
14095 assert!(!data_packet_ends_response(0x0000, &[]));
14096 }
14097
14098 fn replay_boundary(packets: &[(u16, Vec<u8>)]) -> (Vec<u8>, Option<usize>, bool) {
14107 let mut reassembled = Vec::new();
14108 let mut stopped_at = None;
14109 for (index, (flags, payload)) in packets.iter().enumerate() {
14110 let ends = data_packet_ends_response(*flags, payload);
14111 if ends && reassembled.is_empty() {
14112 reassembled = payload.clone();
14114 stopped_at = Some(index);
14115 break;
14116 }
14117 reassembled.extend_from_slice(payload);
14118 if ends {
14119 stopped_at = Some(index);
14120 break;
14121 }
14122 }
14123 let flush_out_binds = matches!(reassembled.last(), Some(&TNS_MSG_TYPE_FLUSH_OUT_BINDS));
14124 (reassembled, stopped_at, flush_out_binds)
14125 }
14126
14127 #[test]
14134 fn boundary_loop_reassembles_packets_ending_in_marker_byte() {
14135 const EOR: u8 = TNS_MSG_TYPE_END_OF_RESPONSE;
14136 const FOB: u8 = TNS_MSG_TYPE_FLUSH_OUT_BINDS;
14137 let packets: [(u16, Vec<u8>); 5] = [
14138 (0x0000, vec![0x10, 0x11, EOR]), (0x0000, vec![0x20, 0x21, 0x22, FOB]), (0x0000, vec![0x30, 0x31, 0x32, EOR]), (0x0000, vec![0x33, 0x34, 0x35]), (
14143 oracledb_protocol::thin::TNS_DATA_FLAGS_END_OF_RESPONSE,
14144 vec![0x40, 0x41, EOR], ),
14146 ];
14147
14148 let (reassembled, stopped_at, flush_out_binds) = replay_boundary(&packets);
14149
14150 assert_eq!(
14151 stopped_at,
14152 Some(4),
14153 "reassembly must stop only on the flagged final packet, not on a body packet ending in a marker byte"
14154 );
14155 assert!(
14156 !flush_out_binds,
14157 "the response ended in END_OF_RESPONSE, not FLUSH_OUT_BINDS"
14158 );
14159 assert_eq!(
14160 reassembled,
14161 vec![
14162 0x10, 0x11, EOR, 0x20, 0x21, 0x22, FOB, 0x30, 0x31, 0x32, EOR, 0x33, 0x34, 0x35, 0x40, 0x41, EOR, ],
14168 "every body packet's bytes must be concatenated in order with none dropped"
14169 );
14170 }
14171
14172 #[test]
14176 fn boundary_loop_detects_flush_out_binds_only_at_true_boundary() {
14177 const FOB: u8 = TNS_MSG_TYPE_FLUSH_OUT_BINDS;
14178 let packets: [(u16, Vec<u8>); 2] = [
14179 (0x0000, vec![0x01, 0x02, FOB]), (
14181 oracledb_protocol::thin::TNS_DATA_FLAGS_END_OF_RESPONSE,
14182 vec![0x03, FOB], ),
14184 ];
14185
14186 let (reassembled, stopped_at, flush_out_binds) = replay_boundary(&packets);
14187
14188 assert_eq!(stopped_at, Some(1), "stop on the EOR-flagged tail");
14189 assert!(
14190 flush_out_binds,
14191 "flush-out-binds must be detected from the terminal FLUSH_OUT_BINDS message byte"
14192 );
14193 assert_eq!(reassembled, vec![0x01, 0x02, FOB, 0x03, FOB]);
14194 }
14195
14196 #[test]
14201 fn single_packet_passthrough_is_byte_identical_to_extend() {
14202 const EOR: u8 = TNS_MSG_TYPE_END_OF_RESPONSE;
14203 const FOB: u8 = TNS_MSG_TYPE_FLUSH_OUT_BINDS;
14204 let eor_flag = oracledb_protocol::thin::TNS_DATA_FLAGS_END_OF_RESPONSE;
14205
14206 fn replay_extend_only(packets: &[(u16, Vec<u8>)]) -> (Vec<u8>, Option<usize>, bool) {
14208 let mut reassembled = Vec::new();
14209 let mut stopped_at = None;
14210 for (index, (flags, payload)) in packets.iter().enumerate() {
14211 reassembled.extend_from_slice(payload);
14212 if data_packet_ends_response(*flags, payload) {
14213 stopped_at = Some(index);
14214 break;
14215 }
14216 }
14217 let flush = matches!(reassembled.last(), Some(&TNS_MSG_TYPE_FLUSH_OUT_BINDS));
14218 (reassembled, stopped_at, flush)
14219 }
14220
14221 let cases: &[(u16, Vec<u8>)] = &[
14224 (eor_flag, vec![0x40, 0x41, EOR]),
14225 (eor_flag, vec![0x03, FOB]), (eor_flag, vec![0xde, 0xad, 0xbe, 0xef]),
14227 (eor_flag, vec![0x00]),
14228 ];
14229 for (flags, payload) in cases {
14230 let one = [(*flags, payload.clone())];
14231 let passthrough = replay_boundary(&one);
14232 let extend = replay_extend_only(&one);
14233 assert_eq!(
14234 passthrough, extend,
14235 "passthrough must equal extend for single packet {payload:02x?}"
14236 );
14237 assert_eq!(&passthrough.0, payload);
14239 }
14240 }
14241
14242 #[test]
14243 fn parse_user_and_proxy_bracket_forms() {
14244 assert_eq!(parse_user_and_proxy("scott"), ("scott".into(), None));
14246 assert_eq!(
14248 parse_user_and_proxy("scott[proxy]"),
14249 ("scott".into(), Some("proxy".into()))
14250 );
14251 assert_eq!(
14253 parse_user_and_proxy("[proxy_only]"),
14254 ("".into(), Some("proxy_only".into()))
14255 );
14256 assert_eq!(
14258 parse_user_and_proxy("scott[proxy"),
14259 ("scott[proxy".into(), None)
14260 );
14261 assert_eq!(parse_user_and_proxy("scott]"), ("scott]".into(), None));
14263 }
14264
14265 #[test]
14266 fn connect_options_desugars_bracket_user_and_full_user_display() {
14267 let base_proxy = ConnectOptions::new("h:1521/s", "scott[proxy]", "pw", identity());
14268 assert_eq!(base_proxy.user(), "scott");
14269 assert_eq!(base_proxy.proxy_user(), Some("proxy"));
14270 assert_eq!(base_proxy.full_user(), "scott[proxy]");
14271 assert!(matches!(base_proxy.auth_mode(), AuthMode::Proxy));
14272
14273 let proxy_only = ConnectOptions::new("h:1521/s", "[proxy_only]", "", identity())
14274 .with_access_token("super-secret-db-token-xyz");
14275 assert_eq!(proxy_only.user(), "");
14276 assert_eq!(proxy_only.proxy_user(), Some("proxy_only"));
14277 assert_eq!(proxy_only.full_user(), "[proxy_only]");
14278 let dbg = format!("{proxy_only:?}");
14280 assert!(dbg.contains("[proxy_only]"), "debug was {dbg}");
14281 assert!(
14282 !dbg.contains("super-secret-db-token-xyz"),
14283 "token leaked in debug: {dbg}"
14284 );
14285 assert!(
14286 dbg.contains("***redacted***"),
14287 "password/token field missing redaction: {dbg}"
14288 );
14289
14290 let overwritten = ConnectOptions::new("h:1521/s", "scott[from_bracket]", "pw", identity())
14292 .with_proxy_user(Some("from_explicit".into()));
14293 assert_eq!(overwritten.user(), "scott");
14294 assert_eq!(overwritten.proxy_user(), Some("from_explicit"));
14295 assert_eq!(overwritten.full_user(), "scott[from_explicit]");
14296 }
14297
14298 #[test]
14299 fn proxy_only_token_auth_encodes_phase_two_proxy_header() {
14300 use oracledb_protocol::crypto::EncryptedPassword;
14302 use oracledb_protocol::thin::build_auth_phase_two_payload_with_proxy_with_seq;
14303 let encrypted = EncryptedPassword {
14304 session_key: "00".repeat(32),
14305 speedy_key: None,
14306 password: "00".repeat(32),
14307 combo_key: vec![0u8; 32],
14308 };
14309 let payload = build_auth_phase_two_payload_with_proxy_with_seq(
14310 "",
14311 &encrypted,
14312 "rust-oracledb",
14313 0,
14314 "",
14315 1,
14316 &[],
14317 Some("proxy_only"),
14318 None,
14319 17, )
14321 .expect("phase-two proxy payload");
14322 let text = String::from_utf8_lossy(&payload);
14323 assert!(
14324 text.contains("PROXY_CLIENT_NAME") && text.contains("proxy_only"),
14325 "payload missing PROXY_CLIENT_NAME/proxy_only: {text:?}"
14326 );
14327 }
14328
14329 #[test]
14330 fn descriptor_builder_uses_identity_in_listener_cid() {
14331 let options = ConnectOptions::new("localhost/FREEPDB1", "user", "password", identity());
14332 let descriptor =
14333 EasyConnect::parse(&options.connect_string).expect("test connect string should parse");
14334 let full_descriptor = EasyConnect::parse_descriptor(&options.connect_string)
14335 .expect("test connect string should parse as full descriptor");
14336 let description = full_descriptor.first_description();
14337 let built = listener_connect_descriptor_with_server(
14338 &descriptor,
14339 description,
14340 &options.identity,
14341 false,
14342 false,
14343 true,
14344 None,
14345 );
14346 assert!(built.contains("(PROGRAM=program)"));
14347 assert!(built.contains("(HOST=machine)"));
14348 assert!(built.contains("(USER=osuser)"));
14349 assert!(!built.contains("(SERVER=emon)"));
14350 let emon = listener_connect_descriptor_with_server(
14352 &descriptor,
14353 description,
14354 &options.identity,
14355 true,
14356 false,
14357 true,
14358 None,
14359 );
14360 assert!(emon.contains("(SERVICE_NAME=FREEPDB1)(SERVER=emon)(CID="));
14361 }
14362
14363 #[test]
14364 fn token_auth_descriptor_uses_tcps_security_and_passthrough() {
14365 let connect_string = concat!(
14366 "(DESCRIPTION=(ADDRESS=(PROTOCOL=tcps)(HOST=adb.example.test)(PORT=2484))",
14367 "(CONNECT_DATA=(SERVICE_NAME=adbsvc))",
14368 "(SECURITY=(SSL_SERVER_DN_MATCH=off)",
14369 "(SSL_SERVER_CERT_DN=CN=adb.example.test)",
14370 "(OCI_IAM_HOST=private-endpoint)))"
14371 );
14372 let descriptor = EasyConnect::parse(connect_string).expect("parse tcps descriptor");
14373 let full_descriptor =
14374 EasyConnect::parse_descriptor(connect_string).expect("parse full descriptor");
14375 let description = full_descriptor.first_description();
14376 let built = auth_connect_descriptor(
14377 &descriptor,
14378 description,
14379 true,
14380 false,
14381 description.security.ssl_server_cert_dn.as_deref(),
14382 );
14383
14384 assert!(built.contains("(PROTOCOL=tcps)"));
14385 assert!(built.contains("(SECURITY="));
14386 assert!(built.contains("(SSL_SERVER_DN_MATCH=OFF)"));
14387 assert!(built.contains("(SSL_SERVER_CERT_DN=CN=adb.example.test)"));
14388 assert!(built.contains("(OCI_IAM_HOST=private-endpoint)"));
14389 assert!(built.contains("(TOKEN_AUTH=OCI_TOKEN)"));
14390 assert!(!built.contains("WALLET"));
14391 }
14392
14393 fn decode_golden_packet_hex(hex: &str) -> Vec<u8> {
14424 let digits: Vec<u8> = hex.bytes().filter(|b| !b.is_ascii_whitespace()).collect();
14425 assert!(
14426 digits.len().is_multiple_of(2),
14427 "hex fixture must have an even number of hex digits"
14428 );
14429 digits
14430 .chunks_exact(2)
14431 .map(|pair| {
14432 let hi = (pair[0] as char).to_digit(16).expect("valid hex digit");
14433 let lo = (pair[1] as char).to_digit(16).expect("valid hex digit");
14434 ((hi << 4) | lo) as u8
14435 })
14436 .collect()
14437 }
14438
14439 fn golden_connect_packet_bytes(connect_data: &str, sdu: u16) -> Vec<u8> {
14444 let payload =
14445 build_connect_packet_payload(connect_data, sdu).expect("connect packet payload");
14446 encode_packet(
14447 TNS_PACKET_TYPE_CONNECT,
14448 0,
14449 None,
14450 &payload,
14451 PacketLengthWidth::Legacy16,
14452 )
14453 .expect("encode CONNECT packet")
14454 }
14455
14456 #[test]
14457 fn golden_password_plain_tcp_descriptor_and_wire_bytes() {
14458 let connect_string = concat!(
14459 "(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=db.example.test)(PORT=1521))",
14460 "(CONNECT_DATA=(SERVICE_NAME=FREEPDB1)))"
14461 );
14462 let descriptor = EasyConnect::parse(connect_string).expect("parse tcp descriptor");
14463 let full_descriptor =
14464 EasyConnect::parse_descriptor(connect_string).expect("parse full tcp descriptor");
14465 let description = full_descriptor.first_description();
14466 let built = auth_connect_descriptor(&descriptor, description, false, true, None);
14467
14468 let expected_descriptor =
14469 include_str!("../tests/golden/connect_descriptor/password_tcp.descriptor.txt").trim();
14470 assert_eq!(
14471 built, expected_descriptor,
14472 "password/plain-TCP built connect descriptor drifted from the golden fixture"
14473 );
14474
14475 let packet = golden_connect_packet_bytes(&built, 8192);
14476 let expected_packet = decode_golden_packet_hex(include_str!(
14477 "../tests/golden/connect_descriptor/password_tcp.packet.hex"
14478 ));
14479 assert_eq!(
14480 packet, expected_packet,
14481 "password/plain-TCP CONNECT wire packet drifted from the golden fixture"
14482 );
14483 }
14484
14485 #[test]
14486 fn golden_wallet_tcps_cert_dn_descriptor_and_wire_bytes() {
14487 let connect_string = concat!(
14488 "(DESCRIPTION=(ADDRESS=(PROTOCOL=tcps)(HOST=adb.example.test)(PORT=2484))",
14489 "(CONNECT_DATA=(SERVICE_NAME=adbsvc))",
14490 "(SECURITY=(SSL_SERVER_DN_MATCH=on)",
14491 "(SSL_SERVER_CERT_DN=CN=adb.example.test,O=ExampleCorp,C=US)",
14492 "(MY_WALLET_DIRECTORY=/opt/wallets/adb)))"
14493 );
14494 let descriptor = EasyConnect::parse(connect_string).expect("parse wallet tcps descriptor");
14495 let full_descriptor = EasyConnect::parse_descriptor(connect_string)
14496 .expect("parse full wallet tcps descriptor");
14497 let description = full_descriptor.first_description();
14498 let built = auth_connect_descriptor(
14503 &descriptor,
14504 description,
14505 false,
14506 description.security.ssl_server_dn_match,
14507 description.security.ssl_server_cert_dn.as_deref(),
14508 );
14509
14510 let expected_descriptor =
14511 include_str!("../tests/golden/connect_descriptor/wallet_tcps_cert_dn.descriptor.txt")
14512 .trim();
14513 assert_eq!(
14514 built, expected_descriptor,
14515 "wallet-TCPS (DC2 cert-DN) built connect descriptor drifted from the golden fixture"
14516 );
14517 assert!(!built.contains("WALLET"));
14520
14521 let packet = golden_connect_packet_bytes(&built, 8192);
14522 let expected_packet = decode_golden_packet_hex(include_str!(
14523 "../tests/golden/connect_descriptor/wallet_tcps_cert_dn.packet.hex"
14524 ));
14525 assert_eq!(
14526 packet, expected_packet,
14527 "wallet-TCPS (DC2 cert-DN) CONNECT wire packet drifted from the golden fixture"
14528 );
14529 }
14530
14531 #[test]
14532 fn golden_iam_token_tcps_descriptor_and_wire_bytes() {
14533 let connect_string = concat!(
14534 "(DESCRIPTION=(ADDRESS=(PROTOCOL=tcps)(HOST=adb.example.test)(PORT=2484))",
14535 "(CONNECT_DATA=(SERVICE_NAME=adbsvc))",
14536 "(SECURITY=(SSL_SERVER_DN_MATCH=off)))"
14537 );
14538 let descriptor = EasyConnect::parse(connect_string).expect("parse iam token descriptor");
14539 let full_descriptor =
14540 EasyConnect::parse_descriptor(connect_string).expect("parse full iam token descriptor");
14541 let description = full_descriptor.first_description();
14542 let built = auth_connect_descriptor(
14543 &descriptor,
14544 description,
14545 true,
14546 description.security.ssl_server_dn_match,
14547 None,
14548 );
14549
14550 let expected_descriptor =
14551 include_str!("../tests/golden/connect_descriptor/iam_token_tcps.descriptor.txt").trim();
14552 assert_eq!(
14553 built, expected_descriptor,
14554 "IAM-token TCPS built connect descriptor drifted from the golden fixture"
14555 );
14556
14557 let packet = golden_connect_packet_bytes(&built, 8192);
14558 let expected_packet = decode_golden_packet_hex(include_str!(
14559 "../tests/golden/connect_descriptor/iam_token_tcps.packet.hex"
14560 ));
14561 assert_eq!(
14562 packet, expected_packet,
14563 "IAM-token TCPS CONNECT wire packet drifted from the golden fixture"
14564 );
14565 }
14566
14567 #[test]
14568 fn transport_connect_timeout_bounds_post_dial_accept_read() -> Result<()> {
14569 let listener = TcpListener::bind("127.0.0.1:0").expect("bind local listener");
14570 let addr = listener.local_addr().expect("listener address");
14571 let server = thread::spawn(move || -> std::io::Result<()> {
14572 let (mut socket, _) = listener.accept()?;
14573 socket.set_read_timeout(Some(Duration::from_secs(5)))?;
14574 let mut header = [0u8; 8];
14575 socket.read_exact(&mut header)?;
14576 let declared = usize::from(u16::from_be_bytes([header[0], header[1]]));
14577 let mut payload = vec![0u8; declared.saturating_sub(header.len())];
14578 socket.read_exact(&mut payload)?;
14579 thread::sleep(Duration::from_millis(300));
14580 Ok(())
14581 });
14582
14583 let options = ConnectOptions::new(
14584 format!(
14585 "127.0.0.1:{}/FREEPDB1?transport_connect_timeout=100ms",
14586 addr.port()
14587 ),
14588 "user",
14589 "password",
14590 identity(),
14591 );
14592 let runtime = build_io_runtime().expect("asupersync runtime");
14593 let started = Instant::now();
14594 let err = runtime
14595 .block_on(async {
14596 let cx = Cx::current().expect("ambient Cx");
14597 Connection::connect(&cx, options).await
14598 })
14599 .expect_err("stalling listener should hit transport connect timeout");
14600 assert!(
14601 matches!(err, Error::CallTimeout(ms) if ms == 100),
14602 "expected 100ms CallTimeout, got {err:?}"
14603 );
14604 assert!(
14605 started.elapsed() < Duration::from_secs(2),
14606 "post-dial ACCEPT read should be bounded"
14607 );
14608 server.join().expect("server thread joins")?;
14609 Ok(())
14610 }
14611
14612 #[test]
14625 fn full_descriptor_connect_timeout_bounds_a_stalled_tls_handshake() -> Result<()> {
14626 let listener = TcpListener::bind("127.0.0.1:0").expect("bind local listener");
14627 let addr = listener.local_addr().expect("listener address");
14628 let server = thread::spawn(move || -> std::io::Result<()> {
14632 let (socket, _) = listener.accept()?;
14633 thread::sleep(Duration::from_secs(3));
14634 drop(socket);
14635 Ok(())
14636 });
14637
14638 let wallet_dir = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixtures/tls");
14639 let connect_string = format!(
14640 "(DESCRIPTION=(CONNECT_TIMEOUT=0.3)\
14641 (ADDRESS=(PROTOCOL=tcps)(HOST=127.0.0.1)(PORT={}))\
14642 (CONNECT_DATA=(SERVICE_NAME=FREEPDB1))\
14643 (SECURITY=(SSL_SERVER_DN_MATCH=off)(MY_WALLET_DIRECTORY=\"{wallet_dir}\")))",
14644 addr.port(),
14645 );
14646 let options = ConnectOptions::new(connect_string, "user", "password", identity());
14647 let runtime = build_io_runtime().expect("asupersync runtime");
14648 let started = Instant::now();
14649 let err = runtime
14650 .block_on(async {
14651 let cx = Cx::current().expect("ambient Cx");
14652 Connection::connect(&cx, options).await
14653 })
14654 .expect_err("a peer that never speaks TLS must not let connect succeed");
14655 let elapsed = started.elapsed();
14656 assert!(
14657 elapsed < Duration::from_secs(2),
14658 "the descriptor's 0.3s CONNECT_TIMEOUT must bound the TLS handshake too, not just \
14659 the TCP dial (the old hard-coded ~20s handshake timeout, or the peer's own 3s \
14660 silent hold, would both blow this budget); actually took {elapsed:?}, err={err:?}"
14661 );
14662 server.join().expect("server thread joins")?;
14663 Ok(())
14664 }
14665
14666 #[test]
14671 fn full_descriptor_preserves_above_twenty_second_tls_handshake_budget() -> Result<()> {
14672 const CONFIGURED_TIMEOUT: Duration = Duration::from_secs(45);
14673 const SERVER_HOLD: Duration = Duration::from_millis(250);
14674
14675 let listener = TcpListener::bind("127.0.0.1:0").expect("bind local listener");
14676 let addr = listener.local_addr().expect("listener address");
14677 let server = thread::spawn(move || -> std::io::Result<()> {
14678 let (socket, _) = listener.accept()?;
14679 thread::sleep(SERVER_HOLD);
14680 drop(socket);
14681 Ok(())
14682 });
14683
14684 let wallet_dir = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixtures/tls");
14685 let connect_string = format!(
14686 "(DESCRIPTION=(CONNECT_TIMEOUT=45)\
14687 (ADDRESS=(PROTOCOL=tcps)(HOST=127.0.0.1)(PORT={}))\
14688 (CONNECT_DATA=(SERVICE_NAME=FREEPDB1))\
14689 (SECURITY=(SSL_SERVER_DN_MATCH=off)(MY_WALLET_DIRECTORY=\"{wallet_dir}\")))",
14690 addr.port(),
14691 );
14692 let parsed = EasyConnect::parse_descriptor(&connect_string)
14693 .expect("full descriptor with a 45-second CONNECT_TIMEOUT parses");
14694 let resolved_connect_timeout =
14695 transport_connect_timeout_duration(parsed.first_description().tcp_connect_timeout);
14696 assert_eq!(resolved_connect_timeout, CONFIGURED_TIMEOUT);
14697 assert_eq!(
14698 tls::handshake_timeout_from_connect_timeout(resolved_connect_timeout),
14699 CONFIGURED_TIMEOUT,
14700 "the production TLS handoff must not re-clamp a configured 45-second budget"
14701 );
14702
14703 let options = ConnectOptions::new(connect_string, "user", "password", identity());
14704 let runtime = build_io_runtime().expect("asupersync runtime");
14705 let started = Instant::now();
14706 let err = runtime
14707 .block_on(async {
14708 let cx = Cx::current().expect("ambient Cx");
14709 Connection::connect(&cx, options).await
14710 })
14711 .expect_err("a peer that never completes TLS must not connect");
14712 let elapsed = started.elapsed();
14713 assert!(
14714 elapsed >= SERVER_HOLD / 2 && elapsed < Duration::from_secs(2),
14715 "the stalled peer must drive the observed failure without turning a {CONFIGURED_TIMEOUT:?} \
14716 configuration check into a long wait; took {elapsed:?}, err={err:?}"
14717 );
14718 server.join().expect("server thread joins")?;
14719 Ok(())
14720 }
14721
14722 fn spawn_dc2_dc3_fixture_tls_server() -> (u16, thread::JoinHandle<()>) {
14765 let mut fixtures = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
14766 fixtures.push("tests");
14767 fixtures.push("fixtures");
14768 fixtures.push("tls");
14769 let leaf = std::fs::read(fixtures.join("leaf.crt")).expect("read leaf.crt fixture");
14770 let key = std::fs::read(fixtures.join("leaf.key")).expect("read leaf.key fixture");
14771 let ca = std::fs::read(fixtures.join("ca.crt")).expect("read ca.crt fixture");
14772
14773 let mut chain: Vec<rustls::pki_types::CertificateDer<'static>> =
14774 rustls_pemfile::certs(&mut std::io::BufReader::new(&leaf[..]))
14775 .filter_map(std::result::Result::ok)
14776 .collect();
14777 chain.extend(
14778 rustls_pemfile::certs(&mut std::io::BufReader::new(&ca[..]))
14779 .filter_map(std::result::Result::ok),
14780 );
14781 let key_der = rustls_pemfile::private_key(&mut std::io::BufReader::new(&key[..]))
14782 .expect("read leaf.key")
14783 .expect("leaf.key must contain a private key");
14784
14785 let config = rustls::ServerConfig::builder()
14786 .with_no_client_auth()
14787 .with_single_cert(chain, key_der)
14788 .expect("fixture server config");
14789 let config = std::sync::Arc::new(config);
14790
14791 let listener = TcpListener::bind("127.0.0.1:0").expect("bind local listener");
14792 let port = listener.local_addr().expect("listener address").port();
14793 let handle = thread::spawn(move || {
14794 if let Ok((mut sock, _)) = listener.accept() {
14795 if let Ok(mut conn) = rustls::ServerConnection::new(config) {
14796 let mut tls = rustls::Stream::new(&mut conn, &mut sock);
14797 let mut buf = [0u8; 4096];
14798 let _ = tls.read(&mut buf);
14804 }
14805 }
14806 });
14807 (port, handle)
14808 }
14809
14810 fn dc2_dc3_temp_ca_trust_wallet_dir(label: &str) -> std::path::PathBuf {
14817 let mut fixture = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
14818 fixture.push("tests");
14819 fixture.push("fixtures");
14820 fixture.push("tls");
14821 fixture.push("ca_wallet.pem");
14822
14823 let dir = std::env::temp_dir().join(format!(
14824 "oracledb-dc2-dc3-wallet-{label}-{}",
14825 std::process::id()
14826 ));
14827 std::fs::create_dir_all(&dir).expect("create temp wallet dir");
14828 std::fs::copy(&fixture, dir.join("ewallet.pem")).expect("copy ca_wallet.pem fixture");
14829 dir
14830 }
14831
14832 fn dc5_untrusted_temp_wallet_dir(label: &str) -> std::path::PathBuf {
14837 let mut fixture = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
14838 fixture.push("tests");
14839 fixture.push("fixtures");
14840 fixture.push("tls");
14841 fixture.push("synthetic");
14842 fixture.push("ca.pem");
14843
14844 let dir = std::env::temp_dir().join(format!(
14845 "oracledb-dc5-untrusted-wallet-{label}-{}",
14846 std::process::id()
14847 ));
14848 std::fs::create_dir_all(&dir).expect("create temp untrusted wallet dir");
14849 std::fs::copy(&fixture, dir.join("ewallet.pem")).expect("copy unrelated ca.pem fixture");
14850 dir
14851 }
14852
14853 fn dc2_dc3_connect_string(
14860 port: u16,
14861 wallet_dir: &std::path::Path,
14862 cert_dn: &str,
14863 dn_match_off: bool,
14864 ) -> String {
14865 let dn_match_clause = if dn_match_off {
14866 "(SSL_SERVER_DN_MATCH=off)"
14867 } else {
14868 ""
14869 };
14870 let wallet = wallet_dir.display();
14871 format!(
14872 "(DESCRIPTION=(CONNECT_TIMEOUT=10)\
14873 (ADDRESS=(PROTOCOL=tcps)(HOST=127.0.0.1)(PORT={port}))\
14874 (CONNECT_DATA=(SERVICE_NAME=FREEPDB1))\
14875 (SECURITY={dn_match_clause}(SSL_SERVER_CERT_DN={cert_dn})\
14876 (MY_WALLET_DIRECTORY=\"{wallet}\")))"
14877 )
14878 }
14879
14880 const DC_CERT_DN_MISMATCH_PHRASE: &str =
14884 "distinguished name (DN) on the server certificate does not match";
14885 const DC_NAME_MISMATCH_PHRASE: &str = "does not match the names in the server certificate";
14889
14890 fn dc_rendered_error(err: &Error) -> String {
14898 format!("{err} / debug={err:?}")
14899 }
14900
14901 #[test]
14908 fn b5_tcps_unknown_issuer_is_tls_error_not_timeout_or_failover() {
14909 let (port, server) = spawn_dc2_dc3_fixture_tls_server();
14910 let wallet_dir = dc5_untrusted_temp_wallet_dir("unknown-issuer");
14911 let options = ConnectOptions::new(
14912 format!("tcps://127.0.0.1:{port}/FREEPDB1?transport_connect_timeout=10"),
14913 "user",
14914 "password",
14915 identity(),
14916 )
14917 .with_wallet_location(wallet_dir.display().to_string());
14918
14919 let runtime = build_io_runtime().expect("asupersync runtime");
14920 let started = Instant::now();
14921 let err = runtime
14922 .block_on(async {
14923 let cx = Cx::current().expect("ambient Cx");
14924 Connection::connect(&cx, options).await
14925 })
14926 .expect_err("an untrusted synthetic TCPS certificate must be refused");
14927 let elapsed = started.elapsed();
14928 let rendered = dc_rendered_error(&err);
14929 assert!(
14930 elapsed < Duration::from_secs(5),
14931 "untrusted issuer must fail fast, not wait for connect timeout; took {elapsed:?}, \
14932 error={rendered}"
14933 );
14934 assert!(
14935 matches!(&err, Error::Tls(detail)
14936 if detail.contains("certificate") && detail.contains("not trusted")),
14937 "untrusted issuer must surface as a structured TLS certificate error, not timeout or \
14938 failover aggregation; got {rendered}"
14939 );
14940 assert!(
14941 !matches!(err, Error::CallTimeout(_) | Error::AllAddressesFailed(_)),
14942 "untrusted issuer must not be hidden as timeout/failover aggregation: {rendered}"
14943 );
14944 let _ = server.join();
14945 }
14946
14947 #[test]
14955 fn f_dc2_dsn_only_pinned_cert_dn_mismatch_is_refused() {
14956 let (port, server) = spawn_dc2_dc3_fixture_tls_server();
14957 let wallet_dir = dc2_dc3_temp_ca_trust_wallet_dir("mismatch");
14958 let wrong_dn = "CN=not-the-real-server.invalid,O=Nope,C=ZZ";
14959 let connect_string = dc2_dc3_connect_string(port, &wallet_dir, wrong_dn, false);
14960 let options = ConnectOptions::new(connect_string, "user", "password", identity());
14961 let runtime = build_io_runtime().expect("asupersync runtime");
14962 let err = runtime
14963 .block_on(async {
14964 let cx = Cx::current().expect("ambient Cx");
14965 Connection::connect(&cx, options).await
14966 })
14967 .expect_err(
14968 "a server whose DN does not match a DSN-only pinned SSL_SERVER_CERT_DN must be \
14969 refused",
14970 );
14971 let rendered = dc_rendered_error(&err);
14972 assert!(
14973 rendered.contains(wrong_dn) && rendered.contains(DC_CERT_DN_MISMATCH_PHRASE),
14974 "the DSN-only pinned SSL_SERVER_CERT_DN must actually reach the verifier — expected \
14975 the rejection to echo the pinned DN {wrong_dn:?} via a CertDnMismatch \
14976 ({DC_CERT_DN_MISMATCH_PHRASE:?}), proving `check_cert_dn` ran with the DSN-supplied \
14977 value; got: {rendered}"
14978 );
14979 let _ = server.join();
14980 }
14981
14982 #[test]
14990 fn f_dc2_dsn_only_pinned_cert_dn_match_passes_tls_layer() {
14991 let (port, server) = spawn_dc2_dc3_fixture_tls_server();
14992 let wallet_dir = dc2_dc3_temp_ca_trust_wallet_dir("match");
14993 let real_dn = "CN=db.example.com,O=ExampleDB,C=US";
14994 let connect_string = dc2_dc3_connect_string(port, &wallet_dir, real_dn, false);
14995 let options = ConnectOptions::new(connect_string, "user", "password", identity());
14996 let runtime = build_io_runtime().expect("asupersync runtime");
14997 let err = runtime
14998 .block_on(async {
14999 let cx = Cx::current().expect("ambient Cx");
15000 Connection::connect(&cx, options).await
15001 })
15002 .expect_err(
15003 "the stub server never answers a real Oracle listener response, so connect must \
15004 still fail overall",
15005 );
15006 let rendered = dc_rendered_error(&err);
15007 assert!(
15008 !rendered.contains(DC_CERT_DN_MISMATCH_PHRASE)
15009 && !rendered.contains(DC_NAME_MISMATCH_PHRASE),
15010 "a matching DSN-only pinned SSL_SERVER_CERT_DN must pass the TLS/DN layer cleanly — \
15011 got a DN-match rejection instead, meaning the pin was not honored (it likely fell \
15012 back to a hostname/SAN check against HOST=127.0.0.1, which this certificate's SAN \
15013 never matches): {rendered}"
15014 );
15015 let _ = server.join();
15016 }
15017
15018 #[test]
15025 fn f_dc3_dn_match_off_deliberately_skips_the_pinned_cert_dn_check() {
15026 let (port, server) = spawn_dc2_dc3_fixture_tls_server();
15027 let wallet_dir = dc2_dc3_temp_ca_trust_wallet_dir("off");
15028 let wrong_dn = "CN=not-the-real-server.invalid,O=Nope,C=ZZ";
15029 let connect_string = dc2_dc3_connect_string(port, &wallet_dir, wrong_dn, true);
15030 let options = ConnectOptions::new(connect_string, "user", "password", identity());
15031 let runtime = build_io_runtime().expect("asupersync runtime");
15032 let err = runtime
15033 .block_on(async {
15034 let cx = Cx::current().expect("ambient Cx");
15035 Connection::connect(&cx, options).await
15036 })
15037 .expect_err("the stub server never answers a real Oracle listener response");
15038 let rendered = dc_rendered_error(&err);
15039 assert!(
15040 !rendered.contains(DC_CERT_DN_MISMATCH_PHRASE)
15041 && !rendered.contains(DC_NAME_MISMATCH_PHRASE),
15042 "SSL_SERVER_DN_MATCH=OFF set only in the connect string must deliberately disable \
15043 the DN check — even with a mismatched pinned SSL_SERVER_CERT_DN — got a DN-match \
15044 rejection instead, meaning OFF did not actually reach the verifier: {rendered}"
15045 );
15046 let _ = server.join();
15047 }
15048
15049 #[test]
15056 fn f_dc3_dsn_and_options_dn_match_merge_is_and_not_options_only() {
15057 let default_dsn = "tcps://h.example:2484/svc";
15058 let default_desc = EasyConnect::parse_descriptor(default_dsn).expect("parse default dsn");
15059 let default_options = ConnectOptions::new(default_dsn, "u", "p", identity());
15060 assert!(
15061 effective_ssl_server_dn_match(default_desc.first_description(), &default_options),
15062 "default DSN + default options must keep DN matching ON"
15063 );
15064
15065 let off_dsn = concat!(
15067 "(DESCRIPTION=(ADDRESS=(PROTOCOL=tcps)(HOST=h.example)(PORT=2484))",
15068 "(CONNECT_DATA=(SERVICE_NAME=svc))",
15069 "(SECURITY=(SSL_SERVER_DN_MATCH=off)))"
15070 );
15071 let off_desc = EasyConnect::parse_descriptor(off_dsn).expect("parse OFF dsn");
15072 assert!(
15073 !off_desc.first_description().security.ssl_server_dn_match,
15074 "DSN SSL_SERVER_DN_MATCH=off must parse to security.ssl_server_dn_match=false"
15075 );
15076 let off_options = ConnectOptions::new(off_dsn, "u", "p", identity());
15077 assert!(
15078 !effective_ssl_server_dn_match(off_desc.first_description(), &off_options),
15079 "DSN SSL_SERVER_DN_MATCH=OFF must win over default options=true"
15080 );
15081
15082 let on_dsn = concat!(
15083 "(DESCRIPTION=(ADDRESS=(PROTOCOL=tcps)(HOST=h.example)(PORT=2484))",
15084 "(CONNECT_DATA=(SERVICE_NAME=svc))",
15085 "(SECURITY=(SSL_SERVER_DN_MATCH=on)))"
15086 );
15087 let on_desc = EasyConnect::parse_descriptor(on_dsn).expect("parse ON dsn");
15088 assert!(on_desc.first_description().security.ssl_server_dn_match);
15089
15090 let options_off =
15092 ConnectOptions::new(on_dsn, "u", "p", identity()).with_ssl_server_dn_match(false);
15093 assert!(!options_off.ssl_server_dn_match());
15094 assert!(
15095 !effective_ssl_server_dn_match(on_desc.first_description(), &options_off),
15096 "explicit ConnectOptions::with_ssl_server_dn_match(false) must win over DSN=ON"
15097 );
15098
15099 let both_off =
15100 ConnectOptions::new(off_dsn, "u", "p", identity()).with_ssl_server_dn_match(false);
15101 assert!(
15102 !effective_ssl_server_dn_match(off_desc.first_description(), &both_off),
15103 "both OFF stays OFF"
15104 );
15105
15106 let easy = EasyConnect::parse(off_dsn).expect("easy parse OFF");
15109 let params = crate::tls::resolve_tls_params(
15110 &easy,
15111 None,
15112 None,
15113 effective_ssl_server_dn_match(off_desc.first_description(), &off_options),
15114 None,
15115 false,
15116 )
15117 .expect("resolve with dn_match=false");
15118 assert!(
15119 !params.dn_match,
15120 "resolve_tls_params must record dn_match=false when effective OFF"
15121 );
15122 }
15123
15124 #[test]
15125 fn async_cancel_handle_requests_break_and_reconciles_ready() -> Result<()> {
15126 let listener = TcpListener::bind("127.0.0.1:0").expect("bind local listener");
15127 let addr = listener.local_addr().expect("listener address");
15128 let server = thread::spawn(move || {
15129 let (mut socket, _) = listener.accept().expect("accept test client");
15130 socket
15131 .set_read_timeout(Some(Duration::from_secs(5)))
15132 .expect("set read timeout");
15133 let mut packet = [0u8; 11];
15134 socket.read_exact(&mut packet).expect("read marker packet");
15135 packet
15136 });
15137
15138 let runtime = build_io_runtime().expect("asupersync runtime");
15139 let recovery = Arc::new(SessionRecovery::new());
15140 let mut handle = runtime.block_on(async {
15141 let stream = TcpStream::connect(addr).await.expect("connect to listener");
15142 let (_read, write) = transport::plain_split(stream);
15143 CancelHandle {
15144 write: Arc::new(AsyncMutex::with_name("oracle_tcp_write_test", write)),
15145 recovery: Arc::clone(&recovery),
15146 }
15147 });
15148
15149 runtime.block_on(async {
15150 let cx = Cx::current()
15151 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
15152 handle.cancel(&cx).await
15153 })?;
15154
15155 let packet = server.join().expect("server thread joins");
15156 assert_eq!(
15157 packet,
15158 [
15159 0,
15160 0,
15161 0,
15162 11,
15163 TNS_PACKET_TYPE_MARKER,
15164 0,
15165 0,
15166 0,
15167 1,
15168 0,
15169 TNS_MARKER_TYPE_BREAK
15170 ]
15171 );
15172 assert_eq!(
15173 recovery.phase(),
15174 SessionRecoveryPhase::BreakSent,
15175 "CancelHandle::cancel(&Cx) requests cancellation without draining"
15176 );
15177 assert!(
15178 recovery.begin_pending_drain()?,
15179 "the connection owner must be able to adopt the pending cancel response"
15180 );
15181 recovery.finish_drain_ready();
15182 assert_eq!(
15183 recovery.phase(),
15184 SessionRecoveryPhase::Ready,
15185 "successful cancel-response drain reconciles the session to Ready"
15186 );
15187 Ok(())
15188 }
15189
15190 #[test]
15191 fn async_cancel_handle_owner_drain_reconciles_ready() -> Result<()> {
15192 const INFLIGHT_BODY: &[u8] = &[0xCA, 0xFE];
15193 const ERROR_BODY: &[u8] = &[0x04, 0x01, 0x0d];
15194 const FRESH_BODY: &[u8] = &[0x07, 0x05, 0x0c];
15195
15196 let listener = TcpListener::bind("127.0.0.1:0").expect("bind local listener");
15197 let addr = listener.local_addr().expect("listener address");
15198 let server = thread::spawn(move || {
15199 let (mut socket, _) = listener.accept().expect("accept test client");
15200 socket
15201 .set_read_timeout(Some(Duration::from_secs(5)))
15202 .expect("set read timeout");
15203 use std::io::Write as _;
15204
15205 assert_eq!(
15206 read_marker_type(&mut socket),
15207 TNS_MARKER_TYPE_BREAK,
15208 "CancelHandle must send exactly one BREAK request"
15209 );
15210 socket
15211 .write_all(&data_packet(INFLIGHT_BODY, true))
15212 .expect("write in-flight response");
15213 socket
15214 .write_all(&marker_packet(TNS_MARKER_TYPE_BREAK))
15215 .expect("write break-ack marker");
15216 assert_eq!(
15217 read_marker_type(&mut socket),
15218 TNS_MARKER_TYPE_RESET,
15219 "owner drain must answer the break marker with RESET"
15220 );
15221 socket
15222 .write_all(&marker_packet(TNS_MARKER_TYPE_RESET))
15223 .expect("write reset-confirm marker");
15224 socket
15225 .write_all(&data_packet(ERROR_BODY, true))
15226 .expect("write trailing cancel error packet");
15227 socket
15228 .write_all(&data_packet(FRESH_BODY, true))
15229 .expect("write fresh response");
15230 });
15231
15232 let runtime = build_io_runtime().expect("asupersync runtime");
15233 let next = runtime.block_on(async {
15234 let cx = Cx::current()
15235 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
15236 let stream = TcpStream::connect(addr).await.expect("connect to listener");
15237 let (read, write) = transport::plain_split(stream);
15238 let mut conn = loopback_connection(read, write);
15239 let mut handle = conn.cancel_handle()?;
15240
15241 handle.cancel(&cx).await?;
15242 assert_eq!(
15243 conn.core.recovery.phase(),
15244 SessionRecoveryPhase::BreakSent,
15245 "handle cancel only requests recovery"
15246 );
15247 conn.drain_cancel_response().await?;
15248 assert_eq!(
15249 conn.core.recovery.phase(),
15250 SessionRecoveryPhase::Ready,
15251 "owner drain reconciles a successful cancel response to Ready"
15252 );
15253 conn.core.read_data_response(&cx).await
15254 })?;
15255
15256 assert_eq!(
15257 next, FRESH_BODY,
15258 "after owner drain the next read must consume the fresh response"
15259 );
15260 server.join().expect("server thread joins");
15261 Ok(())
15262 }
15263
15264 #[test]
15265 fn async_cancel_handle_owner_drain_failure_marks_dead() -> Result<()> {
15266 let listener = TcpListener::bind("127.0.0.1:0").expect("bind local listener");
15267 let addr = listener.local_addr().expect("listener address");
15268 let server = thread::spawn(move || {
15269 let (mut socket, _) = listener.accept().expect("accept test client");
15270 socket
15271 .set_read_timeout(Some(Duration::from_secs(5)))
15272 .expect("set read timeout");
15273 assert_eq!(
15274 read_marker_type(&mut socket),
15275 TNS_MARKER_TYPE_BREAK,
15276 "CancelHandle must send the BREAK before the failed drain"
15277 );
15278 });
15280
15281 let runtime = build_io_runtime().expect("asupersync runtime");
15282 runtime.block_on(async {
15283 let cx = Cx::current()
15284 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
15285 let stream = TcpStream::connect(addr).await.expect("connect to listener");
15286 let (read, write) = transport::plain_split(stream);
15287 let mut conn = loopback_connection(read, write);
15288 let mut handle = conn.cancel_handle()?;
15289
15290 handle.cancel(&cx).await?;
15291 assert_eq!(conn.core.recovery.phase(), SessionRecoveryPhase::BreakSent);
15292 assert!(
15293 conn.drain_cancel_response().await.is_err(),
15294 "incomplete cancel response must fail owner drain"
15295 );
15296 assert!(
15297 conn.is_dead(),
15298 "failed owner drain marks the connection dead"
15299 );
15300 assert_eq!(conn.core.recovery.phase(), SessionRecoveryPhase::Dead);
15301 Ok::<(), Error>(())
15302 })?;
15303
15304 server.join().expect("server thread joins");
15305 Ok(())
15306 }
15307
15308 #[test]
15309 fn async_cancel_handle_does_not_send_duplicate_break_when_recovery_pending() -> Result<()> {
15310 let listener = TcpListener::bind("127.0.0.1:0").expect("bind local listener");
15311 let addr = listener.local_addr().expect("listener address");
15312 let server = thread::spawn(move || {
15313 let (mut socket, _) = listener.accept().expect("accept test client");
15314 socket
15315 .set_read_timeout(Some(Duration::from_secs(5)))
15316 .expect("set read timeout");
15317 let mut packet = [0u8; 11];
15318 socket.read_exact(&mut packet).expect("read first break");
15319 socket
15320 .set_read_timeout(Some(Duration::from_millis(200)))
15321 .expect("set short read timeout");
15322 let mut extra = [0u8; 1];
15323 let extra_read = socket.read_exact(&mut extra);
15324 assert!(
15325 matches!(
15326 extra_read.as_ref().map_err(std::io::Error::kind),
15327 Err(std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut)
15328 ),
15329 "duplicate BREAK check expected a read timeout, got {extra_read:?} with byte {:02x}",
15330 extra[0]
15331 );
15332 packet
15333 });
15334
15335 let runtime = build_io_runtime().expect("asupersync runtime");
15336 let recovery = Arc::new(SessionRecovery::new());
15337 let mut handle = runtime.block_on(async {
15338 let stream = TcpStream::connect(addr).await.expect("connect to listener");
15339 let (_read, write) = transport::plain_split(stream);
15340 CancelHandle {
15341 write: Arc::new(AsyncMutex::with_name("oracle_tcp_write_test", write)),
15342 recovery: Arc::clone(&recovery),
15343 }
15344 });
15345
15346 runtime.block_on(async {
15347 let cx = Cx::current()
15348 .ok_or_else(|| Error::Runtime("asupersync did not install an ambient Cx".into()))?;
15349 handle.cancel(&cx).await?;
15350 assert_eq!(recovery.phase(), SessionRecoveryPhase::BreakSent);
15351 handle.cancel(&cx).await?;
15352 assert!(
15353 recovery.begin_pending_drain()?,
15354 "test moves the pending cancel into Draining"
15355 );
15356 handle.cancel(&cx).await
15357 })?;
15358
15359 let packet = server.join().expect("server thread joins");
15360 assert_eq!(
15361 packet,
15362 [
15363 0,
15364 0,
15365 0,
15366 11,
15367 TNS_PACKET_TYPE_MARKER,
15368 0,
15369 0,
15370 0,
15371 1,
15372 0,
15373 TNS_MARKER_TYPE_BREAK
15374 ]
15375 );
15376 assert_eq!(recovery.phase(), SessionRecoveryPhase::Draining);
15377 Ok(())
15378 }
15379
15380 #[test]
15381 fn blocking_cancel_handle_sends_tns_break_marker() {
15382 let listener = TcpListener::bind("127.0.0.1:0").expect("bind local listener");
15383 let addr = listener.local_addr().expect("listener address");
15384 let server = thread::spawn(move || {
15385 let (mut socket, _) = listener.accept().expect("accept test client");
15386 socket
15387 .set_read_timeout(Some(Duration::from_secs(5)))
15388 .expect("set read timeout");
15389 let mut packet = [0u8; 11];
15390 socket.read_exact(&mut packet).expect("read marker packet");
15391 packet
15392 });
15393
15394 let runtime = build_io_runtime().expect("asupersync runtime");
15395 let recovery = Arc::new(SessionRecovery::new());
15396 let mut handle = runtime.block_on(async {
15397 let stream = TcpStream::connect(addr).await.expect("connect to listener");
15398 let (_read, write) = transport::plain_split(stream);
15399 CancelHandle {
15400 write: Arc::new(AsyncMutex::with_name("oracle_tcp_write_test", write)),
15401 recovery: Arc::clone(&recovery),
15402 }
15403 });
15404
15405 handle.cancel_blocking().expect("cancel marker write");
15406
15407 let packet = server.join().expect("server thread joins");
15408 assert_eq!(
15409 packet,
15410 [
15411 0,
15412 0,
15413 0,
15414 11,
15415 TNS_PACKET_TYPE_MARKER,
15416 0,
15417 0,
15418 0,
15419 1,
15420 0,
15421 TNS_MARKER_TYPE_BREAK
15422 ]
15423 );
15424 assert_eq!(recovery.phase(), SessionRecoveryPhase::BreakSent);
15425 }
15426
15427 const EOR_FLAG: u16 = oracledb_protocol::thin::TNS_DATA_FLAGS_END_OF_RESPONSE;
15437
15438 fn data_packet(message: &[u8], end_of_response: bool) -> Vec<u8> {
15442 let flags = if end_of_response { EOR_FLAG } else { 0 };
15443 encode_packet(
15444 TNS_PACKET_TYPE_DATA,
15445 0,
15446 Some(flags),
15447 message,
15448 PacketLengthWidth::Large32,
15449 )
15450 .expect("encode data packet")
15451 }
15452
15453 fn marker_packet(marker_type: u8) -> Vec<u8> {
15456 encode_packet(
15457 TNS_PACKET_TYPE_MARKER,
15458 0,
15459 None,
15460 &[1, 0, marker_type],
15461 PacketLengthWidth::Large32,
15462 )
15463 .expect("encode marker packet")
15464 }
15465
15466 #[derive(Debug)]
15467 struct ScriptedTransport;
15468
15469 impl WireTransport for ScriptedTransport {
15470 type Read = ScriptedRead;
15471 type Write = ScriptedWrite;
15472 }
15473
15474 #[derive(Debug)]
15475 struct ScriptedRead {
15476 state: Arc<std::sync::Mutex<ScriptedIoState>>,
15477 }
15478
15479 impl ScriptedRead {
15480 fn from_state(state: Arc<std::sync::Mutex<ScriptedIoState>>) -> Self {
15481 Self { state }
15482 }
15483 }
15484
15485 impl asupersync::io::AsyncRead for ScriptedRead {
15486 fn poll_read(
15487 self: std::pin::Pin<&mut Self>,
15488 cx: &mut std::task::Context<'_>,
15489 buf: &mut asupersync::io::ReadBuf<'_>,
15490 ) -> std::task::Poll<std::io::Result<()>> {
15491 let Ok(mut state) = self.state.lock() else {
15492 return std::task::Poll::Ready(Err(std::io::Error::other(
15493 "scripted transport: poisoned read state",
15494 )));
15495 };
15496
15497 loop {
15498 match state.read.front_mut() {
15499 Some(ReadAction::Bytes {
15500 bytes,
15501 offset,
15502 max_chunk,
15503 cancel_current_on_completion,
15504 }) => {
15505 if *offset >= bytes.len() {
15506 state.read.pop_front();
15507 continue;
15508 }
15509 let cap = max_chunk.unwrap_or(usize::MAX);
15510 let available = bytes.len() - *offset;
15511 let take = available.min(buf.remaining()).min(cap);
15512 if take == 0 {
15513 return std::task::Poll::Ready(Ok(()));
15514 }
15515 buf.put_slice(&bytes[*offset..*offset + take]);
15516 *offset += take;
15517 if *offset >= bytes.len() {
15518 let cancel_current_on_completion = *cancel_current_on_completion;
15519 state.read.pop_front();
15520 if cancel_current_on_completion {
15521 if let Some(cx) = Cx::current() {
15522 cx.cancel_fast(asupersync::CancelKind::User);
15523 }
15524 }
15525 }
15526 return std::task::Poll::Ready(Ok(()));
15527 }
15528 Some(ReadAction::Pending) => {
15529 state.read.pop_front();
15530 cx.waker().wake_by_ref();
15531 return std::task::Poll::Pending;
15532 }
15533 Some(ReadAction::PendingUntil(gate)) => {
15534 if gate.is_open() {
15535 state.read.pop_front();
15536 continue;
15537 }
15538 gate.register(cx.waker());
15539 return std::task::Poll::Pending;
15540 }
15541 Some(ReadAction::Eof) | None => return std::task::Poll::Ready(Ok(())),
15542 Some(ReadAction::Error(message)) => {
15543 let message = *message;
15544 state.read.pop_front();
15545 return std::task::Poll::Ready(Err(std::io::Error::other(message)));
15546 }
15547 Some(ReadAction::ErrorKind(kind, message)) => {
15548 let kind = *kind;
15549 let message = *message;
15550 state.read.pop_front();
15551 return std::task::Poll::Ready(Err(std::io::Error::new(kind, message)));
15552 }
15553 Some(ReadAction::AdvanceTime(duration)) => {
15554 let duration = *duration;
15555 state.read.pop_front();
15556 state.clock.advance(duration);
15557 }
15558 }
15559 }
15560 }
15561 }
15562
15563 #[derive(Debug)]
15564 struct ScriptedWrite {
15565 state: Arc<std::sync::Mutex<ScriptedIoState>>,
15566 }
15567
15568 impl ScriptedWrite {
15569 fn from_state(state: Arc<std::sync::Mutex<ScriptedIoState>>) -> Self {
15570 Self { state }
15571 }
15572 }
15573
15574 impl asupersync::io::AsyncWrite for ScriptedWrite {
15575 fn poll_write(
15576 self: std::pin::Pin<&mut Self>,
15577 cx: &mut std::task::Context<'_>,
15578 buf: &[u8],
15579 ) -> std::task::Poll<std::io::Result<usize>> {
15580 let Ok(mut state) = self.state.lock() else {
15581 return std::task::Poll::Ready(Err(std::io::Error::other(
15582 "scripted transport: poisoned write state",
15583 )));
15584 };
15585
15586 loop {
15587 match state.write.front_mut() {
15588 Some(WriteAction::Expect { .. }) => {
15589 let mut completed = None;
15590 let take = {
15591 let Some(WriteAction::Expect {
15592 bytes,
15593 offset,
15594 max_chunk,
15595 }) = state.write.front_mut()
15596 else {
15597 unreachable!("front action already matched Expect");
15598 };
15599 if *offset >= bytes.len() {
15600 state.write.pop_front();
15601 continue;
15602 }
15603 let cap = max_chunk.unwrap_or(usize::MAX);
15604 let available = bytes.len() - *offset;
15605 let take = available.min(buf.len()).min(cap);
15606 if take == 0 {
15607 return std::task::Poll::Ready(Ok(0));
15608 }
15609 if bytes[*offset..*offset + take] != buf[..take] {
15610 return std::task::Poll::Ready(Err(std::io::Error::other(
15611 "scripted transport: write mismatch",
15612 )));
15613 }
15614 *offset += take;
15615 if *offset >= bytes.len() {
15616 completed = Some(bytes.clone());
15617 }
15618 take
15619 };
15620 if let Some(bytes) = completed {
15621 state.note_write(&bytes);
15622 state.write.pop_front();
15623 }
15624 return std::task::Poll::Ready(Ok(take));
15625 }
15626 Some(WriteAction::Pending) => {
15627 state.write.pop_front();
15628 cx.waker().wake_by_ref();
15629 return std::task::Poll::Pending;
15630 }
15631 Some(WriteAction::Eof) => {
15632 state.write.pop_front();
15633 return std::task::Poll::Ready(Ok(0));
15634 }
15635 Some(WriteAction::Error(message)) => {
15636 let message = *message;
15637 state.write.pop_front();
15638 return std::task::Poll::Ready(Err(std::io::Error::other(message)));
15639 }
15640 Some(WriteAction::AdvanceTime(duration)) => {
15641 let duration = *duration;
15642 state.write.pop_front();
15643 state.clock.advance(duration);
15644 }
15645 None => {
15646 return std::task::Poll::Ready(Err(std::io::Error::other(
15647 "scripted transport: unexpected write",
15648 )));
15649 }
15650 }
15651 }
15652 }
15653
15654 fn poll_flush(
15655 self: std::pin::Pin<&mut Self>,
15656 _cx: &mut std::task::Context<'_>,
15657 ) -> std::task::Poll<std::io::Result<()>> {
15658 std::task::Poll::Ready(Ok(()))
15659 }
15660
15661 fn poll_shutdown(
15662 self: std::pin::Pin<&mut Self>,
15663 _cx: &mut std::task::Context<'_>,
15664 ) -> std::task::Poll<std::io::Result<()>> {
15665 std::task::Poll::Ready(Ok(()))
15666 }
15667 }
15668
15669 #[derive(Clone, Debug, Default)]
15670 struct ScriptedClock {
15671 nanos: Arc<std::sync::atomic::AtomicU64>,
15672 }
15673
15674 impl ScriptedClock {
15675 fn advance(&self, duration: Duration) {
15676 let nanos = u64::try_from(duration.as_nanos()).unwrap_or(u64::MAX);
15677 self.nanos.fetch_add(nanos, Ordering::Relaxed);
15678 }
15679
15680 fn elapsed(&self) -> Duration {
15681 Duration::from_nanos(self.nanos.load(Ordering::Relaxed))
15682 }
15683 }
15684
15685 #[derive(Clone, Default)]
15686 struct ScriptedGate {
15687 ready: Arc<AtomicBool>,
15688 waker: Arc<std::sync::Mutex<Option<Waker>>>,
15689 }
15690
15691 impl std::fmt::Debug for ScriptedGate {
15692 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15693 f.debug_struct("ScriptedGate")
15694 .field("ready", &self.ready.load(Ordering::Relaxed))
15695 .finish_non_exhaustive()
15696 }
15697 }
15698
15699 impl ScriptedGate {
15700 fn open(&self) {
15701 self.ready.store(true, Ordering::Release);
15702 if let Ok(mut waker) = self.waker.lock() {
15703 if let Some(waker) = waker.take() {
15704 waker.wake();
15705 }
15706 }
15707 }
15708
15709 fn is_open(&self) -> bool {
15710 self.ready.load(Ordering::Acquire)
15711 }
15712
15713 fn register(&self, waker: &Waker) {
15714 if let Ok(mut current) = self.waker.lock() {
15715 let replace = current
15716 .as_ref()
15717 .is_none_or(|registered| !registered.will_wake(waker));
15718 if replace {
15719 *current = Some(waker.clone());
15720 }
15721 }
15722 }
15723 }
15724
15725 #[derive(Debug)]
15726 struct ScriptedIoState {
15727 read: VecDeque<ReadAction>,
15728 write: VecDeque<WriteAction>,
15729 clock: ScriptedClock,
15730 break_writes: usize,
15731 reset_writes: usize,
15732 }
15733
15734 impl ScriptedIoState {
15735 fn new(read: Vec<ReadAction>, write: Vec<WriteAction>, clock: ScriptedClock) -> Self {
15736 Self {
15737 read: read.into(),
15738 write: write.into(),
15739 clock,
15740 break_writes: 0,
15741 reset_writes: 0,
15742 }
15743 }
15744
15745 fn is_consumed(&self) -> bool {
15746 self.read.is_empty() && self.write.is_empty()
15747 }
15748
15749 fn note_write(&mut self, bytes: &[u8]) {
15750 let break_marker = marker_packet(TNS_MARKER_TYPE_BREAK);
15751 let reset_marker = marker_packet(TNS_MARKER_TYPE_RESET);
15752 if bytes == break_marker.as_slice() {
15753 self.break_writes += 1;
15754 } else if bytes == reset_marker.as_slice() {
15755 self.reset_writes += 1;
15756 }
15757 }
15758 }
15759
15760 #[derive(Debug)]
15761 enum ReadAction {
15762 Bytes {
15763 bytes: Vec<u8>,
15764 offset: usize,
15765 max_chunk: Option<usize>,
15766 cancel_current_on_completion: bool,
15767 },
15768 Pending,
15769 PendingUntil(ScriptedGate),
15770 Eof,
15771 Error(&'static str),
15772 ErrorKind(std::io::ErrorKind, &'static str),
15773 AdvanceTime(Duration),
15774 }
15775
15776 impl ReadAction {
15777 fn bytes(bytes: Vec<u8>, max_chunk: Option<usize>) -> Self {
15778 Self::Bytes {
15779 bytes,
15780 offset: 0,
15781 max_chunk,
15782 cancel_current_on_completion: false,
15783 }
15784 }
15785
15786 fn bytes_then_cancel_current(bytes: Vec<u8>, max_chunk: Option<usize>) -> Self {
15787 Self::Bytes {
15788 bytes,
15789 offset: 0,
15790 max_chunk,
15791 cancel_current_on_completion: true,
15792 }
15793 }
15794 }
15795
15796 #[derive(Debug)]
15797 enum WriteAction {
15798 Expect {
15799 bytes: Vec<u8>,
15800 offset: usize,
15801 max_chunk: Option<usize>,
15802 },
15803 Pending,
15804 Eof,
15805 Error(&'static str),
15806 AdvanceTime(Duration),
15807 }
15808
15809 impl WriteAction {
15810 fn expect_bytes(bytes: Vec<u8>, max_chunk: Option<usize>) -> Self {
15811 Self::Expect {
15812 bytes,
15813 offset: 0,
15814 max_chunk,
15815 }
15816 }
15817 }
15818
15819 fn test_cx() -> Result<Cx> {
15820 Cx::current().ok_or_else(|| Error::Runtime("missing ambient Cx in test runtime".into()))
15821 }
15822
15823 #[test]
15824 fn split_connect_descriptor_uses_legacy16_data_framing_before_accept() -> Result<()> {
15825 let connect_data = format!(
15830 "(DESCRIPTION=(ADDRESS=(PROTOCOL=tcps)(HOST=adb.example.oraclecloud.com)(PORT=1522))\
15831 (CONNECT_DATA=(SERVICE_NAME={})))",
15832 "x".repeat(oracledb_protocol::thin::TNS_MAX_CONNECT_DATA)
15833 );
15834 assert!(
15835 !connect_data_fits_inline(&connect_data),
15836 "the descriptor must take the split-CONNECT path"
15837 );
15838
15839 let expected = encode_packet(
15840 TNS_PACKET_TYPE_DATA,
15841 0,
15842 Some(0),
15843 connect_data.as_bytes(),
15844 PacketLengthWidth::Legacy16,
15845 )?;
15846 assert_eq!(
15847 &expected[..2],
15848 &u16::try_from(expected.len())
15849 .expect("test packet fits a Legacy16 header")
15850 .to_be_bytes(),
15851 "the first two bytes carry the Legacy16 packet length"
15852 );
15853 assert_eq!(
15854 &expected[2..4],
15855 &[0, 0],
15856 "Legacy16 reserves header bytes 2..4; Large32 would put its length there"
15857 );
15858 assert_eq!(expected[4], TNS_PACKET_TYPE_DATA);
15859 assert_eq!(
15860 &expected[6..8],
15861 &[0, 0],
15862 "split CONNECT keeps DATA flags at zero"
15863 );
15864 let large32 = encode_packet(
15865 TNS_PACKET_TYPE_DATA,
15866 0,
15867 Some(0),
15868 connect_data.as_bytes(),
15869 PacketLengthWidth::Large32,
15870 )?;
15871 assert_ne!(
15872 &expected[..4],
15873 &large32[..4],
15874 "the split-CONNECT header must not be Large32"
15875 );
15876
15877 let state = Arc::new(std::sync::Mutex::new(ScriptedIoState::new(
15878 Vec::new(),
15879 vec![WriteAction::expect_bytes(expected, None)],
15880 ScriptedClock::default(),
15881 )));
15882 let core = ConnectionCore::<ScriptedTransport>::from_halves(
15883 ScriptedRead::from_state(Arc::clone(&state)),
15884 ScriptedWrite::from_state(Arc::clone(&state)),
15885 "split_connect_legacy16_write",
15886 );
15887 let runtime = build_io_runtime()?;
15888 runtime.block_on(async {
15889 let cx = test_cx()?;
15890 core.send_connect_data_packet(&cx, connect_data.as_bytes(), 8192)
15891 .await
15892 })?;
15893 assert!(
15894 state
15895 .lock()
15896 .map_err(|_| Error::Runtime("scripted transport state lock poisoned".into()))?
15897 .is_consumed(),
15898 "the pre-ACCEPT split descriptor must be written as the exact Legacy16 DATA packet"
15899 );
15900 Ok(())
15901 }
15902
15903 #[test]
15904 fn connection_core_routes_connect_execute_fetch_over_scripted_transport() -> Result<()> {
15905 const EXECUTE_BODY: &[u8] = b"scripted execute payload";
15906 const FETCH_BODY: &[u8] = b"scripted fetch payload";
15907 const EXECUTE_RESPONSE: &[u8] = b"scripted execute response";
15908 const FETCH_RESPONSE: &[u8] = b"scripted fetch response";
15909
15910 let connect_packet = encode_packet(
15911 TNS_PACKET_TYPE_CONNECT,
15912 0,
15913 None,
15914 b"SCRIPTED-CONNECT",
15915 PacketLengthWidth::Legacy16,
15916 )?;
15917 let accept_packet = encode_packet(
15918 TNS_PACKET_TYPE_ACCEPT,
15919 0,
15920 None,
15921 b"SCRIPTED-ACCEPT",
15922 PacketLengthWidth::Legacy16,
15923 )?;
15924 let execute_packet = encode_packet(
15925 TNS_PACKET_TYPE_DATA,
15926 0,
15927 Some(0),
15928 EXECUTE_BODY,
15929 PacketLengthWidth::Large32,
15930 )?;
15931 let fetch_packet = encode_packet(
15932 TNS_PACKET_TYPE_DATA,
15933 0,
15934 Some(0),
15935 FETCH_BODY,
15936 PacketLengthWidth::Large32,
15937 )?;
15938
15939 let script = Arc::new(std::sync::Mutex::new(ScriptedIoState::new(
15940 vec![
15941 ReadAction::bytes(accept_packet, None),
15942 ReadAction::bytes(data_packet(EXECUTE_RESPONSE, true), None),
15943 ReadAction::bytes(data_packet(FETCH_RESPONSE, true), None),
15944 ],
15945 vec![
15946 WriteAction::expect_bytes(connect_packet.clone(), None),
15947 WriteAction::expect_bytes(execute_packet, None),
15948 WriteAction::expect_bytes(fetch_packet, None),
15949 ],
15950 ScriptedClock::default(),
15951 )));
15952 let mut core = ConnectionCore::<ScriptedTransport>::from_halves(
15953 ScriptedRead::from_state(Arc::clone(&script)),
15954 ScriptedWrite::from_state(Arc::clone(&script)),
15955 "scripted_core_write",
15956 );
15957
15958 let runtime = build_io_runtime()?;
15959 runtime.block_on(async {
15960 let cx = test_cx()?;
15961 core.write_all(&cx, &connect_packet).await?;
15962 let accept = core.read_packet(PacketLengthWidth::Legacy16).await?;
15963 assert_eq!(accept.packet_type, TNS_PACKET_TYPE_ACCEPT);
15964 assert_eq!(accept.payload, b"SCRIPTED-ACCEPT");
15965
15966 core.send_data_packet(&cx, EXECUTE_BODY, 8192).await?;
15967 let execute_response = core.read_data_response(&cx).await?;
15968 assert_eq!(execute_response, EXECUTE_RESPONSE);
15969
15970 core.send_data_packet(&cx, FETCH_BODY, 8192).await?;
15971 let fetch_response = core.read_data_response(&cx).await?;
15972 assert_eq!(fetch_response, FETCH_RESPONSE);
15973 Ok::<_, Error>(())
15974 })?;
15975
15976 let state = script
15977 .lock()
15978 .map_err(|_| Error::Runtime("scripted I/O state lock poisoned".into()))?;
15979 assert!(
15980 state.is_consumed(),
15981 "scripted core must perform exactly the expected connect/execute/fetch I/O"
15982 );
15983 Ok(())
15984 }
15985
15986 #[test]
15987 fn classic_token_auth_negotiates_then_sends_phase_two_over_scripted_transport() -> Result<()> {
15988 const USER: &str = "iam_user";
15989 const TOKEN: &str = "not-a-real-iam-token";
15990 const DRIVER_NAME: &str = "rust-oracledb";
15991 const CONNECT_STRING: &str = "adb.example.oraclecloud.com/service";
15992
15993 let protocol_payload = build_protocol_negotiation_payload()?;
15994 let data_types_payload = build_data_types_payload()?;
15995 let token_payload = build_auth_phase_two_token_payload_with_pop_and_proxy(
15996 USER,
15997 TOKEN,
15998 DRIVER_NAME,
15999 PYTHON_ORACLEDB_COMPAT_VERSION_NUM,
16000 CONNECT_STRING,
16001 None,
16002 None,
16003 None,
16004 )?;
16005 let expected_writes = [protocol_payload, data_types_payload, token_payload]
16006 .into_iter()
16007 .map(|payload| {
16008 encode_packet(
16009 TNS_PACKET_TYPE_DATA,
16010 0,
16011 Some(0),
16012 &payload,
16013 PacketLengthWidth::Large32,
16014 )
16015 .map_err(Error::from)
16016 })
16017 .collect::<Result<Vec<_>>>()?;
16018
16019 let classic_status =
16023 data_packet(&[oracledb_protocol::thin::TNS_MSG_TYPE_STATUS, 0, 0], false);
16024 let script = Arc::new(std::sync::Mutex::new(ScriptedIoState::new(
16025 vec![
16026 ReadAction::bytes(classic_status.clone(), None),
16027 ReadAction::bytes(classic_status.clone(), None),
16028 ReadAction::bytes(classic_status, None),
16029 ],
16030 expected_writes
16031 .into_iter()
16032 .map(|packet| WriteAction::expect_bytes(packet, None))
16033 .collect(),
16034 ScriptedClock::default(),
16035 )));
16036 let mut core = ConnectionCore::<ScriptedTransport>::from_halves(
16037 ScriptedRead::from_state(Arc::clone(&script)),
16038 ScriptedWrite::from_state(Arc::clone(&script)),
16039 "classic_token_auth_write",
16040 );
16041
16042 let runtime = build_io_runtime()?;
16043 runtime.block_on(async {
16044 let cx = test_cx()?;
16045 let (auth, capabilities) = core
16046 .authenticate_classic_token(
16047 &cx,
16048 TokenAuthentication {
16049 user: USER,
16050 token: TOKEN,
16051 driver_name: DRIVER_NAME,
16052 version_num: PYTHON_ORACLEDB_COMPAT_VERSION_NUM,
16053 connect_string: CONNECT_STRING,
16054 edition: None,
16055 pop: None,
16056 proxy_user: None,
16057 },
16058 8192,
16059 )
16060 .await?;
16061 assert!(auth.session_data.is_empty());
16062 assert_eq!(capabilities, ClientCapabilities::default());
16063 Ok::<_, Error>(())
16064 })?;
16065
16066 assert!(
16067 script
16068 .lock()
16069 .map_err(|_| Error::Runtime("scripted I/O state lock poisoned".into()))?
16070 .is_consumed(),
16071 "classic token auth must send protocol negotiation, data types, then AUTH_TOKEN phase two"
16072 );
16073 Ok(())
16074 }
16075
16076 #[test]
16077 fn scripted_transport_replays_short_pending_and_virtual_time() -> Result<()> {
16078 const EXECUTE_BODY: &[u8] = b"fault-matrix execute payload";
16079 const EXECUTE_RESPONSE: &[u8] = b"fault-matrix execute response";
16080
16081 let connect_packet = encode_packet(
16082 TNS_PACKET_TYPE_CONNECT,
16083 0,
16084 None,
16085 b"FAULT-MATRIX-CONNECT",
16086 PacketLengthWidth::Legacy16,
16087 )?;
16088 let accept_packet = encode_packet(
16089 TNS_PACKET_TYPE_ACCEPT,
16090 0,
16091 None,
16092 b"FAULT-MATRIX-ACCEPT",
16093 PacketLengthWidth::Legacy16,
16094 )?;
16095 let execute_packet = encode_packet(
16096 TNS_PACKET_TYPE_DATA,
16097 0,
16098 Some(0),
16099 EXECUTE_BODY,
16100 PacketLengthWidth::Large32,
16101 )?;
16102 let execute_response_packet = data_packet(EXECUTE_RESPONSE, true);
16103
16104 let clock = ScriptedClock::default();
16105 let script = Arc::new(std::sync::Mutex::new(ScriptedIoState::new(
16106 vec![
16107 ReadAction::Pending,
16108 ReadAction::AdvanceTime(Duration::from_millis(7)),
16109 ReadAction::bytes(accept_packet, Some(3)),
16110 ReadAction::Pending,
16111 ReadAction::AdvanceTime(Duration::from_millis(11)),
16112 ReadAction::bytes(execute_response_packet, Some(2)),
16113 ],
16114 vec![
16115 WriteAction::Pending,
16116 WriteAction::AdvanceTime(Duration::from_millis(5)),
16117 WriteAction::expect_bytes(connect_packet.clone(), Some(2)),
16118 WriteAction::Pending,
16119 WriteAction::AdvanceTime(Duration::from_millis(13)),
16120 WriteAction::expect_bytes(execute_packet, Some(3)),
16121 ],
16122 clock.clone(),
16123 )));
16124 let mut core = ConnectionCore::<ScriptedTransport>::from_halves(
16125 ScriptedRead::from_state(Arc::clone(&script)),
16126 ScriptedWrite::from_state(Arc::clone(&script)),
16127 "scripted_fault_matrix_write",
16128 );
16129
16130 let runtime = build_io_runtime()?;
16131 runtime.block_on(async {
16132 let cx = test_cx()?;
16133 core.write_all(&cx, &connect_packet).await?;
16134 let accept = core.read_packet(PacketLengthWidth::Legacy16).await?;
16135 assert_eq!(accept.packet_type, TNS_PACKET_TYPE_ACCEPT);
16136 assert_eq!(accept.payload, b"FAULT-MATRIX-ACCEPT");
16137
16138 core.send_data_packet(&cx, EXECUTE_BODY, 8192).await?;
16139 let execute_response = core.read_data_response(&cx).await?;
16140 assert_eq!(execute_response, EXECUTE_RESPONSE);
16141 Ok::<_, Error>(())
16142 })?;
16143
16144 assert_eq!(
16145 clock.elapsed(),
16146 Duration::from_millis(36),
16147 "virtual time advances are deterministic and do not require wall-clock sleeps"
16148 );
16149 let state = script
16150 .lock()
16151 .map_err(|_| Error::Runtime("scripted I/O state lock poisoned".into()))?;
16152 assert!(
16153 state.is_consumed(),
16154 "scripted fault matrix must consume every read/write step"
16155 );
16156 Ok(())
16157 }
16158
16159 const DPOR_SATURATION_WINDOW: usize = 1;
16160 const DPOR_WIRE_SEED: u64 = 0xE3_E4_D0_00;
16161 const DPOR_WIRE_MAX_ITERS: usize = 96;
16162 const DPOR_WIRE_TIMEOUT_MS: u32 = 1;
16163 fn dpor_wire_recovery_timeout() -> Duration {
16164 Duration::from_secs(1)
16165 }
16166
16167 #[derive(Clone, Copy, Debug)]
16168 enum DporWireMode {
16169 UserCancel,
16170 Timeout,
16171 }
16172
16173 fn dpor_wire_seed(mode: DporWireMode) -> u64 {
16174 DPOR_WIRE_SEED
16175 + match mode {
16176 DporWireMode::UserCancel => 0,
16177 DporWireMode::Timeout => 1,
16178 }
16179 }
16180
16181 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
16182 enum DporWireResultKind {
16183 Cancelled,
16184 CallTimeout,
16185 }
16186
16187 #[derive(Debug)]
16188 struct DporWireObservation {
16189 result: DporWireResultKind,
16190 phase: SessionRecoveryPhase,
16191 break_writes: usize,
16192 reset_writes: usize,
16193 script_consumed: bool,
16194 }
16195
16196 async fn dpor_read_until_cancel_gate(
16197 core: &mut ConnectionCore<ScriptedTransport>,
16198 cx: &Cx,
16199 cancel_gate: &ScriptedGate,
16200 ) -> Result<Vec<u8>> {
16201 let recovery = Arc::clone(&core.recovery);
16202 let read = async {
16203 let mut guard = CancelDrainGuard::arm(recovery)?;
16204 let response = core.read_data_response(cx).await;
16205 if response.is_ok() {
16206 guard.disarm();
16207 }
16208 response
16209 };
16210 let cancel = poll_fn(|task_cx| {
16211 if cancel_gate.is_open() {
16212 Poll::Ready(())
16213 } else {
16214 cancel_gate.register(task_cx.waker());
16215 Poll::Pending
16216 }
16217 });
16218 let mut read = pin!(read);
16219 let mut cancel = pin!(cancel);
16220
16221 poll_fn(|task_cx| {
16222 if cancel_gate.is_open() {
16223 return Poll::Ready(Err(Error::Cancelled));
16224 }
16225 if let Poll::Ready(result) = read.as_mut().poll(task_cx) {
16226 return Poll::Ready(result);
16227 }
16228 if let Poll::Ready(()) = cancel.as_mut().poll(task_cx) {
16229 return Poll::Ready(Err(Error::Cancelled));
16230 }
16231 Poll::Pending
16232 })
16233 .await
16234 }
16235
16236 async fn dpor_read_until_timeout_gate(
16237 core: &mut ConnectionCore<ScriptedTransport>,
16238 cx: &Cx,
16239 timeout_gate: &ScriptedGate,
16240 ) -> Result<Vec<u8>> {
16241 let recovery = Arc::clone(&core.recovery);
16242 let read = async {
16243 let mut guard = CancelDrainGuard::arm(recovery)?;
16244 let response = core.read_data_response(cx).await;
16245 if response.is_ok() {
16246 guard.disarm();
16247 }
16248 response
16249 };
16250 let timeout = poll_fn(|task_cx| {
16251 if timeout_gate.is_open() {
16252 Poll::Ready(())
16253 } else {
16254 timeout_gate.register(task_cx.waker());
16255 Poll::Pending
16256 }
16257 });
16258 let mut read = pin!(read);
16259 let mut timeout = pin!(timeout);
16260
16261 poll_fn(|task_cx| {
16262 if timeout_gate.is_open() {
16263 return Poll::Ready(Err(Error::CallTimeout(DPOR_WIRE_TIMEOUT_MS)));
16264 }
16265 if let Poll::Ready(result) = read.as_mut().poll(task_cx) {
16266 return Poll::Ready(result);
16267 }
16268 if let Poll::Ready(()) = timeout.as_mut().poll(task_cx) {
16269 return Poll::Ready(Err(Error::CallTimeout(DPOR_WIRE_TIMEOUT_MS)));
16270 }
16271 Poll::Pending
16272 })
16273 .await
16274 }
16275
16276 fn dpor_wire_script(
16277 gate: ScriptedGate,
16278 execute_packet: Vec<u8>,
16279 ) -> Arc<std::sync::Mutex<ScriptedIoState>> {
16280 const INFLIGHT_BODY: &[u8] = b"dpor in-flight response";
16281 const ERROR_BODY: &[u8] = &[0x04, 0x01, 0x0d];
16282
16283 Arc::new(std::sync::Mutex::new(ScriptedIoState::new(
16284 vec![
16285 ReadAction::PendingUntil(gate),
16286 ReadAction::bytes(data_packet(INFLIGHT_BODY, true), Some(3)),
16287 ReadAction::Pending,
16288 ReadAction::bytes(marker_packet(TNS_MARKER_TYPE_BREAK), Some(2)),
16289 ReadAction::Pending,
16290 ReadAction::bytes(marker_packet(TNS_MARKER_TYPE_RESET), Some(2)),
16291 ReadAction::Pending,
16292 ReadAction::bytes(data_packet(ERROR_BODY, true), Some(2)),
16293 ],
16294 vec![
16295 WriteAction::Pending,
16296 WriteAction::expect_bytes(execute_packet, Some(3)),
16297 WriteAction::Pending,
16298 WriteAction::expect_bytes(marker_packet(TNS_MARKER_TYPE_BREAK), Some(2)),
16299 WriteAction::Pending,
16300 WriteAction::expect_bytes(marker_packet(TNS_MARKER_TYPE_RESET), Some(2)),
16301 ],
16302 ScriptedClock::default(),
16303 )))
16304 }
16305
16306 async fn run_dpor_wire_operation(
16307 mode: DporWireMode,
16308 gate: ScriptedGate,
16309 script: Arc<std::sync::Mutex<ScriptedIoState>>,
16310 execute_body: &'static [u8],
16311 ) -> Result<DporWireObservation> {
16312 let cx = Cx::current().expect("LabRuntime task should install an ambient Cx");
16313 let mut core = ConnectionCore::<ScriptedTransport>::from_halves(
16314 ScriptedRead::from_state(Arc::clone(&script)),
16315 ScriptedWrite::from_state(Arc::clone(&script)),
16316 "dpor_wire_core_write",
16317 );
16318 core.send_data_packet(&cx, execute_body, 8192).await?;
16319
16320 let result = match mode {
16321 DporWireMode::UserCancel => dpor_read_until_cancel_gate(&mut core, &cx, &gate).await,
16322 DporWireMode::Timeout => dpor_read_until_timeout_gate(&mut core, &cx, &gate).await,
16323 };
16324 let result = match result {
16325 Ok(payload) => {
16326 return Err(Error::Runtime(format!(
16327 "DPOR wire race unexpectedly completed normally with payload {payload:?}"
16328 )));
16329 }
16330 Err(Error::Cancelled) => {
16331 core.recovery.begin_drain_after_break()?;
16332 core.cancel_and_drain_wire(dpor_wire_recovery_timeout())?;
16333 core.recovery.finish_drain_ready();
16334 DporWireResultKind::Cancelled
16335 }
16336 Err(Error::CallTimeout(_)) => {
16337 core.recovery.begin_drain_after_break()?;
16338 core.break_and_drain_wire(dpor_wire_recovery_timeout())?;
16339 core.recovery.finish_drain_ready();
16340 DporWireResultKind::CallTimeout
16341 }
16342 Err(err) => return Err(err),
16343 };
16344
16345 let state = script
16346 .lock()
16347 .map_err(|_| Error::Runtime("scripted DPOR wire state lock poisoned".into()))?;
16348 Ok(DporWireObservation {
16349 result,
16350 phase: core.recovery.phase(),
16351 break_writes: state.break_writes,
16352 reset_writes: state.reset_writes,
16353 script_consumed: state.is_consumed(),
16354 })
16355 }
16356
16357 fn explore_dpor_wire_mode(mode: DporWireMode) -> asupersync::lab::ExplorationReport {
16358 const EXECUTE_BODY: &[u8] = b"dpor execute payload";
16359 let execute_packet = encode_packet(
16360 TNS_PACKET_TYPE_DATA,
16361 0,
16362 Some(0),
16363 EXECUTE_BODY,
16364 PacketLengthWidth::Large32,
16365 )
16366 .expect("encode DPOR execute packet");
16367
16368 let mut explorer = DporExplorer::new(
16369 ExplorerConfig::new(dpor_wire_seed(mode), DPOR_WIRE_MAX_ITERS).max_steps(100_000),
16370 );
16371 explorer.explore(|runtime: &mut LabRuntime| {
16372 let order = Arc::new(std::sync::Mutex::new(Vec::new()));
16378 let root = runtime.state.create_root_region(Budget::INFINITE);
16379
16380 let operation_order = Arc::clone(&order);
16381 let (operation, _operation_handle) = runtime
16382 .state
16383 .create_task(root, Budget::INFINITE, async move {
16384 operation_order
16385 .lock()
16386 .expect("record DPOR wire operation ordering")
16387 .push("operation");
16388 })
16389 .expect("create DPOR wire operation task");
16390 runtime.scheduler.lock().schedule(operation, 0);
16391
16392 let interrupt_order = Arc::clone(&order);
16393 let (interrupt, _interrupt_handle) = runtime
16394 .state
16395 .create_task(root, Budget::INFINITE, async move {
16396 interrupt_order
16397 .lock()
16398 .expect("record DPOR wire interrupt ordering")
16399 .push("interrupt");
16400 })
16401 .expect("create DPOR wire interrupt task");
16402 runtime.scheduler.lock().schedule(interrupt, 0);
16403 runtime.run_until_quiescent();
16404 assert!(
16405 runtime.is_quiescent(),
16406 "DPOR wire ordering model did not quiesce"
16407 );
16408
16409 let observed_order = order.lock().expect("read DPOR wire ordering").clone();
16410 assert_eq!(
16411 observed_order.len(),
16412 2,
16413 "DPOR wire ordering should include operation and interrupt"
16414 );
16415
16416 let replay_gate = ScriptedGate::default();
16417 replay_gate.open();
16418 let script = dpor_wire_script(replay_gate.clone(), execute_packet.clone());
16419 let io_runtime = build_io_runtime().expect("asupersync runtime for DPOR wire replay");
16420 let observed = io_runtime
16421 .block_on(run_dpor_wire_operation(
16422 mode,
16423 replay_gate,
16424 script,
16425 EXECUTE_BODY,
16426 ))
16427 .expect("DPOR wire operation should not fail");
16428 let expected = match mode {
16429 DporWireMode::UserCancel => DporWireResultKind::Cancelled,
16430 DporWireMode::Timeout => DporWireResultKind::CallTimeout,
16431 };
16432 assert_eq!(
16433 observed.result, expected,
16434 "delivered cancel/timeout mapped to the wrong public error"
16435 );
16436 assert_eq!(
16437 observed.phase,
16438 SessionRecoveryPhase::Ready,
16439 "wire recovery must finish at a clean Ready boundary"
16440 );
16441 assert_eq!(observed.break_writes, 1, "exactly one BREAK is required");
16442 assert_eq!(observed.reset_writes, 1, "exactly one RESET is required");
16443 assert!(
16444 observed.script_consumed,
16445 "wire recovery must consume the whole scripted break response"
16446 );
16447 })
16448 }
16449
16450 #[test]
16451 fn dpor_wire_cancel_and_timeout_recovery_saturates() {
16452 for mode in [DporWireMode::UserCancel, DporWireMode::Timeout] {
16453 let report = explore_dpor_wire_mode(mode);
16454 eprintln!(
16455 "[dpor-wire] mode={mode:?} seed={} max_iters={} runs={} classes={} saturated={}",
16456 dpor_wire_seed(mode),
16457 DPOR_WIRE_MAX_ITERS,
16458 report.total_runs,
16459 report.unique_classes,
16460 report.coverage.is_saturated(DPOR_SATURATION_WINDOW)
16461 );
16462 assert!(
16463 !report.has_violations(),
16464 "DPOR wire {mode:?} found violations at seeds {:?}",
16465 report.violation_seeds()
16466 );
16467 assert!(
16468 report.total_runs == DPOR_WIRE_MAX_ITERS,
16469 "DPOR wire fallback seed space did not complete for {mode:?}: runs={}, classes={}, new={}",
16470 report.total_runs,
16471 report.unique_classes,
16472 report.coverage.new_class_discoveries
16473 );
16474 }
16475 }
16476
16477 #[test]
16478 fn flush_out_binds_observes_cancel_before_follow_up_round_trip() -> Result<()> {
16479 let first_response = data_packet(&[TNS_MSG_TYPE_FLUSH_OUT_BINDS], true);
16480 let unread_response = data_packet(b"must-not-read-after-cancel", true);
16481 let script = Arc::new(std::sync::Mutex::new(ScriptedIoState::new(
16482 vec![
16483 ReadAction::bytes_then_cancel_current(first_response, None),
16484 ReadAction::bytes(unread_response, None),
16485 ],
16486 Vec::new(),
16487 ScriptedClock::default(),
16488 )));
16489
16490 let runtime = build_io_runtime()?;
16491 let err = runtime.block_on(async {
16492 let cx = test_cx()?;
16493 let mut read = ScriptedRead::from_state(Arc::clone(&script));
16494 let write = Arc::new(AsyncMutex::with_name(
16495 "flush_cancel_checkpoint_test_write",
16496 ScriptedWrite::from_state(Arc::clone(&script)),
16497 ));
16498 let err = read_data_response_flushing_out_binds(&mut read, &cx, &write, 8192)
16499 .await
16500 .expect_err("cancel checkpoint must stop before FLUSH_OUT_BINDS follow-up");
16501 Ok::<_, Error>(err)
16502 })?;
16503
16504 assert!(
16507 matches!(&err, Error::Cancelled),
16508 "flush continuation should stop on the cancellation checkpoint with a distinct cancel error, got {err:?}"
16509 );
16510 assert_eq!(err.kind(), ErrorKind::Cancel);
16511 let state = script
16512 .lock()
16513 .map_err(|_| Error::Runtime("scripted I/O state lock poisoned".into()))?;
16514 assert_eq!(
16515 state.read.len(),
16516 1,
16517 "the next response packet must not be read after cancellation"
16518 );
16519 assert!(
16520 state.write.is_empty(),
16521 "the FLUSH_OUT_BINDS follow-up must not be sent after cancellation"
16522 );
16523 Ok(())
16524 }
16525
16526 #[test]
16527 fn pending_cx_cancel_is_observed_before_fetch_continuation_write() -> Result<()> {
16528 let listener = TcpListener::bind("127.0.0.1:0")?;
16529 let addr = listener.local_addr()?;
16530 let server = thread::spawn(move || -> std::io::Result<Option<u8>> {
16531 let (mut socket, _) = listener.accept()?;
16532 socket.set_read_timeout(Some(Duration::from_millis(300)))?;
16533 let mut first_byte = [0u8; 1];
16534 match socket.read(&mut first_byte) {
16535 Ok(0) => Ok(None),
16536 Ok(_) => Ok(Some(first_byte[0])),
16537 Err(err)
16538 if matches!(
16539 err.kind(),
16540 std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut
16541 ) =>
16542 {
16543 Ok(None)
16544 }
16545 Err(err) => Err(err),
16546 }
16547 });
16548
16549 let runtime = build_io_runtime()?;
16550 runtime.block_on(async {
16551 let cx = test_cx()?;
16552 let stream = TcpStream::connect(addr).await?;
16553 let (read, write) = transport::plain_split(stream);
16554 let mut connection = loopback_connection(read, write);
16555 let before_seq = connection.ttc_seq_num;
16556
16557 cx.cancel_fast(asupersync::CancelKind::User);
16558 let err = connection
16559 .fetch_rows_request(&cx, 42, 10)
16560 .await
16561 .expect_err("fetch continuation must checkpoint before writing");
16562 assert!(
16565 matches!(&err, Error::Cancelled),
16566 "fetch continuation should stop on the cancellation checkpoint with a distinct cancel error, got {err:?}"
16567 );
16568 assert_eq!(err.kind(), ErrorKind::Cancel);
16569 assert_eq!(
16570 connection.ttc_seq_num, before_seq,
16571 "checkpoint must run before allocating the next TTC sequence number"
16572 );
16573 assert_eq!(
16574 connection.core.recovery.phase(),
16575 SessionRecoveryPhase::Ready,
16576 "checkpoint failure must not arm the recovery state machine"
16577 );
16578 Ok::<_, Error>(())
16579 })?;
16580
16581 let received = server.join().expect("server thread joins")?;
16582 assert_eq!(
16583 received, None,
16584 "cancelled fetch continuation must not write a FETCH packet"
16585 );
16586 Ok(())
16587 }
16588
16589 #[test]
16590 fn fetch_rows_ref_drop_mid_read_arms_break_recovery() -> Result<()> {
16591 let listener = TcpListener::bind("127.0.0.1:0")?;
16592 let addr = listener.local_addr()?;
16593 let (packet_tx, packet_rx) = std::sync::mpsc::channel();
16594 let (release_tx, release_rx) = std::sync::mpsc::channel();
16595 let server = thread::spawn(move || -> std::io::Result<()> {
16596 let (mut socket, _) = listener.accept()?;
16597 socket.set_read_timeout(Some(Duration::from_secs(2)))?;
16598 let mut buf = [0u8; 1024];
16599 let read = socket.read(&mut buf)?;
16600 packet_tx
16601 .send(read)
16602 .expect("test packet notification receiver is alive");
16603 let _ = release_rx.recv_timeout(Duration::from_secs(2));
16604 Ok(())
16605 });
16606
16607 let runtime = build_io_runtime()?;
16608 runtime.block_on(async {
16609 let cx = test_cx()?;
16610 let stream = TcpStream::connect(addr).await?;
16611 let (read, write) = transport::plain_split(stream);
16612 let mut connection = loopback_connection(read, write);
16613
16614 {
16615 let mut fetch = pin!(connection.fetch_rows_ref(&cx, 42, 10, None));
16616 let first_poll = poll_fn(|task_cx| Poll::Ready(fetch.as_mut().poll(task_cx))).await;
16617 assert!(
16618 matches!(first_poll, Poll::Pending),
16619 "fetch_rows_ref must wait for the server response"
16620 );
16621 let packet_len = packet_rx
16622 .recv_timeout(Duration::from_secs(2))
16623 .expect("fetch request should be sent before the response read waits");
16624 assert!(packet_len > 0, "fetch request packet must not be empty");
16625
16626 let second_poll =
16627 poll_fn(|task_cx| Poll::Ready(fetch.as_mut().poll(task_cx))).await;
16628 assert!(
16629 matches!(second_poll, Poll::Pending),
16630 "fetch_rows_ref response read should still be pending"
16631 );
16632 }
16633
16634 assert_eq!(
16635 connection.core.recovery.phase(),
16636 SessionRecoveryPhase::BreakSent,
16637 "dropping fetch_rows_ref mid-read must arm break/drain recovery"
16638 );
16639 release_tx
16640 .send(())
16641 .expect("test server release receiver is alive");
16642 Ok::<_, Error>(())
16643 })?;
16644
16645 server
16646 .join()
16647 .expect("server thread joins")
16648 .map_err(Error::Io)?;
16649 Ok(())
16650 }
16651
16652 #[test]
16653 fn in_flight_context_cancel_does_not_leak_interrupted_io() -> Result<()> {
16654 let listener = TcpListener::bind("127.0.0.1:0")?;
16655 let addr = listener.local_addr()?;
16656 let (request_seen_tx, request_seen_rx) = std::sync::mpsc::channel();
16657 let (release_tx, release_rx) = std::sync::mpsc::channel();
16658 let (cx_tx, cx_rx) = std::sync::mpsc::channel::<Cx>();
16659 let server = thread::spawn(move || -> std::io::Result<()> {
16660 let (mut socket, _) = listener.accept()?;
16661 socket.set_read_timeout(Some(Duration::from_secs(2)))?;
16662 assert_eq!(
16663 read_one_wire_packet(&mut socket),
16664 TNS_PACKET_TYPE_DATA,
16665 "COMMIT request must reach the server before cancellation"
16666 );
16667 request_seen_tx
16668 .send(())
16669 .expect("client cancellation task is alive");
16670 let _ = release_rx.recv_timeout(Duration::from_secs(2));
16671 Ok(())
16672 });
16673
16674 let runtime = build_io_runtime()?;
16675 let canceller = thread::spawn(move || -> Result<()> {
16676 let cancel_cx = cx_rx
16677 .recv_timeout(Duration::from_secs(2))
16678 .map_err(|_| Error::Runtime("COMMIT task never exposed its Cx".into()))?;
16679 request_seen_rx
16680 .recv_timeout(Duration::from_secs(2))
16681 .map_err(|_| Error::Runtime("server never observed COMMIT request".into()))?;
16682 cancel_cx.cancel_fast(CancelKind::User);
16683 Ok(())
16684 });
16685 let task = runtime.handle().spawn(async move {
16689 let cx = test_cx()?;
16690 cx_tx
16691 .send(cx.clone())
16692 .map_err(|_| Error::Runtime("cancellation test coordinator exited".into()))?;
16693 let stream = TcpStream::connect(addr).await?;
16694 let (read, write) = transport::plain_split(stream);
16695 let mut connection = loopback_connection(read, write);
16696
16697 let err = connection
16698 .commit(&cx)
16699 .await
16700 .expect_err("the cancelled in-flight COMMIT must not complete");
16701 Ok::<_, Error>((err, connection.core.recovery.phase()))
16702 });
16703 let (err, phase) = runtime.block_on(task)?;
16704 canceller.join().expect("cancellation thread joins")?;
16705
16706 let _ = release_tx.send(());
16707 server
16708 .join()
16709 .expect("server thread joins")
16710 .map_err(Error::Io)?;
16711
16712 assert!(
16713 matches!(&err, Error::Cancelled),
16714 "a mid-read user cancellation must surface the typed Error::Cancelled, not {err:?}"
16715 );
16716 assert_eq!(err.kind(), ErrorKind::Cancel);
16717 assert_eq!(
16718 phase,
16719 SessionRecoveryPhase::BreakSent,
16720 "the cancelled in-flight response must be reclaimed before a later request"
16721 );
16722 Ok(())
16723 }
16724
16725 #[test]
16726 fn scroll_cursor_context_cancel_arms_recovery_and_is_typed() -> Result<()> {
16727 let listener = TcpListener::bind("127.0.0.1:0")?;
16728 let addr = listener.local_addr()?;
16729 let (request_seen_tx, request_seen_rx) = std::sync::mpsc::channel();
16730 let (release_tx, release_rx) = std::sync::mpsc::channel();
16731 let (cx_tx, cx_rx) = std::sync::mpsc::channel::<Cx>();
16732 let server = thread::spawn(move || -> std::io::Result<()> {
16733 let (mut socket, _) = listener.accept()?;
16734 socket.set_read_timeout(Some(Duration::from_secs(2)))?;
16735 assert_eq!(
16736 read_one_wire_packet(&mut socket),
16737 TNS_PACKET_TYPE_DATA,
16738 "SCROLL request must reach the server before cancellation"
16739 );
16740 request_seen_tx
16741 .send(())
16742 .expect("client cancellation task is alive");
16743 let _ = release_rx.recv_timeout(Duration::from_secs(2));
16744 Ok(())
16745 });
16746
16747 let runtime = build_io_runtime()?;
16748 let canceller = thread::spawn(move || -> Result<()> {
16749 let cancel_cx = cx_rx
16750 .recv_timeout(Duration::from_secs(2))
16751 .map_err(|_| Error::Runtime("SCROLL task never exposed its Cx".into()))?;
16752 request_seen_rx
16753 .recv_timeout(Duration::from_secs(2))
16754 .map_err(|_| Error::Runtime("server never observed SCROLL request".into()))?;
16755 cancel_cx.cancel_fast(CancelKind::User);
16756 Ok(())
16757 });
16758 let task = runtime.handle().spawn(async move {
16759 let cx = test_cx()?;
16760 cx_tx
16761 .send(cx.clone())
16762 .map_err(|_| Error::Runtime("cancellation test coordinator exited".into()))?;
16763 let stream = TcpStream::connect(addr).await?;
16764 let (read, write) = transport::plain_split(stream);
16765 let mut connection = loopback_connection(read, write);
16766
16767 let err = connection
16768 .scroll_cursor(&cx, "select value from scroll_cancel_fixture", 42, 1, 0, 0)
16769 .await
16770 .expect_err("the cancelled in-flight SCROLL must not complete");
16771 Ok::<_, Error>((err, connection.core.recovery.phase()))
16772 });
16773 let (err, phase) = runtime.block_on(task)?;
16774 canceller.join().expect("cancellation thread joins")?;
16775
16776 let _ = release_tx.send(());
16777 server
16778 .join()
16779 .expect("server thread joins")
16780 .map_err(Error::Io)?;
16781
16782 assert!(
16783 matches!(&err, Error::Cancelled),
16784 "a mid-read SCROLL cancellation must surface Error::Cancelled, not {err:?}"
16785 );
16786 assert_eq!(
16787 phase,
16788 SessionRecoveryPhase::BreakSent,
16789 "a cancelled SCROLL response must be reclaimed before a later request"
16790 );
16791 Ok(())
16792 }
16793
16794 #[test]
16795 fn dropped_cancellable_read_is_drained_before_commit_request() -> Result<()> {
16796 const STRANDED_BODY: &[u8] = b"stranded response";
16797 const TRAILING_CANCEL_ERROR: &[u8] = &[0x04, 0x01, 0x0d];
16798
16799 let commit_packet = encode_packet(
16800 TNS_PACKET_TYPE_DATA,
16801 0,
16802 Some(0),
16803 &build_function_payload_with_seq(
16804 TNS_FUNC_COMMIT,
16805 1,
16806 ClientCapabilities::default().ttc_field_version,
16807 ),
16808 PacketLengthWidth::Large32,
16809 )?;
16810 let listener = TcpListener::bind("127.0.0.1:0").expect("bind local listener");
16811 let addr = listener.local_addr().expect("listener address");
16812 let server = thread::spawn(move || {
16813 let (mut socket, _) = listener.accept().expect("accept test client");
16814 socket
16815 .set_read_timeout(Some(Duration::from_secs(5)))
16816 .expect("set read timeout");
16817 use std::io::Write as _;
16818
16819 assert_eq!(
16820 read_marker_type(&mut socket),
16821 TNS_MARKER_TYPE_BREAK,
16822 "commit must send BREAK before its own request when recovery is pending"
16823 );
16824 socket
16825 .write_all(&data_packet(STRANDED_BODY, true))
16826 .expect("write stranded response");
16827 socket
16828 .write_all(&marker_packet(TNS_MARKER_TYPE_BREAK))
16829 .expect("write break-ack marker");
16830 assert_eq!(
16831 read_marker_type(&mut socket),
16832 TNS_MARKER_TYPE_RESET,
16833 "commit recovery drain must answer the server break marker with RESET"
16834 );
16835 socket
16836 .write_all(&marker_packet(TNS_MARKER_TYPE_RESET))
16837 .expect("write reset-confirm marker");
16838 socket
16839 .write_all(&data_packet(TRAILING_CANCEL_ERROR, true))
16840 .expect("write trailing cancel error packet");
16841
16842 let mut header = [0u8; 8];
16843 socket
16844 .read_exact(&mut header)
16845 .expect("read fresh commit request header");
16846 let len = u32::from_be_bytes([header[0], header[1], header[2], header[3]]) as usize;
16847 let mut commit_request = header.to_vec();
16848 let mut body = vec![0u8; len - header.len()];
16849 socket
16850 .read_exact(&mut body)
16851 .expect("read fresh commit request body");
16852 commit_request.extend_from_slice(&body);
16853 assert_eq!(
16854 commit_request, commit_packet,
16855 "fresh COMMIT request must be written after BREAK/RESET drain"
16856 );
16857 socket
16858 .write_all(&data_packet(&[TNS_MSG_TYPE_END_OF_RESPONSE], true))
16859 .expect("write commit response");
16860 });
16861
16862 let runtime = build_io_runtime()?;
16863 runtime.block_on(async {
16864 let cx = test_cx()?;
16865 let stream = TcpStream::connect(addr).await?;
16866 let (read, write) = transport::plain_split(stream);
16867 let mut connection = loopback_connection(read, write);
16868 {
16869 let _guard = CancelDrainGuard::arm(Arc::clone(&connection.core.recovery))?;
16870 }
16871 assert_eq!(
16872 connection.core.recovery.phase(),
16873 SessionRecoveryPhase::BreakSent
16874 );
16875 connection.commit(&cx).await?;
16876 assert_eq!(
16877 connection.core.recovery.phase(),
16878 SessionRecoveryPhase::Ready
16879 );
16880 Ok::<_, Error>(())
16881 })?;
16882
16883 server.join().expect("server thread joins");
16884 Ok(())
16885 }
16886
16887 fn assert_borrowed_stream_cursor_retired(
16888 connection: &Connection,
16889 cursor_id: u32,
16890 expected_phase: SessionRecoveryPhase,
16891 ) {
16892 assert_eq!(connection.core.recovery.phase(), expected_phase);
16893 assert!(!connection.in_use_cursors.contains(&cursor_id));
16894 assert!(!connection.copied_cursors.contains(&cursor_id));
16895 assert!(
16896 connection
16897 .statement_cache
16898 .iter()
16899 .all(|entry| entry.cursor_id != cursor_id),
16900 "failed borrowed stream must evict its cached cursor"
16901 );
16902 assert!(!connection.cursor_columns.contains_key(&cursor_id));
16903 assert!(!connection.lob_prefetch_cursors.contains(&cursor_id));
16904 assert_eq!(
16905 connection
16906 .cursors_to_close
16907 .iter()
16908 .filter(|queued| **queued == cursor_id)
16909 .count(),
16910 1,
16911 "failed borrowed stream must queue exactly one cursor close"
16912 );
16913 }
16914
16915 fn expected_next_close_piggyback(connection: &Connection, cursor_id: u32) -> Vec<u8> {
16916 let mut seq_num = connection.ttc_seq_num;
16917 let close_seq = next_ttc_sequence(&mut seq_num);
16918 oracledb_protocol::thin::build_close_cursors_piggyback(
16919 &[cursor_id],
16920 close_seq,
16921 connection.capabilities.ttc_field_version,
16922 )
16923 }
16924
16925 fn serve_borrowed_stream_break_drain(
16926 socket: &mut std::net::TcpStream,
16927 stranded_response: &[u8],
16928 ) {
16929 use std::io::Write as _;
16930 const TRAILING_CANCEL_ERROR: &[u8] = &[0x04, 0x01, 0x0d];
16931
16932 assert_eq!(
16933 read_marker_type(socket),
16934 TNS_MARKER_TYPE_BREAK,
16935 "reuse must BREAK before sending a request or cursor-close piggyback"
16936 );
16937 socket
16938 .write_all(&data_packet(stranded_response, true))
16939 .expect("write stranded borrowed response");
16940 socket
16941 .write_all(&marker_packet(TNS_MARKER_TYPE_BREAK))
16942 .expect("write break acknowledgement");
16943 assert_eq!(
16944 read_marker_type(socket),
16945 TNS_MARKER_TYPE_RESET,
16946 "drain must RESET before the reuse request"
16947 );
16948 socket
16949 .write_all(&marker_packet(TNS_MARKER_TYPE_RESET))
16950 .expect("write reset confirmation");
16951 socket
16952 .write_all(&data_packet(TRAILING_CANCEL_ERROR, true))
16953 .expect("write trailing cancel response");
16954 }
16955
16956 #[test]
16957 fn borrowed_stream_first_page_callback_error_retires_cursor() -> Result<()> {
16958 const SQL: &str = "select value from borrowed_callback_failure";
16959 let response = synthetic_pipeline_execute_response_payload();
16960 let expected = sequential_op_decode(&response);
16961 let cursor_id = expected.cursor_id;
16962 assert_ne!(cursor_id, 0, "fixture must open a query cursor");
16963 assert!(
16964 !expected.rows.is_empty(),
16965 "fixture must invoke the first-page callback"
16966 );
16967
16968 let listener = TcpListener::bind("127.0.0.1:0")?;
16969 let addr = listener.local_addr()?;
16970 let server = thread::spawn(move || -> std::io::Result<()> {
16971 use std::io::Write as _;
16972 let (mut socket, _) = listener.accept()?;
16973 socket.set_read_timeout(Some(Duration::from_secs(2)))?;
16974 let _execute = read_one_wire_packet(&mut socket);
16975 let response_packet = data_packet(&response, true);
16976 socket.write_all(&response_packet)?;
16977 socket.flush()?;
16978
16979 let _retry = read_one_wire_packet(&mut socket);
16983 socket.write_all(&response_packet)?;
16984 socket.flush()?;
16985 Ok(())
16986 });
16987
16988 let runtime = build_io_runtime()?;
16989 runtime.block_on(async {
16990 let cx = test_cx()?;
16991 let stream = TcpStream::connect(addr).await?;
16992 let (read, write) = transport::plain_split(stream);
16993 let mut connection = loopback_connection(read, write);
16994
16995 let err = connection
16996 .for_each_row_ref(&cx, SQL, 2, |_row| {
16997 Err(Error::Runtime("stop borrowed callback".to_string()))
16998 })
16999 .await
17000 .expect_err("callback failure must be surfaced");
17001 assert!(matches!(err, Error::Runtime(message) if message == "stop borrowed callback"));
17002 assert!(!connection.in_use_cursors.contains(&cursor_id));
17003 assert!(
17004 connection
17005 .statement_cache
17006 .iter()
17007 .all(|entry| entry.cursor_id != cursor_id),
17008 "failed borrowed stream must not leave its cursor reusable"
17009 );
17010 assert!(!connection.cursor_columns.contains_key(&cursor_id));
17011 assert_eq!(
17012 connection
17013 .cursors_to_close
17014 .iter()
17015 .filter(|queued| **queued == cursor_id)
17016 .count(),
17017 1,
17018 "failed borrowed stream must queue one cursor close"
17019 );
17020 assert_eq!(
17021 connection.core.recovery.phase(),
17022 SessionRecoveryPhase::Ready,
17023 "a first-page callback error has no stranded response"
17024 );
17025
17026 let mut retried_rows = 0usize;
17027 connection
17028 .for_each_row_ref(&cx, SQL, 2, |_row| {
17029 retried_rows += 1;
17030 Ok(())
17031 })
17032 .await?;
17033 assert_eq!(retried_rows, expected.rows.len());
17034 assert!(
17035 connection
17036 .statement_cache
17037 .iter()
17038 .any(|entry| entry.cursor_id == cursor_id),
17039 "a successful retry must return its fresh cursor to the cache"
17040 );
17041 assert!(!connection.in_use_cursors.contains(&cursor_id));
17042 assert!(!connection.copied_cursors.contains(&cursor_id));
17043 assert!(
17044 !connection.cursors_to_close.contains(&cursor_id),
17045 "the retry request must consume the abandoned cursor's close piggyback"
17046 );
17047 Ok::<_, Error>(())
17048 })?;
17049
17050 server.join().expect("server thread joins")?;
17051 Ok(())
17052 }
17053
17054 #[test]
17055 fn borrowed_stream_paged_callback_error_drains_before_close_and_reuse() -> Result<()> {
17056 const SQL: &str = "select value from paged_borrowed_callback_failure";
17057 const FRESH_SQL: &str = "select 7 + 5 as value from dual";
17058
17059 let execute_response = synthetic_open_cursor_execute_response_payload();
17060 let first = sequential_op_decode(&execute_response);
17061 let cursor_id = first.cursor_id;
17062 let continuation = synthetic_open_borrowed_fetch_response("13");
17063 let parsed_continuation = parse_query_response_borrowed_with_limits(
17064 &continuation,
17065 ClientCapabilities::default(),
17066 &first.columns,
17067 first.rows.last().map(Vec::as_slice),
17068 ProtocolLimits::DEFAULT,
17069 )
17070 .expect("synthetic continuation must decode through the borrowed parser");
17071 assert_eq!(parsed_continuation.batch.row_count(), 1);
17072 assert!(parsed_continuation.more_rows);
17073 drop(parsed_continuation);
17074
17075 let (close_tx, close_rx) = std::sync::mpsc::channel::<Vec<u8>>();
17076 let listener = TcpListener::bind("127.0.0.1:0")?;
17077 let addr = listener.local_addr()?;
17078 let stranded = continuation.clone();
17079 let server = thread::spawn(move || {
17080 use std::io::Write as _;
17081 let (mut socket, _) = listener.accept().expect("accept test client");
17082 socket
17083 .set_read_timeout(Some(Duration::from_secs(5)))
17084 .expect("set read timeout");
17085
17086 let _execute = read_one_wire_data_payload(&mut socket);
17087 socket
17088 .write_all(&data_packet(&execute_response, true))
17089 .expect("write open-cursor execute response");
17090 let _first_fetch = read_one_wire_data_payload(&mut socket);
17091 socket
17092 .write_all(&data_packet(&continuation, true))
17093 .expect("write first continuation page");
17094
17095 let _speculative_fetch = read_one_wire_data_payload(&mut socket);
17098 serve_borrowed_stream_break_drain(&mut socket, &stranded);
17099
17100 let reuse = read_one_wire_data_payload(&mut socket);
17101 let expected_close = close_rx
17102 .recv_timeout(Duration::from_secs(2))
17103 .expect("client provides expected close piggyback");
17104 assert!(
17105 reuse.starts_with(&expected_close),
17106 "cursor close must be deduplicated and prepended only after BREAK/RESET drain"
17107 );
17108 socket
17109 .write_all(&data_packet(
17110 &synthetic_pipeline_execute_response_payload(),
17111 true,
17112 ))
17113 .expect("write fresh reuse response");
17114 });
17115
17116 let runtime = build_io_runtime()?;
17117 runtime.block_on(async {
17118 let cx = test_cx()?;
17119 let stream = TcpStream::connect(addr).await?;
17120 let (read, write) = transport::plain_split(stream);
17121 let mut connection = loopback_connection(read, write);
17122 let mut rows_seen = 0usize;
17123
17124 let err = connection
17125 .for_each_row_ref(&cx, SQL, 2, |_row| {
17126 rows_seen += 1;
17127 if rows_seen == 2 {
17128 Err(Error::Runtime("stop paged callback".into()))
17129 } else {
17130 Ok(())
17131 }
17132 })
17133 .await
17134 .expect_err("paged callback failure must be surfaced");
17135 assert!(matches!(err, Error::Runtime(message) if message == "stop paged callback"));
17136 assert_eq!(
17137 rows_seen, 2,
17138 "failure must occur in the fetched-page callback, after speculative send"
17139 );
17140 assert_borrowed_stream_cursor_retired(
17141 &connection,
17142 cursor_id,
17143 SessionRecoveryPhase::InFlight,
17144 );
17145 connection.close_cursor(cursor_id);
17146 assert_borrowed_stream_cursor_retired(
17147 &connection,
17148 cursor_id,
17149 SessionRecoveryPhase::InFlight,
17150 );
17151
17152 close_tx
17153 .send(expected_next_close_piggyback(&connection, cursor_id))
17154 .expect("server is waiting for close proof");
17155 let fresh = connection
17156 .execute_raw(&cx, FRESH_SQL, 2, &[], ExecuteOptions::default(), None)
17157 .await?;
17158 assert_eq!(
17159 fresh,
17160 sequential_op_decode(&synthetic_pipeline_execute_response_payload()),
17161 "same connection must decode its own response after drain"
17162 );
17163 assert_eq!(
17164 connection.core.recovery.phase(),
17165 SessionRecoveryPhase::Ready
17166 );
17167 assert!(!connection.cursors_to_close.contains(&cursor_id));
17168 connection.release_cursor(fresh.cursor_id);
17169 Ok::<_, Error>(())
17170 })?;
17171
17172 server.join().expect("server thread joins");
17173 Ok(())
17174 }
17175
17176 fn exercise_borrowed_stream_fetch_decode_failure(malformed: Vec<u8>) -> Result<()> {
17177 const SQL: &str = "select value from malformed_borrowed_fetch";
17178 const FRESH_SQL: &str = "select 7 + 5 as value from dual";
17179
17180 let execute_response = synthetic_open_cursor_execute_response_payload();
17181 let cursor_id = sequential_op_decode(&execute_response).cursor_id;
17182 let (close_tx, close_rx) = std::sync::mpsc::channel::<Vec<u8>>();
17183 let listener = TcpListener::bind("127.0.0.1:0")?;
17184 let addr = listener.local_addr()?;
17185 let server = thread::spawn(move || {
17186 use std::io::Write as _;
17187 let (mut socket, _) = listener.accept().expect("accept test client");
17188 socket
17189 .set_read_timeout(Some(Duration::from_secs(5)))
17190 .expect("set read timeout");
17191
17192 let _execute = read_one_wire_data_payload(&mut socket);
17193 socket
17194 .write_all(&data_packet(&execute_response, true))
17195 .expect("write open-cursor execute response");
17196 let _fetch = read_one_wire_data_payload(&mut socket);
17197 socket
17198 .write_all(&data_packet(&malformed, true))
17199 .expect("write malformed fetch response");
17200
17201 let reuse = read_one_wire_data_payload(&mut socket);
17204 let expected_close = close_rx
17205 .recv_timeout(Duration::from_secs(2))
17206 .expect("client provides expected close piggyback");
17207 assert!(reuse.starts_with(&expected_close));
17208 socket
17209 .write_all(&data_packet(
17210 &synthetic_pipeline_execute_response_payload(),
17211 true,
17212 ))
17213 .expect("write fresh reuse response");
17214 });
17215
17216 let runtime = build_io_runtime()?;
17217 runtime.block_on(async {
17218 let cx = test_cx()?;
17219 let stream = TcpStream::connect(addr).await?;
17220 let (read, write) = transport::plain_split(stream);
17221 let mut connection = loopback_connection(read, write);
17222 let mut rows_seen = 0usize;
17223
17224 let err = connection
17225 .for_each_row_ref(&cx, SQL, 2, |_row| {
17226 rows_seen += 1;
17227 Ok(())
17228 })
17229 .await
17230 .expect_err("malformed continuation must fail decoding");
17231 assert!(matches!(err, Error::Protocol(_)));
17232 assert_eq!(rows_seen, 1, "only the execute page may reach the callback");
17233 assert_borrowed_stream_cursor_retired(
17234 &connection,
17235 cursor_id,
17236 SessionRecoveryPhase::Ready,
17237 );
17238 connection.close_cursor(cursor_id);
17239 assert_borrowed_stream_cursor_retired(
17240 &connection,
17241 cursor_id,
17242 SessionRecoveryPhase::Ready,
17243 );
17244
17245 close_tx
17246 .send(expected_next_close_piggyback(&connection, cursor_id))
17247 .expect("server is waiting for close proof");
17248 let fresh = connection
17249 .execute_raw(&cx, FRESH_SQL, 2, &[], ExecuteOptions::default(), None)
17250 .await?;
17251 assert_eq!(
17252 fresh,
17253 sequential_op_decode(&synthetic_pipeline_execute_response_payload())
17254 );
17255 assert_eq!(
17256 connection.core.recovery.phase(),
17257 SessionRecoveryPhase::Ready
17258 );
17259 assert!(!connection.cursors_to_close.contains(&cursor_id));
17260 connection.release_cursor(fresh.cursor_id);
17261 Ok::<_, Error>(())
17262 })?;
17263
17264 server.join().expect("server thread joins");
17265 Ok(())
17266 }
17267
17268 #[test]
17269 fn borrowed_stream_malformed_and_truncated_fetches_retire_before_reuse() -> Result<()> {
17270 exercise_borrowed_stream_fetch_decode_failure(vec![0xff])?;
17271 exercise_borrowed_stream_fetch_decode_failure(vec![
17272 oracledb_protocol::thin::TNS_MSG_TYPE_ROW_HEADER,
17273 ])
17274 }
17275
17276 #[test]
17277 fn borrowed_stream_fetch_eof_retires_cursor() -> Result<()> {
17278 const SQL: &str = "select value from eof_borrowed_fetch";
17279 let execute_response = synthetic_open_cursor_execute_response_payload();
17280 let cursor_id = sequential_op_decode(&execute_response).cursor_id;
17281 let listener = TcpListener::bind("127.0.0.1:0")?;
17282 let addr = listener.local_addr()?;
17283 let server = thread::spawn(move || {
17284 use std::io::Write as _;
17285 let (mut socket, _) = listener.accept().expect("accept test client");
17286 socket
17287 .set_read_timeout(Some(Duration::from_secs(5)))
17288 .expect("set read timeout");
17289 let _execute = read_one_wire_data_payload(&mut socket);
17290 socket
17291 .write_all(&data_packet(&execute_response, true))
17292 .expect("write open-cursor execute response");
17293 let _fetch = read_one_wire_data_payload(&mut socket);
17294 socket
17295 .shutdown(std::net::Shutdown::Both)
17296 .expect("close during fetch response");
17297 });
17298
17299 let runtime = build_io_runtime()?;
17300 runtime.block_on(async {
17301 let cx = test_cx()?;
17302 let stream = TcpStream::connect(addr).await?;
17303 let (read, write) = transport::plain_split(stream);
17304 let mut connection = loopback_connection(read, write);
17305 let err = connection
17306 .for_each_row_ref(&cx, SQL, 2, |_row| Ok(()))
17307 .await
17308 .expect_err("EOF during continuation fetch must fail");
17309 assert!(matches!(err, Error::Io(_) | Error::ConnectionClosed(_)));
17310 assert_borrowed_stream_cursor_retired(
17311 &connection,
17312 cursor_id,
17313 SessionRecoveryPhase::BreakSent,
17314 );
17315 connection.close_cursor(cursor_id);
17316 assert_borrowed_stream_cursor_retired(
17317 &connection,
17318 cursor_id,
17319 SessionRecoveryPhase::BreakSent,
17320 );
17321 Ok::<_, Error>(())
17322 })?;
17323
17324 server.join().expect("server thread joins");
17325 Ok(())
17326 }
17327
17328 #[test]
17329 fn borrowed_stream_cancel_before_fetch_request_retires_without_wire_write() -> Result<()> {
17330 const SQL: &str = "select value from pre_request_borrowed_cancel";
17331 let execute_response = synthetic_open_cursor_execute_response_payload();
17332 let cursor_id = sequential_op_decode(&execute_response).cursor_id;
17333 let listener = TcpListener::bind("127.0.0.1:0")?;
17334 let addr = listener.local_addr()?;
17335 let server = thread::spawn(move || -> std::io::Result<Option<u8>> {
17336 use std::io::Write as _;
17337 let (mut socket, _) = listener.accept()?;
17338 socket.set_read_timeout(Some(Duration::from_millis(500)))?;
17339 let _execute = read_one_wire_data_payload(&mut socket);
17340 socket.write_all(&data_packet(&execute_response, true))?;
17341 socket.flush()?;
17342
17343 let mut byte = [0u8; 1];
17344 match socket.read(&mut byte) {
17345 Ok(0) => Ok(None),
17346 Ok(_) => Ok(Some(byte[0])),
17347 Err(err)
17348 if matches!(
17349 err.kind(),
17350 std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut
17351 ) =>
17352 {
17353 Ok(None)
17354 }
17355 Err(err) => Err(err),
17356 }
17357 });
17358
17359 let runtime = build_io_runtime()?;
17360 runtime.block_on(async {
17361 let cx = test_cx()?;
17362 let stream = TcpStream::connect(addr).await?;
17363 let (read, write) = transport::plain_split(stream);
17364 let mut connection = loopback_connection(read, write);
17365 let err = connection
17366 .for_each_row_ref(&cx, SQL, 2, |_row| {
17367 cx.cancel_fast(asupersync::CancelKind::User);
17368 Ok(())
17369 })
17370 .await
17371 .expect_err("cancel checkpoint must stop before FETCH write");
17372 assert!(matches!(err, Error::Cancelled));
17373 assert_eq!(
17374 connection.ttc_seq_num, 1,
17375 "cancel-before-request must not allocate a FETCH sequence"
17376 );
17377 assert_borrowed_stream_cursor_retired(
17378 &connection,
17379 cursor_id,
17380 SessionRecoveryPhase::Ready,
17381 );
17382 connection.close_cursor(cursor_id);
17383 assert_borrowed_stream_cursor_retired(
17384 &connection,
17385 cursor_id,
17386 SessionRecoveryPhase::Ready,
17387 );
17388 Ok::<_, Error>(())
17389 })?;
17390
17391 let received = server.join().expect("server thread joins")?;
17392 assert_eq!(received, None, "cancel-before-request must write no FETCH");
17393 Ok(())
17394 }
17395
17396 #[test]
17397 fn borrowed_stream_drop_after_fetch_request_drains_before_close_and_reuse() -> Result<()> {
17398 const SQL: &str = "select value from post_request_borrowed_cancel";
17399 const FRESH_SQL: &str = "select 7 + 5 as value from dual";
17400 let execute_response = synthetic_open_cursor_execute_response_payload();
17401 let cursor_id = sequential_op_decode(&execute_response).cursor_id;
17402 let stranded = synthetic_open_borrowed_fetch_response("14");
17403 let (execute_ready_tx, execute_ready_rx) = std::sync::mpsc::channel();
17404 let (fetch_seen_tx, fetch_seen_rx) = std::sync::mpsc::channel();
17405 let (close_tx, close_rx) = std::sync::mpsc::channel::<Vec<u8>>();
17406 let listener = TcpListener::bind("127.0.0.1:0")?;
17407 let addr = listener.local_addr()?;
17408 let server = thread::spawn(move || {
17409 use std::io::Write as _;
17410 let (mut socket, _) = listener.accept().expect("accept test client");
17411 socket
17412 .set_read_timeout(Some(Duration::from_secs(5)))
17413 .expect("set read timeout");
17414 let _execute = read_one_wire_data_payload(&mut socket);
17415 socket
17416 .write_all(&data_packet(&execute_response, true))
17417 .expect("write open-cursor execute response");
17418 socket.flush().expect("flush execute response");
17419 execute_ready_tx
17420 .send(())
17421 .expect("client waits for execute response");
17422 let _fetch = read_one_wire_data_payload(&mut socket);
17423 fetch_seen_tx
17424 .send(())
17425 .expect("client waits for FETCH write");
17426
17427 serve_borrowed_stream_break_drain(&mut socket, &stranded);
17428 let reuse = read_one_wire_data_payload(&mut socket);
17429 let expected_close = close_rx
17430 .recv_timeout(Duration::from_secs(2))
17431 .expect("client provides expected close piggyback");
17432 assert!(reuse.starts_with(&expected_close));
17433 socket
17434 .write_all(&data_packet(
17435 &synthetic_pipeline_execute_response_payload(),
17436 true,
17437 ))
17438 .expect("write fresh reuse response");
17439 });
17440
17441 let runtime = build_io_runtime()?;
17442 runtime.block_on(async {
17443 let cx = test_cx()?;
17444 let stream = TcpStream::connect(addr).await?;
17445 let (read, write) = transport::plain_split(stream);
17446 let mut connection = loopback_connection(read, write);
17447 let mut rows_seen = 0usize;
17448
17449 {
17450 let mut fetch = pin!(connection.for_each_row_ref(&cx, SQL, 2, |_row| {
17451 rows_seen += 1;
17452 Ok(())
17453 }));
17454 let first_poll = poll_fn(|task_cx| Poll::Ready(fetch.as_mut().poll(task_cx))).await;
17455 assert!(matches!(first_poll, Poll::Pending));
17456 execute_ready_rx
17457 .recv_timeout(Duration::from_secs(2))
17458 .expect("server wrote execute response");
17459
17460 let deadline = Instant::now() + Duration::from_secs(2);
17461 loop {
17462 match fetch_seen_rx.try_recv() {
17463 Ok(()) => break,
17464 Err(std::sync::mpsc::TryRecvError::Empty) => {}
17465 Err(std::sync::mpsc::TryRecvError::Disconnected) => {
17466 panic!("server exited before seeing FETCH")
17467 }
17468 }
17469 assert!(Instant::now() < deadline, "FETCH request was never sent");
17470 let polled = poll_fn(|task_cx| Poll::Ready(fetch.as_mut().poll(task_cx))).await;
17471 assert!(matches!(polled, Poll::Pending));
17472 thread::yield_now();
17473 }
17474 }
17475
17476 assert_eq!(
17477 rows_seen, 1,
17478 "execute page callback must run before FETCH wait"
17479 );
17480 assert_borrowed_stream_cursor_retired(
17481 &connection,
17482 cursor_id,
17483 SessionRecoveryPhase::BreakSent,
17484 );
17485 connection.close_cursor(cursor_id);
17486 assert_borrowed_stream_cursor_retired(
17487 &connection,
17488 cursor_id,
17489 SessionRecoveryPhase::BreakSent,
17490 );
17491
17492 close_tx
17493 .send(expected_next_close_piggyback(&connection, cursor_id))
17494 .expect("server is waiting for close proof");
17495 let fresh = connection
17496 .execute_raw(&cx, FRESH_SQL, 2, &[], ExecuteOptions::default(), None)
17497 .await?;
17498 assert_eq!(
17499 fresh,
17500 sequential_op_decode(&synthetic_pipeline_execute_response_payload())
17501 );
17502 assert_eq!(
17503 connection.core.recovery.phase(),
17504 SessionRecoveryPhase::Ready
17505 );
17506 assert!(!connection.cursors_to_close.contains(&cursor_id));
17507 connection.release_cursor(fresh.cursor_id);
17508 Ok::<_, Error>(())
17509 })?;
17510
17511 server.join().expect("server thread joins");
17512 Ok(())
17513 }
17514
17515 #[test]
17516 fn streaming_cancel_mid_stream_leaves_connection_reusable() -> Result<()> {
17517 const STRANDED_BODY: &[u8] = b"stranded stream response";
17532 const TRAILING_CANCEL_ERROR: &[u8] = &[0x04, 0x01, 0x0d]; let listener = TcpListener::bind("127.0.0.1:0").expect("bind local listener");
17535 let addr = listener.local_addr().expect("listener address");
17536 let server = thread::spawn(move || {
17537 use std::io::Write as _;
17538 let (mut socket, _) = listener.accept().expect("accept test client");
17539 socket
17540 .set_read_timeout(Some(Duration::from_secs(5)))
17541 .expect("set read timeout");
17542
17543 let _fetch_request = read_one_wire_packet(&mut socket);
17545
17546 assert_eq!(
17548 read_marker_type(&mut socket),
17549 TNS_MARKER_TYPE_BREAK,
17550 "the reuse op must BREAK the stranded stream before its own request"
17551 );
17552 socket
17553 .write_all(&data_packet(STRANDED_BODY, true))
17554 .expect("write stranded stream response");
17555 socket
17556 .write_all(&marker_packet(TNS_MARKER_TYPE_BREAK))
17557 .expect("write break-ack marker");
17558 assert_eq!(
17559 read_marker_type(&mut socket),
17560 TNS_MARKER_TYPE_RESET,
17561 "the drain must answer the server break marker with RESET"
17562 );
17563 socket
17564 .write_all(&marker_packet(TNS_MARKER_TYPE_RESET))
17565 .expect("write reset-confirm marker");
17566 socket
17567 .write_all(&data_packet(TRAILING_CANCEL_ERROR, true))
17568 .expect("write trailing cancel error packet");
17569
17570 let _reuse_request = read_one_wire_packet(&mut socket);
17572 socket
17573 .write_all(&data_packet(
17574 &synthetic_pipeline_execute_response_payload(),
17575 true,
17576 ))
17577 .expect("write reuse query response");
17578 socket.flush().expect("flush responses");
17579 });
17580
17581 let runtime = build_io_runtime()?;
17582 runtime.block_on(async {
17583 let cx = test_cx()?;
17584 let stream = TcpStream::connect(addr).await?;
17585 let (read, write) = transport::plain_split(stream);
17586 let mut connection = loopback_connection(read, write);
17587
17588 {
17592 let mut fetch = pin!(connection.fetch_rows_ref(&cx, 42, 10, None));
17593 let first = poll_fn(|task_cx| Poll::Ready(fetch.as_mut().poll(task_cx))).await;
17594 assert!(
17595 matches!(first, Poll::Pending),
17596 "the stream must be waiting on the server response when cancelled"
17597 );
17598 }
17599 assert_eq!(
17600 connection.core.recovery.phase(),
17601 SessionRecoveryPhase::BreakSent,
17602 "cancelling the stream mid-read arms break/drain recovery"
17603 );
17604
17605 let reused = connection
17608 .execute_raw(
17609 &cx,
17610 "select value from synthetic_fixture",
17611 2,
17612 &[],
17613 ExecuteOptions::default(),
17614 None,
17615 )
17616 .await?;
17617 assert_eq!(
17618 connection.core.recovery.phase(),
17619 SessionRecoveryPhase::Ready,
17620 "after the drain the connection is back to Ready"
17621 );
17622 assert_eq!(
17623 reused,
17624 sequential_op_decode(&synthetic_pipeline_execute_response_payload()),
17625 "the reused connection decodes the follow-up query byte-identically"
17626 );
17627 Ok::<_, Error>(())
17628 })?;
17629
17630 server.join().expect("server thread joins");
17631 Ok(())
17632 }
17633
17634 #[test]
17635 fn dropped_define_fetch_mid_read_drains_before_connection_reuse() -> Result<()> {
17636 const CURSOR_ID: u32 = 42;
17637 const STRANDED_BODY: &[u8] = b"stranded define-fetch response";
17638 const TRAILING_CANCEL_ERROR: &[u8] = &[0x04, 0x01, 0x0d];
17639 const FRESH_SQL: &str = "select value from define_fetch_reuse_fixture";
17640
17641 let listener = TcpListener::bind("127.0.0.1:0")?;
17642 let addr = listener.local_addr()?;
17643 let (define_seen_tx, define_seen_rx) = std::sync::mpsc::channel();
17644 let server = thread::spawn(move || -> std::io::Result<bool> {
17645 use std::io::Write as _;
17646 let (mut socket, _) = listener.accept()?;
17647 socket.set_read_timeout(Some(Duration::from_secs(5)))?;
17648
17649 assert_eq!(
17650 read_one_wire_packet(&mut socket),
17651 TNS_PACKET_TYPE_DATA,
17652 "define-fetch must send a DATA request"
17653 );
17654 define_seen_tx
17655 .send(())
17656 .expect("client waits for define-fetch request proof");
17657
17658 let (next_packet_type, next_body) = read_one_wire_packet_bytes(&mut socket);
17659 if next_packet_type == TNS_PACKET_TYPE_MARKER {
17660 assert_eq!(
17661 next_body,
17662 vec![1, 0, TNS_MARKER_TYPE_BREAK],
17663 "reuse must BREAK the stranded define-fetch before its request"
17664 );
17665 socket.write_all(&data_packet(STRANDED_BODY, true))?;
17666 socket.write_all(&marker_packet(TNS_MARKER_TYPE_BREAK))?;
17667 assert_eq!(
17668 read_marker_type(&mut socket),
17669 TNS_MARKER_TYPE_RESET,
17670 "define-fetch drain must complete the RESET handshake"
17671 );
17672 socket.write_all(&marker_packet(TNS_MARKER_TYPE_RESET))?;
17673 socket.write_all(&data_packet(TRAILING_CANCEL_ERROR, true))?;
17674
17675 assert_eq!(
17676 read_one_wire_packet(&mut socket),
17677 TNS_PACKET_TYPE_DATA,
17678 "fresh execute follows the completed drain"
17679 );
17680 socket.write_all(&data_packet(
17681 &synthetic_pipeline_execute_response_payload(),
17682 true,
17683 ))?;
17684 socket.flush()?;
17685 Ok(true)
17686 } else {
17687 assert_eq!(
17688 next_packet_type, TNS_PACKET_TYPE_DATA,
17689 "without recovery the next operation is sent directly"
17690 );
17691 socket.write_all(&data_packet(STRANDED_BODY, true))?;
17694 socket.write_all(&data_packet(
17695 &synthetic_pipeline_execute_response_payload(),
17696 true,
17697 ))?;
17698 socket.flush()?;
17699 Ok(false)
17700 }
17701 });
17702
17703 let runtime = build_io_runtime()?;
17704 let outcome = runtime.block_on(async {
17705 let cx = test_cx()?;
17706 let stream = TcpStream::connect(addr).await?;
17707 let (read, write) = transport::plain_split(stream);
17708 let mut connection = loopback_connection(read, write);
17709 let define_columns = vec![ColumnMetadata::new(
17710 "DOC",
17711 oracledb_protocol::thin::ORA_TYPE_NUM_JSON,
17712 )];
17713
17714 {
17715 let mut define = pin!(connection.define_and_fetch_rows_with_columns(
17716 &cx,
17717 CURSOR_ID,
17718 10,
17719 &define_columns,
17720 None,
17721 ));
17722 let first = poll_fn(|task_cx| Poll::Ready(define.as_mut().poll(task_cx))).await;
17723 assert!(
17724 matches!(first, Poll::Pending),
17725 "define-fetch must be waiting for its response before drop"
17726 );
17727 define_seen_rx
17728 .recv_timeout(Duration::from_secs(2))
17729 .expect("server observed define-fetch request");
17730 }
17731
17732 let reused = connection
17733 .execute_raw(&cx, FRESH_SQL, 2, &[], ExecuteOptions::default(), None)
17734 .await;
17735 Ok::<_, Error>((reused, connection.core.recovery.phase()))
17736 });
17737
17738 let recovered = server.join().expect("define-fetch server joins")?;
17739 let (reused, phase) = outcome?;
17740 let reused = reused.expect("fresh execute must not decode the stranded define-fetch");
17741 assert_eq!(
17742 reused,
17743 sequential_op_decode(&synthetic_pipeline_execute_response_payload()),
17744 "reuse must decode its own response byte-identically"
17745 );
17746 assert!(recovered, "reuse must take the BREAK/drain branch");
17747 assert_eq!(phase, SessionRecoveryPhase::Ready);
17748 Ok(())
17749 }
17750
17751 #[test]
17752 fn dropped_commit_mid_read_drains_before_connection_reuse() -> Result<()> {
17753 const STRANDED_BODY: &[u8] = &[TNS_MSG_TYPE_END_OF_RESPONSE];
17754 const TRAILING_CANCEL_ERROR: &[u8] = &[0x04, 0x01, 0x0d];
17755 const FRESH_SQL: &str = "select value from commit_reuse_fixture";
17756
17757 let listener = TcpListener::bind("127.0.0.1:0")?;
17758 let addr = listener.local_addr()?;
17759 let (commit_seen_tx, commit_seen_rx) = std::sync::mpsc::channel();
17760 let server = thread::spawn(move || -> std::io::Result<bool> {
17761 use std::io::Write as _;
17762 let (mut socket, _) = listener.accept()?;
17763 socket.set_read_timeout(Some(Duration::from_secs(5)))?;
17764
17765 assert_eq!(
17766 read_one_wire_packet(&mut socket),
17767 TNS_PACKET_TYPE_DATA,
17768 "commit must send a DATA request"
17769 );
17770 commit_seen_tx
17771 .send(())
17772 .expect("client waits for commit request proof");
17773
17774 let (next_packet_type, next_body) = read_one_wire_packet_bytes(&mut socket);
17775 if next_packet_type == TNS_PACKET_TYPE_MARKER {
17776 assert_eq!(
17777 next_body,
17778 vec![1, 0, TNS_MARKER_TYPE_BREAK],
17779 "reuse must BREAK the stranded commit before its request"
17780 );
17781 socket.write_all(&data_packet(STRANDED_BODY, true))?;
17782 socket.write_all(&marker_packet(TNS_MARKER_TYPE_BREAK))?;
17783 assert_eq!(
17784 read_marker_type(&mut socket),
17785 TNS_MARKER_TYPE_RESET,
17786 "commit drain must complete the RESET handshake"
17787 );
17788 socket.write_all(&marker_packet(TNS_MARKER_TYPE_RESET))?;
17789 socket.write_all(&data_packet(TRAILING_CANCEL_ERROR, true))?;
17790
17791 assert_eq!(
17792 read_one_wire_packet(&mut socket),
17793 TNS_PACKET_TYPE_DATA,
17794 "fresh execute follows the completed drain"
17795 );
17796 socket.write_all(&data_packet(
17797 &synthetic_pipeline_execute_response_payload(),
17798 true,
17799 ))?;
17800 socket.flush()?;
17801 Ok(true)
17802 } else {
17803 assert_eq!(
17804 next_packet_type, TNS_PACKET_TYPE_DATA,
17805 "without recovery the next operation is sent directly"
17806 );
17807 socket.write_all(&data_packet(STRANDED_BODY, true))?;
17810 socket.write_all(&data_packet(
17811 &synthetic_pipeline_execute_response_payload(),
17812 true,
17813 ))?;
17814 socket.flush()?;
17815 Ok(false)
17816 }
17817 });
17818
17819 let runtime = build_io_runtime()?;
17820 let outcome = runtime.block_on(async {
17821 let cx = test_cx()?;
17822 let stream = TcpStream::connect(addr).await?;
17823 let (read, write) = transport::plain_split(stream);
17824 let mut connection = loopback_connection(read, write);
17825
17826 {
17827 let mut commit = pin!(connection.commit(&cx));
17828 let first = poll_fn(|task_cx| Poll::Ready(commit.as_mut().poll(task_cx))).await;
17829 assert!(
17830 matches!(first, Poll::Pending),
17831 "commit must be waiting for its response before drop"
17832 );
17833 commit_seen_rx
17834 .recv_timeout(Duration::from_secs(2))
17835 .expect("server observed commit request");
17836 }
17837
17838 let phase_after_drop = connection.core.recovery.phase();
17839 let reused = connection
17840 .execute_raw(&cx, FRESH_SQL, 2, &[], ExecuteOptions::default(), None)
17841 .await;
17842 Ok::<_, Error>((phase_after_drop, reused, connection.core.recovery.phase()))
17843 });
17844
17845 let recovered = server.join().expect("commit server joins")?;
17846 let (phase_after_drop, reused, final_phase) = outcome?;
17847 let reused = reused.expect("fresh execute must not decode the stranded commit");
17848 assert_eq!(
17849 reused,
17850 sequential_op_decode(&synthetic_pipeline_execute_response_payload()),
17851 "reuse must decode its own response byte-identically"
17852 );
17853 assert!(recovered, "reuse must take the BREAK/drain branch");
17854 assert_eq!(phase_after_drop, SessionRecoveryPhase::BreakSent);
17855 assert_eq!(final_phase, SessionRecoveryPhase::Ready);
17856 Ok(())
17857 }
17858
17859 #[test]
17860 fn successful_commit_disarms_recovery_before_reuse() -> Result<()> {
17861 const FRESH_SQL: &str = "select value from commit_success_fixture";
17862 let execute_response = synthetic_pipeline_execute_response_payload();
17863 let server_execute_response = execute_response.clone();
17864 let listener = TcpListener::bind("127.0.0.1:0")?;
17865 let addr = listener.local_addr()?;
17866 let server = thread::spawn(move || -> std::io::Result<()> {
17867 use std::io::Write as _;
17868 let (mut socket, _) = listener.accept()?;
17869 socket.set_read_timeout(Some(Duration::from_secs(2)))?;
17870
17871 assert_eq!(
17872 read_one_wire_packet(&mut socket),
17873 TNS_PACKET_TYPE_DATA,
17874 "commit must send a DATA request"
17875 );
17876 socket.write_all(&data_packet(&[TNS_MSG_TYPE_END_OF_RESPONSE], true))?;
17877 socket.flush()?;
17878
17879 assert_eq!(
17880 read_one_wire_packet(&mut socket),
17881 TNS_PACKET_TYPE_DATA,
17882 "a successful commit must not cause a spurious BREAK on reuse"
17883 );
17884 socket.write_all(&data_packet(&server_execute_response, true))?;
17885 socket.flush()
17886 });
17887
17888 let runtime = build_io_runtime()?;
17889 runtime.block_on(async {
17890 let cx = test_cx()?;
17891 let stream = TcpStream::connect(addr).await?;
17892 let (read, write) = transport::plain_split(stream);
17893 let mut connection = loopback_connection(read, write);
17894
17895 connection.commit(&cx).await?;
17896 assert_eq!(
17897 connection.core.recovery.phase(),
17898 SessionRecoveryPhase::Ready,
17899 "a completed response must disarm the plain-function guard"
17900 );
17901
17902 let reused = connection
17903 .execute_raw(&cx, FRESH_SQL, 2, &[], ExecuteOptions::default(), None)
17904 .await?;
17905 assert_eq!(reused, sequential_op_decode(&execute_response));
17906 assert_eq!(
17907 connection.core.recovery.phase(),
17908 SessionRecoveryPhase::Ready
17909 );
17910 Ok::<_, Error>(())
17911 })?;
17912
17913 server.join().expect("successful commit server joins")?;
17914 Ok(())
17915 }
17916
17917 #[test]
17918 fn precancelled_commit_writes_nothing_and_keeps_wire_ready() -> Result<()> {
17919 let listener = TcpListener::bind("127.0.0.1:0")?;
17920 let addr = listener.local_addr()?;
17921 let server = thread::spawn(move || -> std::io::Result<bool> {
17922 let (mut socket, _) = listener.accept()?;
17923 socket.set_read_timeout(Some(Duration::from_millis(300)))?;
17924 let mut byte = [0u8; 1];
17925 match socket.read(&mut byte) {
17926 Ok(0) => Ok(false),
17927 Ok(_) => Ok(true),
17928 Err(err)
17929 if matches!(
17930 err.kind(),
17931 std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut
17932 ) =>
17933 {
17934 Ok(false)
17935 }
17936 Err(err) => Err(err),
17937 }
17938 });
17939
17940 let runtime = build_io_runtime()?;
17941 let (phase, sequence_before, sequence_after) = runtime.block_on(async {
17942 let cx = test_cx()?;
17943 let stream = TcpStream::connect(addr).await?;
17944 let (read, write) = transport::plain_split(stream);
17945 let mut connection = loopback_connection(read, write);
17946 let sequence_before = connection.ttc_seq_num;
17947 cx.cancel_fast(CancelKind::User);
17948
17949 let err = connection
17950 .commit(&cx)
17951 .await
17952 .expect_err("pending cancellation stops before COMMIT");
17953 assert!(matches!(err, Error::Cancelled), "{err:?}");
17954 Ok::<_, Error>((
17955 connection.core.recovery.phase(),
17956 sequence_before,
17957 connection.ttc_seq_num,
17958 ))
17959 })?;
17960
17961 assert!(
17962 !server.join().expect("pre-cancel commit server joins")?,
17963 "pre-cancelled COMMIT must not write any wire bytes"
17964 );
17965 assert_eq!(phase, SessionRecoveryPhase::Ready);
17966 assert_eq!(sequence_after, sequence_before);
17967 Ok(())
17968 }
17969
17970 #[test]
17971 fn dropped_change_password_mid_response_drains_before_connection_reuse() -> Result<()> {
17972 const STRANDED_BODY: &[u8] = &[TNS_MSG_TYPE_END_OF_RESPONSE];
17973 const TRAILING_CANCEL_ERROR: &[u8] = &[0x04, 0x01, 0x0d];
17974 const FRESH_SQL: &str = "select value from change_password_reuse_fixture";
17975 const OLD_PASSWORD: &str = "old-change-password-proof";
17976 const NEW_PASSWORD: &str = "new-change-password-proof";
17977
17978 let listener = TcpListener::bind("127.0.0.1:0")?;
17979 let addr = listener.local_addr()?;
17980 let (change_seen_tx, change_seen_rx) = std::sync::mpsc::channel();
17981 let server = thread::spawn(move || -> std::io::Result<bool> {
17982 use std::io::Write as _;
17983 let (mut socket, _) = listener.accept()?;
17984 socket.set_read_timeout(Some(Duration::from_secs(5)))?;
17985
17986 let (packet_type, body) = read_one_wire_packet_bytes(&mut socket);
17987 assert_eq!(
17988 packet_type, TNS_PACKET_TYPE_DATA,
17989 "password change must send a DATA request"
17990 );
17991 assert!(
17992 !body
17993 .windows(OLD_PASSWORD.len())
17994 .any(|window| window == OLD_PASSWORD.as_bytes()),
17995 "password-change request must not expose the old password"
17996 );
17997 assert!(
17998 !body
17999 .windows(NEW_PASSWORD.len())
18000 .any(|window| window == NEW_PASSWORD.as_bytes()),
18001 "password-change request must not expose the new password"
18002 );
18003 change_seen_tx
18004 .send(())
18005 .expect("client waits for password-change request proof");
18006
18007 let (next_packet_type, next_body) = read_one_wire_packet_bytes(&mut socket);
18008 if next_packet_type == TNS_PACKET_TYPE_MARKER {
18009 assert_eq!(
18010 next_body,
18011 vec![1, 0, TNS_MARKER_TYPE_BREAK],
18012 "reuse must BREAK the stranded password change before its request"
18013 );
18014 socket.write_all(&data_packet(STRANDED_BODY, true))?;
18015 socket.write_all(&marker_packet(TNS_MARKER_TYPE_BREAK))?;
18016 assert_eq!(
18017 read_marker_type(&mut socket),
18018 TNS_MARKER_TYPE_RESET,
18019 "password-change drain must complete the RESET handshake"
18020 );
18021 socket.write_all(&marker_packet(TNS_MARKER_TYPE_RESET))?;
18022 socket.write_all(&data_packet(TRAILING_CANCEL_ERROR, true))?;
18023
18024 assert_eq!(
18025 read_one_wire_packet(&mut socket),
18026 TNS_PACKET_TYPE_DATA,
18027 "fresh execute follows the completed drain"
18028 );
18029 socket.write_all(&data_packet(
18030 &synthetic_pipeline_execute_response_payload(),
18031 true,
18032 ))?;
18033 socket.flush()?;
18034 Ok(true)
18035 } else {
18036 assert_eq!(
18037 next_packet_type, TNS_PACKET_TYPE_DATA,
18038 "without recovery the next operation is sent directly"
18039 );
18040 socket.write_all(&data_packet(STRANDED_BODY, true))?;
18041 socket.write_all(&data_packet(
18042 &synthetic_pipeline_execute_response_payload(),
18043 true,
18044 ))?;
18045 socket.flush()?;
18046 Ok(false)
18047 }
18048 });
18049
18050 let runtime = build_io_runtime()?;
18051 let outcome = runtime.block_on(async {
18052 let cx = test_cx()?;
18053 let stream = TcpStream::connect(addr).await?;
18054 let (read, write) = transport::plain_split(stream);
18055 let mut connection = loopback_connection(read, write);
18056 connection.combo_key = vec![0x11; 32];
18057
18058 {
18059 let mut change_password =
18060 pin!(connection.change_password(&cx, OLD_PASSWORD, NEW_PASSWORD,));
18061 let first =
18062 poll_fn(|task_cx| Poll::Ready(change_password.as_mut().poll(task_cx))).await;
18063 assert!(
18064 matches!(first, Poll::Pending),
18065 "password change must be waiting for its response before drop"
18066 );
18067 change_seen_rx
18068 .recv_timeout(Duration::from_secs(2))
18069 .expect("server observed password-change request");
18070 }
18071
18072 let phase_after_drop = connection.core.recovery.phase();
18073 let reused = connection
18074 .execute_raw(&cx, FRESH_SQL, 2, &[], ExecuteOptions::default(), None)
18075 .await;
18076 Ok::<_, Error>((phase_after_drop, reused, connection.core.recovery.phase()))
18077 });
18078
18079 let recovered = server.join().expect("password-change server joins")?;
18080 let (phase_after_drop, reused, final_phase) = outcome?;
18081 let reused = reused.expect("fresh execute must not decode the stranded password change");
18082 assert_eq!(
18083 reused,
18084 sequential_op_decode(&synthetic_pipeline_execute_response_payload()),
18085 "reuse must decode its own response byte-identically"
18086 );
18087 assert!(recovered, "reuse must take the BREAK/drain branch");
18088 assert_eq!(phase_after_drop, SessionRecoveryPhase::BreakSent);
18089 assert_eq!(final_phase, SessionRecoveryPhase::Ready);
18090 Ok(())
18091 }
18092
18093 #[test]
18094 fn successful_change_password_disarms_recovery_before_reuse() -> Result<()> {
18095 const FRESH_SQL: &str = "select value from change_password_success_fixture";
18096 let execute_response = synthetic_pipeline_execute_response_payload();
18097 let server_execute_response = execute_response.clone();
18098 let listener = TcpListener::bind("127.0.0.1:0")?;
18099 let addr = listener.local_addr()?;
18100 let server = thread::spawn(move || -> std::io::Result<()> {
18101 use std::io::Write as _;
18102 let (mut socket, _) = listener.accept()?;
18103 socket.set_read_timeout(Some(Duration::from_secs(2)))?;
18104
18105 assert_eq!(
18106 read_one_wire_packet(&mut socket),
18107 TNS_PACKET_TYPE_DATA,
18108 "password change must send a DATA request"
18109 );
18110 socket.write_all(&data_packet(&[TNS_MSG_TYPE_END_OF_RESPONSE], true))?;
18111 socket.flush()?;
18112
18113 assert_eq!(
18114 read_one_wire_packet(&mut socket),
18115 TNS_PACKET_TYPE_DATA,
18116 "a successful password change must not cause a spurious BREAK on reuse"
18117 );
18118 socket.write_all(&data_packet(&server_execute_response, true))?;
18119 socket.flush()
18120 });
18121
18122 let runtime = build_io_runtime()?;
18123 runtime.block_on(async {
18124 let cx = test_cx()?;
18125 let stream = TcpStream::connect(addr).await?;
18126 let (read, write) = transport::plain_split(stream);
18127 let mut connection = loopback_connection(read, write);
18128 connection.combo_key = vec![0x11; 32];
18129
18130 connection
18131 .change_password(
18132 &cx,
18133 "old-change-password-proof",
18134 "new-change-password-proof",
18135 )
18136 .await?;
18137 assert_eq!(
18138 connection.core.recovery.phase(),
18139 SessionRecoveryPhase::Ready,
18140 "a completed password-change response must disarm its guard"
18141 );
18142
18143 let reused = connection
18144 .execute_raw(&cx, FRESH_SQL, 2, &[], ExecuteOptions::default(), None)
18145 .await?;
18146 assert_eq!(reused, sequential_op_decode(&execute_response));
18147 assert_eq!(
18148 connection.core.recovery.phase(),
18149 SessionRecoveryPhase::Ready
18150 );
18151 Ok::<_, Error>(())
18152 })?;
18153
18154 server
18155 .join()
18156 .expect("successful password-change server joins")?;
18157 Ok(())
18158 }
18159
18160 #[test]
18161 fn precancelled_change_password_writes_nothing_and_keeps_wire_ready() -> Result<()> {
18162 let listener = TcpListener::bind("127.0.0.1:0")?;
18163 let addr = listener.local_addr()?;
18164 let server = thread::spawn(move || -> std::io::Result<bool> {
18165 let (mut socket, _) = listener.accept()?;
18166 socket.set_read_timeout(Some(Duration::from_millis(300)))?;
18167 let mut byte = [0u8; 1];
18168 match socket.read(&mut byte) {
18169 Ok(0) => Ok(false),
18170 Ok(_) => Ok(true),
18171 Err(err)
18172 if matches!(
18173 err.kind(),
18174 std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut
18175 ) =>
18176 {
18177 Ok(false)
18178 }
18179 Err(err) => Err(err),
18180 }
18181 });
18182
18183 let runtime = build_io_runtime()?;
18184 let (phase, sequence_before, sequence_after) = runtime.block_on(async {
18185 let cx = test_cx()?;
18186 let stream = TcpStream::connect(addr).await?;
18187 let (read, write) = transport::plain_split(stream);
18188 let mut connection = loopback_connection(read, write);
18189 let sequence_before = connection.ttc_seq_num;
18190 cx.cancel_fast(CancelKind::User);
18191
18192 let err = connection
18193 .change_password(
18194 &cx,
18195 "old-change-password-proof",
18196 "new-change-password-proof",
18197 )
18198 .await
18199 .expect_err("pending cancellation stops before PASSWORD CHANGE");
18200 assert!(matches!(err, Error::Cancelled), "{err:?}");
18201 Ok::<_, Error>((
18202 connection.core.recovery.phase(),
18203 sequence_before,
18204 connection.ttc_seq_num,
18205 ))
18206 })?;
18207
18208 assert!(
18209 !server
18210 .join()
18211 .expect("pre-cancel password-change server joins")?,
18212 "pre-cancelled PASSWORD CHANGE must not write any wire bytes"
18213 );
18214 assert_eq!(phase, SessionRecoveryPhase::Ready);
18215 assert_eq!(sequence_after, sequence_before);
18216 Ok(())
18217 }
18218
18219 #[test]
18220 fn dropped_subscribe_register_mid_response_drains_before_connection_reuse() -> Result<()> {
18221 const FRESH_SQL: &str = "select value from subscribe_register_reuse_fixture";
18222
18223 let expected_request = build_subscribe_payload_with_seq(
18224 1,
18225 TNS_SUBSCR_OP_REGISTER,
18226 Some("test_user"),
18227 None,
18228 oracledb_protocol::thin::TNS_SUBSCR_NAMESPACE_DBCHANGE,
18229 None,
18230 oracledb_protocol::thin::SUBSCR_QOS_ROWIDS,
18231 0,
18232 10,
18233 0,
18234 0,
18235 0,
18236 0,
18237 ClientCapabilities::default().ttc_field_version,
18238 )?;
18239 let stranded_response = synthetic_subscribe_register_response_payload();
18240 let listener = TcpListener::bind("127.0.0.1:0")?;
18241 let addr = listener.local_addr()?;
18242 let (request_seen_tx, request_seen_rx) = std::sync::mpsc::channel();
18243 let server = thread::spawn(move || {
18244 serve_dropped_response_recovery(
18245 listener,
18246 expected_request,
18247 stranded_response,
18248 request_seen_tx,
18249 )
18250 });
18251
18252 let runtime = build_io_runtime()?;
18253 let outcome = runtime.block_on(async {
18254 let cx = test_cx()?;
18255 let stream = TcpStream::connect(addr).await?;
18256 let (read, write) = transport::plain_split(stream);
18257 let mut connection = loopback_connection(read, write);
18258
18259 {
18260 let mut subscribe = pin!(connection.subscribe_register(
18261 &cx,
18262 oracledb_protocol::thin::TNS_SUBSCR_NAMESPACE_DBCHANGE,
18263 None,
18264 oracledb_protocol::thin::SUBSCR_QOS_ROWIDS,
18265 0,
18266 10,
18267 0,
18268 0,
18269 0,
18270 ));
18271 let first = poll_fn(|task_cx| Poll::Ready(subscribe.as_mut().poll(task_cx))).await;
18272 assert!(
18273 matches!(first, Poll::Pending),
18274 "CQN register must be waiting for its response before drop"
18275 );
18276 request_seen_rx
18277 .recv_timeout(Duration::from_secs(2))
18278 .expect("server observed CQN register request");
18279 }
18280
18281 let phase_after_drop = connection.core.recovery.phase();
18282 let reused = connection
18283 .execute_raw(&cx, FRESH_SQL, 2, &[], ExecuteOptions::default(), None)
18284 .await;
18285 Ok::<_, Error>((phase_after_drop, reused, connection.core.recovery.phase()))
18286 });
18287
18288 let recovered = server.join().expect("CQN register server joins")?;
18289 let (phase_after_drop, reused, final_phase) = outcome?;
18290 let reused = reused.expect("fresh execute must not decode the stranded CQN register");
18291 assert_eq!(
18292 reused,
18293 sequential_op_decode(&synthetic_pipeline_execute_response_payload()),
18294 "reuse must decode its own response byte-identically"
18295 );
18296 assert!(recovered, "reuse must take the BREAK/drain branch");
18297 assert_eq!(phase_after_drop, SessionRecoveryPhase::BreakSent);
18298 assert_eq!(final_phase, SessionRecoveryPhase::Ready);
18299 Ok(())
18300 }
18301
18302 #[test]
18303 fn dropped_subscribe_unregister_mid_response_drains_before_connection_reuse() -> Result<()> {
18304 const FRESH_SQL: &str = "select value from subscribe_unregister_reuse_fixture";
18305 const CLIENT_ID: &[u8] = b"OCI:EP:301";
18306
18307 let expected_request = build_subscribe_payload_with_seq(
18308 1,
18309 TNS_SUBSCR_OP_UNREGISTER,
18310 Some("test_user"),
18311 Some(CLIENT_ID),
18312 oracledb_protocol::thin::TNS_SUBSCR_NAMESPACE_DBCHANGE,
18313 None,
18314 oracledb_protocol::thin::SUBSCR_QOS_ROWIDS,
18315 0,
18316 10,
18317 0,
18318 0,
18319 0,
18320 302,
18321 ClientCapabilities::default().ttc_field_version,
18322 )?;
18323 let listener = TcpListener::bind("127.0.0.1:0")?;
18324 let addr = listener.local_addr()?;
18325 let (request_seen_tx, request_seen_rx) = std::sync::mpsc::channel();
18326 let server = thread::spawn(move || {
18327 serve_dropped_response_recovery(
18328 listener,
18329 expected_request,
18330 vec![TNS_MSG_TYPE_END_OF_RESPONSE],
18331 request_seen_tx,
18332 )
18333 });
18334
18335 let runtime = build_io_runtime()?;
18336 let outcome = runtime.block_on(async {
18337 let cx = test_cx()?;
18338 let stream = TcpStream::connect(addr).await?;
18339 let (read, write) = transport::plain_split(stream);
18340 let mut connection = loopback_connection(read, write);
18341
18342 {
18343 let mut unsubscribe = pin!(connection.subscribe_unregister(
18344 &cx,
18345 302,
18346 CLIENT_ID,
18347 oracledb_protocol::thin::TNS_SUBSCR_NAMESPACE_DBCHANGE,
18348 None,
18349 oracledb_protocol::thin::SUBSCR_QOS_ROWIDS,
18350 0,
18351 10,
18352 0,
18353 0,
18354 0,
18355 ));
18356 let first =
18357 poll_fn(|task_cx| Poll::Ready(unsubscribe.as_mut().poll(task_cx))).await;
18358 assert!(
18359 matches!(first, Poll::Pending),
18360 "CQN unregister must be waiting for its response before drop"
18361 );
18362 request_seen_rx
18363 .recv_timeout(Duration::from_secs(2))
18364 .expect("server observed CQN unregister request");
18365 }
18366
18367 let phase_after_drop = connection.core.recovery.phase();
18368 let reused = connection
18369 .execute_raw(&cx, FRESH_SQL, 2, &[], ExecuteOptions::default(), None)
18370 .await;
18371 Ok::<_, Error>((phase_after_drop, reused, connection.core.recovery.phase()))
18372 });
18373
18374 let recovered = server.join().expect("CQN unregister server joins")?;
18375 let (phase_after_drop, reused, final_phase) = outcome?;
18376 let reused = reused.expect("fresh execute must not decode the stranded CQN unregister");
18377 assert_eq!(
18378 reused,
18379 sequential_op_decode(&synthetic_pipeline_execute_response_payload()),
18380 "reuse must decode its own response byte-identically"
18381 );
18382 assert!(recovered, "reuse must take the BREAK/drain branch");
18383 assert_eq!(phase_after_drop, SessionRecoveryPhase::BreakSent);
18384 assert_eq!(final_phase, SessionRecoveryPhase::Ready);
18385 Ok(())
18386 }
18387
18388 #[test]
18389 fn successful_subscribe_round_trips_disarm_recovery_before_reuse() -> Result<()> {
18390 const CLIENT_ID: &[u8] = b"OCI:EP:301";
18391 const FRESH_SQL: &str = "select value from subscribe_success_fixture";
18392
18393 let register_request = build_subscribe_payload_with_seq(
18394 1,
18395 TNS_SUBSCR_OP_REGISTER,
18396 Some("test_user"),
18397 None,
18398 oracledb_protocol::thin::TNS_SUBSCR_NAMESPACE_DBCHANGE,
18399 None,
18400 oracledb_protocol::thin::SUBSCR_QOS_ROWIDS,
18401 0,
18402 10,
18403 0,
18404 0,
18405 0,
18406 0,
18407 ClientCapabilities::default().ttc_field_version,
18408 )?;
18409 let unregister_request = build_subscribe_payload_with_seq(
18410 2,
18411 TNS_SUBSCR_OP_UNREGISTER,
18412 Some("test_user"),
18413 Some(CLIENT_ID),
18414 oracledb_protocol::thin::TNS_SUBSCR_NAMESPACE_DBCHANGE,
18415 None,
18416 oracledb_protocol::thin::SUBSCR_QOS_ROWIDS,
18417 0,
18418 10,
18419 0,
18420 0,
18421 0,
18422 302,
18423 ClientCapabilities::default().ttc_field_version,
18424 )?;
18425 let register_response = synthetic_subscribe_register_response_payload();
18426 let execute_response = synthetic_pipeline_execute_response_payload();
18427 let server_execute_response = execute_response.clone();
18428 let listener = TcpListener::bind("127.0.0.1:0")?;
18429 let addr = listener.local_addr()?;
18430 let server = thread::spawn(move || -> std::io::Result<()> {
18431 use std::io::Write as _;
18432 let (mut socket, _) = listener.accept()?;
18433 socket.set_read_timeout(Some(Duration::from_secs(2)))?;
18434
18435 assert_eq!(
18436 read_one_wire_data_payload(&mut socket),
18437 register_request,
18438 "successful CQN register request remains byte-identical"
18439 );
18440 socket.write_all(&data_packet(®ister_response, true))?;
18441 socket.flush()?;
18442
18443 assert_eq!(
18444 read_one_wire_data_payload(&mut socket),
18445 unregister_request,
18446 "successful CQN unregister must follow directly without a spurious BREAK"
18447 );
18448 socket.write_all(&data_packet(&[TNS_MSG_TYPE_END_OF_RESPONSE], true))?;
18449 socket.flush()?;
18450
18451 assert_eq!(
18452 read_one_wire_packet(&mut socket),
18453 TNS_PACKET_TYPE_DATA,
18454 "fresh execute must follow both completed CQN responses directly"
18455 );
18456 socket.write_all(&data_packet(&server_execute_response, true))?;
18457 socket.flush()
18458 });
18459
18460 let runtime = build_io_runtime()?;
18461 runtime.block_on(async {
18462 let cx = test_cx()?;
18463 let stream = TcpStream::connect(addr).await?;
18464 let (read, write) = transport::plain_split(stream);
18465 let mut connection = loopback_connection(read, write);
18466 connection.supports_end_of_response = false;
18467
18468 let registered = connection
18469 .subscribe_register(
18470 &cx,
18471 oracledb_protocol::thin::TNS_SUBSCR_NAMESPACE_DBCHANGE,
18472 None,
18473 oracledb_protocol::thin::SUBSCR_QOS_ROWIDS,
18474 0,
18475 10,
18476 0,
18477 0,
18478 0,
18479 )
18480 .await?;
18481 assert_eq!(registered.registration_id, 302);
18482 assert_eq!(registered.client_id.as_deref(), Some(CLIENT_ID));
18483 assert_eq!(
18484 connection.core.recovery.phase(),
18485 SessionRecoveryPhase::Ready,
18486 "completed classic CQN register response must disarm its guard"
18487 );
18488
18489 connection
18490 .subscribe_unregister(
18491 &cx,
18492 registered.registration_id,
18493 CLIENT_ID,
18494 oracledb_protocol::thin::TNS_SUBSCR_NAMESPACE_DBCHANGE,
18495 None,
18496 oracledb_protocol::thin::SUBSCR_QOS_ROWIDS,
18497 0,
18498 10,
18499 0,
18500 0,
18501 0,
18502 )
18503 .await?;
18504 assert_eq!(
18505 connection.core.recovery.phase(),
18506 SessionRecoveryPhase::Ready,
18507 "completed classic CQN unregister response must disarm its guard"
18508 );
18509
18510 let reused = connection
18511 .execute_raw(&cx, FRESH_SQL, 2, &[], ExecuteOptions::default(), None)
18512 .await?;
18513 assert_eq!(reused, sequential_op_decode(&execute_response));
18514 assert_eq!(
18515 connection.core.recovery.phase(),
18516 SessionRecoveryPhase::Ready
18517 );
18518 Ok::<_, Error>(())
18519 })?;
18520
18521 server.join().expect("successful CQN server joins")?;
18522 Ok(())
18523 }
18524
18525 #[test]
18526 fn dropped_sessionless_begin_mid_response_drains_before_connection_reuse() -> Result<()> {
18527 const FRESH_SQL: &str = "select value from sessionless_begin_reuse_fixture";
18528 const TRANSACTION_ID: &[u8] = b"qa-sessionless-begin";
18529
18530 let expected_request = build_tpc_txn_switch_payload_with_seq(
18531 1,
18532 0,
18533 TNS_TPC_TXN_START,
18534 TPC_TXN_FLAGS_NEW | TPC_TXN_FLAGS_SESSIONLESS,
18535 30,
18536 Some(TRANSACTION_ID),
18537 );
18538 let listener = TcpListener::bind("127.0.0.1:0")?;
18539 let addr = listener.local_addr()?;
18540 let (request_seen_tx, request_seen_rx) = std::sync::mpsc::channel();
18541 let server = thread::spawn(move || {
18542 serve_dropped_response_recovery(
18543 listener,
18544 expected_request,
18545 vec![TNS_MSG_TYPE_END_OF_RESPONSE],
18546 request_seen_tx,
18547 )
18548 });
18549
18550 let runtime = build_io_runtime()?;
18551 let outcome = runtime.block_on(async {
18552 let cx = test_cx()?;
18553 let stream = TcpStream::connect(addr).await?;
18554 let (read, write) = transport::plain_split(stream);
18555 let mut connection = loopback_connection(read, write);
18556
18557 {
18558 let mut begin =
18559 pin!(connection.begin_sessionless_transaction(&cx, TRANSACTION_ID, 30, false,));
18560 let first = poll_fn(|task_cx| Poll::Ready(begin.as_mut().poll(task_cx))).await;
18561 assert!(
18562 matches!(first, Poll::Pending),
18563 "sessionless begin must be waiting for its response before drop"
18564 );
18565 request_seen_rx
18566 .recv_timeout(Duration::from_secs(2))
18567 .expect("server observed sessionless begin request");
18568 }
18569
18570 let phase_after_drop = connection.core.recovery.phase();
18571 let reused = connection
18572 .execute_raw(&cx, FRESH_SQL, 2, &[], ExecuteOptions::default(), None)
18573 .await;
18574 Ok::<_, Error>((phase_after_drop, reused, connection.core.recovery.phase()))
18575 });
18576
18577 let recovered = server.join().expect("sessionless begin server joins")?;
18578 let (phase_after_drop, reused, final_phase) = outcome?;
18579 let reused =
18580 reused.expect("fresh execute must not decode the stranded sessionless response");
18581 assert_eq!(
18582 reused,
18583 sequential_op_decode(&synthetic_pipeline_execute_response_payload()),
18585 "reuse must decode its own response byte-identically"
18586 );
18587 assert!(recovered, "reuse must take the BREAK/drain branch");
18588 assert_eq!(phase_after_drop, SessionRecoveryPhase::BreakSent);
18589 assert_eq!(final_phase, SessionRecoveryPhase::Ready);
18590 Ok(())
18591 }
18592
18593 #[test]
18594 fn dropped_sessionless_suspend_mid_response_drains_before_connection_reuse() -> Result<()> {
18595 const FRESH_SQL: &str = "select value from sessionless_suspend_reuse_fixture";
18596 const TRANSACTION_ID: &[u8] = b"qa-sessionless-suspend";
18597
18598 let expected_request = build_tpc_txn_switch_payload_with_seq(
18599 1,
18600 0,
18601 TNS_TPC_TXN_DETACH,
18602 TPC_TXN_FLAGS_SESSIONLESS,
18603 0,
18604 None,
18605 );
18606 let listener = TcpListener::bind("127.0.0.1:0")?;
18607 let addr = listener.local_addr()?;
18608 let (request_seen_tx, request_seen_rx) = std::sync::mpsc::channel();
18609 let server = thread::spawn(move || {
18610 serve_dropped_response_recovery(
18611 listener,
18612 expected_request,
18613 vec![TNS_MSG_TYPE_END_OF_RESPONSE],
18614 request_seen_tx,
18615 )
18616 });
18617
18618 let runtime = build_io_runtime()?;
18619 let outcome = runtime.block_on(async {
18620 let cx = test_cx()?;
18621 let stream = TcpStream::connect(addr).await?;
18622 let (read, write) = transport::plain_split(stream);
18623 let mut connection = loopback_connection(read, write);
18624 connection.sessionless_data = Some(SessionlessData {
18625 transaction_id: TRANSACTION_ID.to_vec(),
18626 timeout: 30,
18627 operation: TNS_TPC_TXN_START,
18628 flags: TPC_TXN_FLAGS_NEW,
18629 piggyback_pending: false,
18630 started_on_server: false,
18631 });
18632
18633 {
18634 let mut suspend = pin!(connection.suspend_sessionless_transaction(&cx));
18635 let first = poll_fn(|task_cx| Poll::Ready(suspend.as_mut().poll(task_cx))).await;
18636 assert!(
18637 matches!(first, Poll::Pending),
18638 "sessionless suspend must be waiting for its response before drop"
18639 );
18640 request_seen_rx
18641 .recv_timeout(Duration::from_secs(2))
18642 .expect("server observed sessionless suspend request");
18643 }
18644
18645 let phase_after_drop = connection.core.recovery.phase();
18646 let reused = connection
18647 .execute_raw(&cx, FRESH_SQL, 2, &[], ExecuteOptions::default(), None)
18648 .await;
18649 Ok::<_, Error>((phase_after_drop, reused, connection.core.recovery.phase()))
18650 });
18651
18652 let recovered = server.join().expect("sessionless suspend server joins")?;
18653 let (phase_after_drop, reused, final_phase) = outcome?;
18654 let reused =
18655 reused.expect("fresh execute must not decode the stranded sessionless response");
18656 assert_eq!(
18657 reused,
18658 sequential_op_decode(&synthetic_pipeline_execute_response_payload()),
18660 "reuse must decode its own response byte-identically"
18661 );
18662 assert!(recovered, "reuse must take the BREAK/drain branch");
18663 assert_eq!(phase_after_drop, SessionRecoveryPhase::BreakSent);
18664 assert_eq!(final_phase, SessionRecoveryPhase::Ready);
18665 Ok(())
18666 }
18667
18668 #[test]
18669 fn dropped_tpc_begin_mid_response_drains_before_connection_reuse() -> Result<()> {
18670 const FRESH_SQL: &str = "select value from tpc_begin_reuse_fixture";
18671 const GTID: &[u8] = b"qa-tpc-begin";
18672 const BQUAL: &[u8] = b"branch";
18673
18674 let xid = TpcXid {
18675 format_id: 4400,
18676 global_transaction_id: GTID,
18677 branch_qualifier: BQUAL,
18678 };
18679 let expected_request = build_tpc_switch_payload_with_seq_and_version(
18680 1,
18681 TNS_TPC_TXN_START,
18682 TPC_TXN_FLAGS_NEW,
18683 0,
18684 Some(&xid),
18685 None,
18686 ClientCapabilities::default().ttc_field_version,
18687 );
18688 let listener = TcpListener::bind("127.0.0.1:0")?;
18689 let addr = listener.local_addr()?;
18690 let (request_seen_tx, request_seen_rx) = std::sync::mpsc::channel();
18691 let server = thread::spawn(move || {
18692 serve_dropped_response_recovery(
18693 listener,
18694 expected_request,
18695 vec![TNS_MSG_TYPE_END_OF_RESPONSE],
18696 request_seen_tx,
18697 )
18698 });
18699
18700 let runtime = build_io_runtime()?;
18701 let outcome = runtime.block_on(async {
18702 let cx = test_cx()?;
18703 let stream = TcpStream::connect(addr).await?;
18704 let (read, write) = transport::plain_split(stream);
18705 let mut connection = loopback_connection(read, write);
18706
18707 {
18708 let mut begin =
18709 pin!(connection.tpc_begin(&cx, 4400, GTID, BQUAL, TPC_TXN_FLAGS_NEW, 0,));
18710 let first = poll_fn(|task_cx| Poll::Ready(begin.as_mut().poll(task_cx))).await;
18711 assert!(
18712 matches!(first, Poll::Pending),
18713 "TPC begin must be waiting for its response before drop"
18714 );
18715 request_seen_rx
18716 .recv_timeout(Duration::from_secs(2))
18717 .expect("server observed TPC begin request");
18718 }
18719
18720 let phase_after_drop = connection.core.recovery.phase();
18721 let reused = connection
18722 .execute_raw(&cx, FRESH_SQL, 2, &[], ExecuteOptions::default(), None)
18723 .await;
18724 Ok::<_, Error>((phase_after_drop, reused, connection.core.recovery.phase()))
18725 });
18726
18727 let recovered = server.join().expect("TPC begin server joins")?;
18728 let (phase_after_drop, reused, final_phase) = outcome?;
18729 let reused =
18730 reused.expect("fresh execute must not decode the stranded TPC switch response");
18731 assert_eq!(
18732 reused,
18733 sequential_op_decode(&synthetic_pipeline_execute_response_payload()),
18735 "reuse must decode its own response byte-identically"
18736 );
18737 assert!(recovered, "reuse must take the BREAK/drain branch");
18738 assert_eq!(phase_after_drop, SessionRecoveryPhase::BreakSent);
18739 assert_eq!(final_phase, SessionRecoveryPhase::Ready);
18740 Ok(())
18741 }
18742
18743 #[test]
18744 fn dropped_tpc_commit_mid_response_drains_before_connection_reuse() -> Result<()> {
18745 const FRESH_SQL: &str = "select value from tpc_commit_reuse_fixture";
18746 const GTID: &[u8] = b"qa-tpc-commit";
18747 const BQUAL: &[u8] = b"branch";
18748
18749 let xid = TpcXid {
18750 format_id: 4400,
18751 global_transaction_id: GTID,
18752 branch_qualifier: BQUAL,
18753 };
18754 let expected_request = build_tpc_change_state_payload_with_seq_and_version(
18755 1,
18756 TNS_TPC_TXN_COMMIT,
18757 TNS_TPC_TXN_STATE_READ_ONLY,
18758 0,
18759 Some(&xid),
18760 None,
18761 ClientCapabilities::default().ttc_field_version,
18762 );
18763 let listener = TcpListener::bind("127.0.0.1:0")?;
18764 let addr = listener.local_addr()?;
18765 let (request_seen_tx, request_seen_rx) = std::sync::mpsc::channel();
18766 let server = thread::spawn(move || {
18767 serve_dropped_response_recovery(
18768 listener,
18769 expected_request,
18770 vec![TNS_MSG_TYPE_END_OF_RESPONSE],
18771 request_seen_tx,
18772 )
18773 });
18774
18775 let runtime = build_io_runtime()?;
18776 let outcome = runtime.block_on(async {
18777 let cx = test_cx()?;
18778 let stream = TcpStream::connect(addr).await?;
18779 let (read, write) = transport::plain_split(stream);
18780 let mut connection = loopback_connection(read, write);
18781
18782 {
18783 let mut commit = pin!(connection.tpc_commit(&cx, Some((4400, GTID, BQUAL)), true,));
18784 let first = poll_fn(|task_cx| Poll::Ready(commit.as_mut().poll(task_cx))).await;
18785 assert!(
18786 matches!(first, Poll::Pending),
18787 "TPC commit must be waiting for its response before drop"
18788 );
18789 request_seen_rx
18790 .recv_timeout(Duration::from_secs(2))
18791 .expect("server observed TPC commit request");
18792 }
18793
18794 let phase_after_drop = connection.core.recovery.phase();
18795 let reused = connection
18796 .execute_raw(&cx, FRESH_SQL, 2, &[], ExecuteOptions::default(), None)
18797 .await;
18798 Ok::<_, Error>((phase_after_drop, reused, connection.core.recovery.phase()))
18799 });
18800
18801 let recovered = server.join().expect("TPC commit server joins")?;
18802 let (phase_after_drop, reused, final_phase) = outcome?;
18803 let reused = reused.expect("fresh execute must not decode the stranded TPC response");
18804 assert_eq!(
18805 reused,
18806 sequential_op_decode(&synthetic_pipeline_execute_response_payload()),
18808 "reuse must decode its own response byte-identically"
18809 );
18810 assert!(recovered, "reuse must take the BREAK/drain branch");
18811 assert_eq!(phase_after_drop, SessionRecoveryPhase::BreakSent);
18812 assert_eq!(final_phase, SessionRecoveryPhase::Ready);
18813 Ok(())
18814 }
18815
18816 #[test]
18817 fn successful_tpc_round_trips_disarm_recovery() -> Result<()> {
18818 const GTID: &[u8] = b"qa-tpc-success";
18819 const BQUAL: &[u8] = b"branch";
18820
18821 let xid = TpcXid {
18822 format_id: 4400,
18823 global_transaction_id: GTID,
18824 branch_qualifier: BQUAL,
18825 };
18826 let expected_begin = build_tpc_switch_payload_with_seq_and_version(
18827 1,
18828 TNS_TPC_TXN_START,
18829 TPC_TXN_FLAGS_NEW,
18830 0,
18831 Some(&xid),
18832 None,
18833 ClientCapabilities::default().ttc_field_version,
18834 );
18835 let expected_commit = build_tpc_change_state_payload_with_seq_and_version(
18836 2,
18837 TNS_TPC_TXN_COMMIT,
18838 TNS_TPC_TXN_STATE_READ_ONLY,
18839 0,
18840 Some(&xid),
18841 Some(&[]),
18842 ClientCapabilities::default().ttc_field_version,
18843 );
18844 let mut commit_response = oracledb_protocol::wire::TtcWriter::new();
18845 commit_response.write_u8(oracledb_protocol::thin::TNS_MSG_TYPE_PARAMETER);
18846 commit_response.write_ub4(TNS_TPC_TXN_STATE_READ_ONLY);
18847 commit_response.write_u8(TNS_MSG_TYPE_END_OF_RESPONSE);
18848 let commit_response = commit_response.into_bytes();
18849
18850 let listener = TcpListener::bind("127.0.0.1:0")?;
18851 let addr = listener.local_addr()?;
18852 let server = thread::spawn(move || -> std::io::Result<()> {
18853 use std::io::Write as _;
18854 let (mut socket, _) = listener.accept()?;
18855 socket.set_read_timeout(Some(Duration::from_secs(2)))?;
18856
18857 assert_eq!(read_one_wire_data_payload(&mut socket), expected_begin);
18858 socket.write_all(&data_packet(&[TNS_MSG_TYPE_END_OF_RESPONSE], true))?;
18859 socket.flush()?;
18860
18861 assert_eq!(read_one_wire_data_payload(&mut socket), expected_commit);
18862 socket.write_all(&data_packet(&commit_response, true))?;
18863 socket.flush()
18864 });
18865
18866 let runtime = build_io_runtime()?;
18867 runtime.block_on(async {
18868 let cx = test_cx()?;
18869 let stream = TcpStream::connect(addr).await?;
18870 let (read, write) = transport::plain_split(stream);
18871 let mut connection = loopback_connection(read, write);
18872
18873 connection
18874 .tpc_begin(&cx, 4400, GTID, BQUAL, TPC_TXN_FLAGS_NEW, 0)
18875 .await?;
18876 assert_eq!(
18877 connection.core.recovery.phase(),
18878 SessionRecoveryPhase::Ready
18879 );
18880 connection
18881 .tpc_commit(&cx, Some((4400, GTID, BQUAL)), true)
18882 .await?;
18883 assert_eq!(
18884 connection.core.recovery.phase(),
18885 SessionRecoveryPhase::Ready
18886 );
18887 Ok::<_, Error>(())
18888 })?;
18889
18890 server.join().expect("successful TPC server joins")?;
18891 Ok(())
18892 }
18893
18894 #[test]
18895 fn dropped_direct_path_prepare_mid_response_drains_before_connection_reuse() -> Result<()> {
18896 const FRESH_SQL: &str = "select value from dpl_prepare_reuse_fixture";
18897 const CURSOR_ID: u16 = 73;
18898
18899 let column_names = Vec::<String>::new();
18900 let expected_request =
18901 oracledb_protocol::dpl::build_direct_path_prepare_payload_with_version(
18902 "QA",
18903 "DPL_DROP",
18904 &column_names,
18905 1,
18906 ClientCapabilities::default().ttc_field_version,
18907 )?;
18908 let listener = TcpListener::bind("127.0.0.1:0")?;
18909 let addr = listener.local_addr()?;
18910 let (request_seen_tx, request_seen_rx) = std::sync::mpsc::channel();
18911 let server = thread::spawn(move || {
18912 serve_dropped_response_recovery(
18913 listener,
18914 expected_request,
18915 synthetic_direct_path_prepare_response_payload(CURSOR_ID),
18916 request_seen_tx,
18917 )
18918 });
18919
18920 let runtime = build_io_runtime()?;
18921 let outcome = runtime.block_on(async {
18922 let cx = test_cx()?;
18923 let stream = TcpStream::connect(addr).await?;
18924 let (read, write) = transport::plain_split(stream);
18925 let mut connection = loopback_connection(read, write);
18926
18927 {
18928 let mut prepare =
18929 pin!(connection.direct_path_prepare(&cx, "QA", "DPL_DROP", &column_names,));
18930 let first = poll_fn(|task_cx| Poll::Ready(prepare.as_mut().poll(task_cx))).await;
18931 assert!(
18932 matches!(first, Poll::Pending),
18933 "direct path prepare must await its response before drop"
18934 );
18935 request_seen_rx
18936 .recv_timeout(Duration::from_secs(2))
18937 .expect("server observed direct path prepare request");
18938 }
18939
18940 let phase_after_drop = connection.core.recovery.phase();
18941 let reused = connection
18942 .execute_raw(&cx, FRESH_SQL, 2, &[], ExecuteOptions::default(), None)
18943 .await;
18944 Ok::<_, Error>((phase_after_drop, reused, connection.core.recovery.phase()))
18945 });
18946
18947 let recovered = server.join().expect("direct path prepare server joins")?;
18948 let (phase_after_drop, reused, final_phase) = outcome?;
18949 let reused = reused.expect("fresh execute must not decode the stranded DPL response");
18950 assert_eq!(
18951 reused,
18952 sequential_op_decode(&synthetic_pipeline_execute_response_payload()),
18954 "reuse must decode its own response byte-identically"
18955 );
18956 assert!(recovered, "reuse must take the BREAK/drain branch");
18957 assert_eq!(phase_after_drop, SessionRecoveryPhase::BreakSent);
18958 assert_eq!(final_phase, SessionRecoveryPhase::Ready);
18959 Ok(())
18960 }
18961
18962 #[test]
18963 fn dropped_direct_path_load_stream_mid_response_drains_before_connection_reuse() -> Result<()> {
18964 const FRESH_SQL: &str = "select value from dpl_stream_reuse_fixture";
18965 const CURSOR_ID: u16 = 73;
18966
18967 let stream = oracledb_protocol::dpl::encode_direct_path_rows(&[], &[], 1)?;
18968 let expected_request =
18969 oracledb_protocol::dpl::build_direct_path_load_stream_payload_with_version(
18970 CURSOR_ID,
18971 &stream,
18972 1,
18973 ClientCapabilities::default().ttc_field_version,
18974 )?;
18975 let listener = TcpListener::bind("127.0.0.1:0")?;
18976 let addr = listener.local_addr()?;
18977 let (request_seen_tx, request_seen_rx) = std::sync::mpsc::channel();
18978 let server = thread::spawn(move || {
18979 serve_dropped_response_recovery(
18980 listener,
18981 expected_request,
18982 synthetic_direct_path_simple_response_payload(),
18983 request_seen_tx,
18984 )
18985 });
18986
18987 let runtime = build_io_runtime()?;
18988 let outcome = runtime.block_on(async {
18989 let cx = test_cx()?;
18990 let stream_socket = TcpStream::connect(addr).await?;
18991 let (read, write) = transport::plain_split(stream_socket);
18992 let mut connection = loopback_connection(read, write);
18993
18994 {
18995 let mut load = pin!(connection.direct_path_load_stream(&cx, CURSOR_ID, &stream));
18996 let first = poll_fn(|task_cx| Poll::Ready(load.as_mut().poll(task_cx))).await;
18997 assert!(
18998 matches!(first, Poll::Pending),
18999 "direct path load stream must await its response before drop"
19000 );
19001 request_seen_rx
19002 .recv_timeout(Duration::from_secs(2))
19003 .expect("server observed direct path load stream request");
19004 }
19005
19006 let phase_after_drop = connection.core.recovery.phase();
19007 let reused = connection
19008 .execute_raw(&cx, FRESH_SQL, 2, &[], ExecuteOptions::default(), None)
19009 .await;
19010 Ok::<_, Error>((phase_after_drop, reused, connection.core.recovery.phase()))
19011 });
19012
19013 let recovered = server.join().expect("direct path load server joins")?;
19014 let (phase_after_drop, reused, final_phase) = outcome?;
19015 let reused = reused.expect("fresh execute must not decode the stranded DPL response");
19016 assert_eq!(
19017 reused,
19018 sequential_op_decode(&synthetic_pipeline_execute_response_payload()),
19020 "reuse must decode its own response byte-identically"
19021 );
19022 assert!(recovered, "reuse must take the BREAK/drain branch");
19023 assert_eq!(phase_after_drop, SessionRecoveryPhase::BreakSent);
19024 assert_eq!(final_phase, SessionRecoveryPhase::Ready);
19025 Ok(())
19026 }
19027
19028 #[test]
19029 fn dropped_direct_path_op_mid_response_drains_before_connection_reuse() -> Result<()> {
19030 const FRESH_SQL: &str = "select value from dpl_op_reuse_fixture";
19031 const CURSOR_ID: u16 = 73;
19032
19033 let expected_request = oracledb_protocol::dpl::build_direct_path_op_payload_with_version(
19034 CURSOR_ID,
19035 oracledb_protocol::dpl::TNS_DP_OP_FINISH,
19036 1,
19037 ClientCapabilities::default().ttc_field_version,
19038 );
19039 let listener = TcpListener::bind("127.0.0.1:0")?;
19040 let addr = listener.local_addr()?;
19041 let (request_seen_tx, request_seen_rx) = std::sync::mpsc::channel();
19042 let server = thread::spawn(move || {
19043 serve_dropped_response_recovery(
19044 listener,
19045 expected_request,
19046 synthetic_direct_path_simple_response_payload(),
19047 request_seen_tx,
19048 )
19049 });
19050
19051 let runtime = build_io_runtime()?;
19052 let outcome = runtime.block_on(async {
19053 let cx = test_cx()?;
19054 let stream = TcpStream::connect(addr).await?;
19055 let (read, write) = transport::plain_split(stream);
19056 let mut connection = loopback_connection(read, write);
19057
19058 {
19059 let mut op = pin!(connection.direct_path_op(
19060 &cx,
19061 CURSOR_ID,
19062 oracledb_protocol::dpl::TNS_DP_OP_FINISH,
19063 ));
19064 let first = poll_fn(|task_cx| Poll::Ready(op.as_mut().poll(task_cx))).await;
19065 assert!(
19066 matches!(first, Poll::Pending),
19067 "direct path op must await its response before drop"
19068 );
19069 request_seen_rx
19070 .recv_timeout(Duration::from_secs(2))
19071 .expect("server observed direct path op request");
19072 }
19073
19074 let phase_after_drop = connection.core.recovery.phase();
19075 let reused = connection
19076 .execute_raw(&cx, FRESH_SQL, 2, &[], ExecuteOptions::default(), None)
19077 .await;
19078 Ok::<_, Error>((phase_after_drop, reused, connection.core.recovery.phase()))
19079 });
19080
19081 let recovered = server.join().expect("direct path op server joins")?;
19082 let (phase_after_drop, reused, final_phase) = outcome?;
19083 let reused = reused.expect("fresh execute must not decode the stranded DPL response");
19084 assert_eq!(
19085 reused,
19086 sequential_op_decode(&synthetic_pipeline_execute_response_payload()),
19088 "reuse must decode its own response byte-identically"
19089 );
19090 assert!(recovered, "reuse must take the BREAK/drain branch");
19091 assert_eq!(phase_after_drop, SessionRecoveryPhase::BreakSent);
19092 assert_eq!(final_phase, SessionRecoveryPhase::Ready);
19093 Ok(())
19094 }
19095
19096 #[test]
19097 fn successful_direct_path_round_trips_disarm_recovery_before_reuse() -> Result<()> {
19098 const FRESH_SQL: &str = "select value from dpl_success_fixture";
19099 const CURSOR_ID: u16 = 73;
19100
19101 let column_names = Vec::<String>::new();
19102 let stream = oracledb_protocol::dpl::encode_direct_path_rows(&[], &[], 1)?;
19103 let field_version = ClientCapabilities::default().ttc_field_version;
19104 let expected_prepare =
19105 oracledb_protocol::dpl::build_direct_path_prepare_payload_with_version(
19106 "QA",
19107 "DPL_SUCCESS",
19108 &column_names,
19109 1,
19110 field_version,
19111 )?;
19112 let expected_load =
19113 oracledb_protocol::dpl::build_direct_path_load_stream_payload_with_version(
19114 CURSOR_ID,
19115 &stream,
19116 2,
19117 field_version,
19118 )?;
19119 let expected_op = oracledb_protocol::dpl::build_direct_path_op_payload_with_version(
19120 CURSOR_ID,
19121 oracledb_protocol::dpl::TNS_DP_OP_FINISH,
19122 3,
19123 field_version,
19124 );
19125 let execute_response = synthetic_pipeline_execute_response_payload();
19126 let server_execute_response = execute_response.clone();
19127 let listener = TcpListener::bind("127.0.0.1:0")?;
19128 let addr = listener.local_addr()?;
19129 let server = thread::spawn(move || -> std::io::Result<()> {
19130 use std::io::Write as _;
19131 let (mut socket, _) = listener.accept()?;
19132 socket.set_read_timeout(Some(Duration::from_secs(2)))?;
19133
19134 assert_eq!(read_one_wire_data_payload(&mut socket), expected_prepare);
19135 socket.write_all(&data_packet(
19136 &synthetic_direct_path_prepare_response_payload(CURSOR_ID),
19137 true,
19138 ))?;
19139 socket.flush()?;
19140
19141 assert_eq!(read_one_wire_data_payload(&mut socket), expected_load);
19142 socket.write_all(&data_packet(
19143 &synthetic_direct_path_simple_response_payload(),
19144 true,
19145 ))?;
19146 socket.flush()?;
19147
19148 assert_eq!(read_one_wire_data_payload(&mut socket), expected_op);
19149 socket.write_all(&data_packet(
19150 &synthetic_direct_path_simple_response_payload(),
19151 true,
19152 ))?;
19153 socket.flush()?;
19154
19155 assert_eq!(
19156 read_one_wire_packet(&mut socket),
19157 TNS_PACKET_TYPE_DATA,
19158 "reuse after DPL op must not send a spurious BREAK"
19159 );
19160 socket.write_all(&data_packet(&server_execute_response, true))?;
19161 socket.flush()
19162 });
19163
19164 let runtime = build_io_runtime()?;
19165 runtime.block_on(async {
19166 let cx = test_cx()?;
19167 let socket = TcpStream::connect(addr).await?;
19168 let (read, write) = transport::plain_split(socket);
19169 let mut connection = loopback_connection(read, write);
19170
19171 let prepared = connection
19172 .direct_path_prepare(&cx, "QA", "DPL_SUCCESS", &column_names)
19173 .await?;
19174 assert_eq!(prepared.cursor_id, CURSOR_ID);
19175 assert_eq!(
19176 connection.core.recovery.phase(),
19177 SessionRecoveryPhase::Ready
19178 );
19179
19180 connection
19181 .direct_path_load_stream(&cx, CURSOR_ID, &stream)
19182 .await?;
19183 assert_eq!(
19184 connection.core.recovery.phase(),
19185 SessionRecoveryPhase::Ready
19186 );
19187
19188 connection
19189 .direct_path_op(&cx, CURSOR_ID, oracledb_protocol::dpl::TNS_DP_OP_FINISH)
19190 .await?;
19191 assert_eq!(
19192 connection.core.recovery.phase(),
19193 SessionRecoveryPhase::Ready
19194 );
19195
19196 let reused = connection
19197 .execute_raw(&cx, FRESH_SQL, 2, &[], ExecuteOptions::default(), None)
19198 .await?;
19199 assert_eq!(reused, sequential_op_decode(&execute_response));
19200 assert_eq!(
19201 connection.core.recovery.phase(),
19202 SessionRecoveryPhase::Ready
19203 );
19204 Ok::<_, Error>(())
19205 })?;
19206
19207 server
19208 .join()
19209 .expect("successful direct path server joins")?;
19210 Ok(())
19211 }
19212
19213 #[test]
19214 fn precancelled_direct_path_prepare_writes_nothing_and_keeps_wire_ready() -> Result<()> {
19215 let listener = TcpListener::bind("127.0.0.1:0")?;
19216 let addr = listener.local_addr()?;
19217 let server = thread::spawn(move || -> std::io::Result<bool> {
19218 let (mut socket, _) = listener.accept()?;
19219 socket.set_read_timeout(Some(Duration::from_millis(300)))?;
19220 let mut byte = [0u8; 1];
19221 match socket.read(&mut byte) {
19222 Ok(0) => Ok(false),
19223 Ok(_) => Ok(true),
19224 Err(err)
19225 if matches!(
19226 err.kind(),
19227 std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut
19228 ) =>
19229 {
19230 Ok(false)
19231 }
19232 Err(err) => Err(err),
19233 }
19234 });
19235
19236 let runtime = build_io_runtime()?;
19237 let (phase, sequence_before, sequence_after) = runtime.block_on(async {
19238 let cx = test_cx()?;
19239 let stream = TcpStream::connect(addr).await?;
19240 let (read, write) = transport::plain_split(stream);
19241 let mut connection = loopback_connection(read, write);
19242 let sequence_before = connection.ttc_seq_num;
19243 cx.cancel_fast(CancelKind::User);
19244
19245 let err = connection
19246 .direct_path_prepare(&cx, "QA", "DPL_PRE_CANCEL", &[])
19247 .await
19248 .expect_err("pending cancellation stops before DPL PREPARE");
19249 assert!(matches!(err, Error::Cancelled), "{err:?}");
19250 Ok::<_, Error>((
19251 connection.core.recovery.phase(),
19252 sequence_before,
19253 connection.ttc_seq_num,
19254 ))
19255 })?;
19256
19257 assert!(
19258 !server
19259 .join()
19260 .expect("pre-cancel direct path server joins")?,
19261 "pre-cancelled DPL PREPARE must not write any wire bytes"
19262 );
19263 assert_eq!(phase, SessionRecoveryPhase::Ready);
19264 assert_eq!(sequence_after, sequence_before);
19265 Ok(())
19266 }
19267
19268 #[test]
19269 fn precancelled_subscribe_register_writes_nothing_and_keeps_wire_ready() -> Result<()> {
19270 let listener = TcpListener::bind("127.0.0.1:0")?;
19271 let addr = listener.local_addr()?;
19272 let server = thread::spawn(move || -> std::io::Result<bool> {
19273 let (mut socket, _) = listener.accept()?;
19274 socket.set_read_timeout(Some(Duration::from_millis(300)))?;
19275 let mut byte = [0u8; 1];
19276 match socket.read(&mut byte) {
19277 Ok(0) => Ok(false),
19278 Ok(_) => Ok(true),
19279 Err(err)
19280 if matches!(
19281 err.kind(),
19282 std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut
19283 ) =>
19284 {
19285 Ok(false)
19286 }
19287 Err(err) => Err(err),
19288 }
19289 });
19290
19291 let runtime = build_io_runtime()?;
19292 let (phase, sequence_before, sequence_after) = runtime.block_on(async {
19293 let cx = test_cx()?;
19294 let stream = TcpStream::connect(addr).await?;
19295 let (read, write) = transport::plain_split(stream);
19296 let mut connection = loopback_connection(read, write);
19297 let sequence_before = connection.ttc_seq_num;
19298 cx.cancel_fast(CancelKind::User);
19299
19300 let err = connection
19301 .subscribe_register(
19302 &cx,
19303 oracledb_protocol::thin::TNS_SUBSCR_NAMESPACE_DBCHANGE,
19304 None,
19305 oracledb_protocol::thin::SUBSCR_QOS_ROWIDS,
19306 0,
19307 10,
19308 0,
19309 0,
19310 0,
19311 )
19312 .await
19313 .expect_err("pending cancellation stops before CQN REGISTER");
19314 assert!(matches!(err, Error::Cancelled), "{err:?}");
19315 Ok::<_, Error>((
19316 connection.core.recovery.phase(),
19317 sequence_before,
19318 connection.ttc_seq_num,
19319 ))
19320 })?;
19321
19322 assert!(
19323 !server
19324 .join()
19325 .expect("pre-cancel CQN register server joins")?,
19326 "pre-cancelled CQN REGISTER must not write any wire bytes"
19327 );
19328 assert_eq!(phase, SessionRecoveryPhase::Ready);
19329 assert_eq!(sequence_after, sequence_before);
19330 Ok(())
19331 }
19332
19333 #[test]
19334 fn dropped_lob_read_mid_response_drains_before_connection_reuse() -> Result<()> {
19335 const TRAILING_CANCEL_ERROR: &[u8] = &[0x04, 0x01, 0x0d];
19336 const FRESH_SQL: &str = "select value from lob_read_reuse_fixture";
19337
19338 let locator = vec![0x11; 4];
19339 let stranded_response = synthetic_lob_read_response_payload(&locator, b"x", 1);
19340 let server_stranded_response = stranded_response.clone();
19341 let listener = TcpListener::bind("127.0.0.1:0")?;
19342 let addr = listener.local_addr()?;
19343 let (lob_seen_tx, lob_seen_rx) = std::sync::mpsc::channel();
19344 let server = thread::spawn(move || -> std::io::Result<bool> {
19345 use std::io::Write as _;
19346 let (mut socket, _) = listener.accept()?;
19347 socket.set_read_timeout(Some(Duration::from_secs(5)))?;
19348
19349 assert_eq!(
19350 read_one_wire_packet(&mut socket),
19351 TNS_PACKET_TYPE_DATA,
19352 "LOB read must send a DATA request"
19353 );
19354 lob_seen_tx
19355 .send(())
19356 .expect("client waits for LOB read request proof");
19357
19358 let (next_packet_type, next_body) = read_one_wire_packet_bytes(&mut socket);
19359 if next_packet_type == TNS_PACKET_TYPE_MARKER {
19360 assert_eq!(
19361 next_body,
19362 vec![1, 0, TNS_MARKER_TYPE_BREAK],
19363 "reuse must BREAK the stranded LOB read before its request"
19364 );
19365 socket.write_all(&data_packet(&server_stranded_response, true))?;
19366 socket.write_all(&marker_packet(TNS_MARKER_TYPE_BREAK))?;
19367 assert_eq!(
19368 read_marker_type(&mut socket),
19369 TNS_MARKER_TYPE_RESET,
19370 "LOB read drain must complete the RESET handshake"
19371 );
19372 socket.write_all(&marker_packet(TNS_MARKER_TYPE_RESET))?;
19373 socket.write_all(&data_packet(TRAILING_CANCEL_ERROR, true))?;
19374
19375 assert_eq!(
19376 read_one_wire_packet(&mut socket),
19377 TNS_PACKET_TYPE_DATA,
19378 "fresh execute follows the completed drain"
19379 );
19380 socket.write_all(&data_packet(
19381 &synthetic_pipeline_execute_response_payload(),
19382 true,
19383 ))?;
19384 socket.flush()?;
19385 Ok(true)
19386 } else {
19387 assert_eq!(
19388 next_packet_type, TNS_PACKET_TYPE_DATA,
19389 "without recovery the next operation is sent directly"
19390 );
19391 socket.write_all(&data_packet(&server_stranded_response, true))?;
19392 socket.write_all(&data_packet(
19393 &synthetic_pipeline_execute_response_payload(),
19394 true,
19395 ))?;
19396 socket.flush()?;
19397 Ok(false)
19398 }
19399 });
19400
19401 let runtime = build_io_runtime()?;
19402 let outcome = runtime.block_on(async {
19403 let cx = test_cx()?;
19404 let stream = TcpStream::connect(addr).await?;
19405 let (read, write) = transport::plain_split(stream);
19406 let mut connection = loopback_connection(read, write);
19407
19408 {
19409 let mut read_lob = pin!(connection.read_lob(&cx, &locator, 1, 1));
19410 let first = poll_fn(|task_cx| Poll::Ready(read_lob.as_mut().poll(task_cx))).await;
19411 assert!(
19412 matches!(first, Poll::Pending),
19413 "LOB read must be waiting for its response before drop"
19414 );
19415 lob_seen_rx
19416 .recv_timeout(Duration::from_secs(2))
19417 .expect("server observed LOB read request");
19418 }
19419
19420 let phase_after_drop = connection.core.recovery.phase();
19421 let reused = connection
19422 .execute_raw(&cx, FRESH_SQL, 2, &[], ExecuteOptions::default(), None)
19423 .await;
19424 Ok::<_, Error>((phase_after_drop, reused, connection.core.recovery.phase()))
19425 });
19426
19427 let recovered = server.join().expect("LOB read server joins")?;
19428 let (phase_after_drop, reused, final_phase) = outcome?;
19429 let reused = reused.expect("fresh execute must not decode the stranded LOB response");
19430 assert_eq!(
19431 reused,
19432 sequential_op_decode(&synthetic_pipeline_execute_response_payload()),
19433 "reuse must decode its own response byte-identically"
19434 );
19435 assert!(recovered, "reuse must take the BREAK/drain branch");
19436 assert_eq!(phase_after_drop, SessionRecoveryPhase::BreakSent);
19437 assert_eq!(final_phase, SessionRecoveryPhase::Ready);
19438 Ok(())
19439 }
19440
19441 #[test]
19442 fn dropped_aq_enqueue_mid_response_drains_before_connection_reuse() -> Result<()> {
19443 const TRAILING_CANCEL_ERROR: &[u8] = &[0x04, 0x01, 0x0d];
19444 const FRESH_SQL: &str = "select value from aq_enqueue_reuse_fixture";
19445
19446 let msgid = [0x2a; 16];
19447 let stranded_response = synthetic_aq_enqueue_response_payload(&msgid);
19448 let server_stranded_response = stranded_response.clone();
19449 let listener = TcpListener::bind("127.0.0.1:0")?;
19450 let addr = listener.local_addr()?;
19451 let (aq_seen_tx, aq_seen_rx) = std::sync::mpsc::channel();
19452 let server = thread::spawn(move || -> std::io::Result<bool> {
19453 use std::io::Write as _;
19454 let (mut socket, _) = listener.accept()?;
19455 socket.set_read_timeout(Some(Duration::from_secs(5)))?;
19456
19457 assert_eq!(
19458 read_one_wire_packet(&mut socket),
19459 TNS_PACKET_TYPE_DATA,
19460 "AQ enqueue must send a DATA request"
19461 );
19462 aq_seen_tx
19463 .send(())
19464 .expect("client waits for AQ enqueue request proof");
19465
19466 let (next_packet_type, next_body) = read_one_wire_packet_bytes(&mut socket);
19467 if next_packet_type == TNS_PACKET_TYPE_MARKER {
19468 assert_eq!(
19469 next_body,
19470 vec![1, 0, TNS_MARKER_TYPE_BREAK],
19471 "reuse must BREAK the stranded AQ enqueue before its request"
19472 );
19473 socket.write_all(&data_packet(&server_stranded_response, true))?;
19474 socket.write_all(&marker_packet(TNS_MARKER_TYPE_BREAK))?;
19475 assert_eq!(
19476 read_marker_type(&mut socket),
19477 TNS_MARKER_TYPE_RESET,
19478 "AQ enqueue drain must complete the RESET handshake"
19479 );
19480 socket.write_all(&marker_packet(TNS_MARKER_TYPE_RESET))?;
19481 socket.write_all(&data_packet(TRAILING_CANCEL_ERROR, true))?;
19482
19483 assert_eq!(
19484 read_one_wire_packet(&mut socket),
19485 TNS_PACKET_TYPE_DATA,
19486 "fresh execute follows the completed drain"
19487 );
19488 socket.write_all(&data_packet(
19489 &synthetic_pipeline_execute_response_payload(),
19490 true,
19491 ))?;
19492 socket.flush()?;
19493 Ok(true)
19494 } else {
19495 assert_eq!(
19496 next_packet_type, TNS_PACKET_TYPE_DATA,
19497 "without recovery the next operation is sent directly"
19498 );
19499 socket.write_all(&data_packet(&server_stranded_response, true))?;
19500 socket.write_all(&data_packet(
19501 &synthetic_pipeline_execute_response_payload(),
19502 true,
19503 ))?;
19504 socket.flush()?;
19505 Ok(false)
19506 }
19507 });
19508
19509 let runtime = build_io_runtime()?;
19510 let outcome = runtime.block_on(async {
19511 let cx = test_cx()?;
19512 let stream = TcpStream::connect(addr).await?;
19513 let (read, write) = transport::plain_split(stream);
19514 let mut connection = loopback_connection(read, write);
19515 let (queue, props, options) = synthetic_aq_enqueue_request();
19516
19517 {
19518 let mut enqueue = pin!(connection.aq_enq_one(&cx, &queue, &props, &options));
19519 let first = poll_fn(|task_cx| Poll::Ready(enqueue.as_mut().poll(task_cx))).await;
19520 assert!(
19521 matches!(first, Poll::Pending),
19522 "AQ enqueue must be waiting for its response before drop"
19523 );
19524 aq_seen_rx
19525 .recv_timeout(Duration::from_secs(2))
19526 .expect("server observed AQ enqueue request");
19527 }
19528
19529 let phase_after_drop = connection.core.recovery.phase();
19530 let reused = connection
19531 .execute_raw(&cx, FRESH_SQL, 2, &[], ExecuteOptions::default(), None)
19532 .await;
19533 Ok::<_, Error>((phase_after_drop, reused, connection.core.recovery.phase()))
19534 });
19535
19536 let recovered = server.join().expect("AQ enqueue server joins")?;
19537 let (phase_after_drop, reused, final_phase) = outcome?;
19538 let reused = reused.expect("fresh execute must not decode the stranded AQ response");
19539 assert_eq!(
19540 reused,
19541 sequential_op_decode(&synthetic_pipeline_execute_response_payload()),
19542 "reuse must decode its own response byte-identically"
19543 );
19544 assert!(recovered, "reuse must take the BREAK/drain branch");
19545 assert_eq!(phase_after_drop, SessionRecoveryPhase::BreakSent);
19546 assert_eq!(final_phase, SessionRecoveryPhase::Ready);
19547 Ok(())
19548 }
19549
19550 #[test]
19551 fn successful_aq_enqueue_disarms_recovery_before_reuse() -> Result<()> {
19552 const FRESH_SQL: &str = "select value from aq_enqueue_success_fixture";
19553 let msgid = [0x2a; 16];
19554 let aq_response = synthetic_aq_enqueue_response_payload(&msgid);
19555 let execute_response = synthetic_pipeline_execute_response_payload();
19556 let server_execute_response = execute_response.clone();
19557 let listener = TcpListener::bind("127.0.0.1:0")?;
19558 let addr = listener.local_addr()?;
19559 let server = thread::spawn(move || -> std::io::Result<()> {
19560 use std::io::Write as _;
19561 let (mut socket, _) = listener.accept()?;
19562 socket.set_read_timeout(Some(Duration::from_secs(2)))?;
19563
19564 assert_eq!(
19565 read_one_wire_packet(&mut socket),
19566 TNS_PACKET_TYPE_DATA,
19567 "AQ enqueue must send a DATA request"
19568 );
19569 socket.write_all(&data_packet(&aq_response, true))?;
19570 socket.flush()?;
19571
19572 assert_eq!(
19573 read_one_wire_packet(&mut socket),
19574 TNS_PACKET_TYPE_DATA,
19575 "a successful AQ enqueue must not cause a spurious BREAK on reuse"
19576 );
19577 socket.write_all(&data_packet(&server_execute_response, true))?;
19578 socket.flush()
19579 });
19580
19581 let runtime = build_io_runtime()?;
19582 runtime.block_on(async {
19583 let cx = test_cx()?;
19584 let stream = TcpStream::connect(addr).await?;
19585 let (read, write) = transport::plain_split(stream);
19586 let mut connection = loopback_connection(read, write);
19587 let (queue, props, options) = synthetic_aq_enqueue_request();
19588
19589 let assigned = connection.aq_enq_one(&cx, &queue, &props, &options).await?;
19590 assert_eq!(assigned.as_deref(), Some(&msgid[..]));
19591 assert_eq!(
19592 connection.core.recovery.phase(),
19593 SessionRecoveryPhase::Ready,
19594 "a completed AQ response must disarm its guard"
19595 );
19596
19597 let reused = connection
19598 .execute_raw(&cx, FRESH_SQL, 2, &[], ExecuteOptions::default(), None)
19599 .await?;
19600 assert_eq!(reused, sequential_op_decode(&execute_response));
19601 assert_eq!(
19602 connection.core.recovery.phase(),
19603 SessionRecoveryPhase::Ready
19604 );
19605 Ok::<_, Error>(())
19606 })?;
19607
19608 server.join().expect("successful AQ enqueue server joins")?;
19609 Ok(())
19610 }
19611
19612 #[test]
19613 fn precancelled_aq_enqueue_writes_nothing_and_keeps_wire_ready() -> Result<()> {
19614 let listener = TcpListener::bind("127.0.0.1:0")?;
19615 let addr = listener.local_addr()?;
19616 let server = thread::spawn(move || -> std::io::Result<bool> {
19617 let (mut socket, _) = listener.accept()?;
19618 socket.set_read_timeout(Some(Duration::from_millis(300)))?;
19619 let mut byte = [0u8; 1];
19620 match socket.read(&mut byte) {
19621 Ok(0) => Ok(false),
19622 Ok(_) => Ok(true),
19623 Err(err)
19624 if matches!(
19625 err.kind(),
19626 std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut
19627 ) =>
19628 {
19629 Ok(false)
19630 }
19631 Err(err) => Err(err),
19632 }
19633 });
19634
19635 let runtime = build_io_runtime()?;
19636 let (phase, sequence_before, sequence_after) = runtime.block_on(async {
19637 let cx = test_cx()?;
19638 let stream = TcpStream::connect(addr).await?;
19639 let (read, write) = transport::plain_split(stream);
19640 let mut connection = loopback_connection(read, write);
19641 let sequence_before = connection.ttc_seq_num;
19642 let (queue, props, options) = synthetic_aq_enqueue_request();
19643 cx.cancel_fast(CancelKind::User);
19644
19645 let err = connection
19646 .aq_enq_one(&cx, &queue, &props, &options)
19647 .await
19648 .expect_err("pending cancellation stops before AQ ENQUEUE");
19649 assert!(matches!(err, Error::Cancelled), "{err:?}");
19650 Ok::<_, Error>((
19651 connection.core.recovery.phase(),
19652 sequence_before,
19653 connection.ttc_seq_num,
19654 ))
19655 })?;
19656
19657 assert!(
19658 !server.join().expect("pre-cancel AQ enqueue server joins")?,
19659 "pre-cancelled AQ ENQUEUE must not write any wire bytes"
19660 );
19661 assert_eq!(phase, SessionRecoveryPhase::Ready);
19662 assert_eq!(sequence_after, sequence_before);
19663 Ok(())
19664 }
19665
19666 #[test]
19667 fn successful_lob_read_disarms_recovery_before_reuse() -> Result<()> {
19668 const FRESH_SQL: &str = "select value from lob_read_success_fixture";
19669 let locator = vec![0x11; 4];
19670 let replacement_locator = vec![0x22; 4];
19671 let lob_response = synthetic_lob_read_response_payload(&replacement_locator, b"abc", 3);
19672 let execute_response = synthetic_pipeline_execute_response_payload();
19673 let server_execute_response = execute_response.clone();
19674 let listener = TcpListener::bind("127.0.0.1:0")?;
19675 let addr = listener.local_addr()?;
19676 let server = thread::spawn(move || -> std::io::Result<()> {
19677 use std::io::Write as _;
19678 let (mut socket, _) = listener.accept()?;
19679 socket.set_read_timeout(Some(Duration::from_secs(2)))?;
19680
19681 assert_eq!(
19682 read_one_wire_packet(&mut socket),
19683 TNS_PACKET_TYPE_DATA,
19684 "LOB read must send a DATA request"
19685 );
19686 socket.write_all(&data_packet(&lob_response, true))?;
19687 socket.flush()?;
19688
19689 assert_eq!(
19690 read_one_wire_packet(&mut socket),
19691 TNS_PACKET_TYPE_DATA,
19692 "a successful LOB read must not cause a spurious BREAK on reuse"
19693 );
19694 socket.write_all(&data_packet(&server_execute_response, true))?;
19695 socket.flush()
19696 });
19697
19698 let runtime = build_io_runtime()?;
19699 runtime.block_on(async {
19700 let cx = test_cx()?;
19701 let stream = TcpStream::connect(addr).await?;
19702 let (read, write) = transport::plain_split(stream);
19703 let mut connection = loopback_connection(read, write);
19704
19705 let result = connection.read_lob(&cx, &locator, 1, 3).await?;
19706 assert_eq!(result.data.as_deref(), Some(&b"abc"[..]));
19707 assert_eq!(result.locator, replacement_locator);
19708 assert_eq!(result.amount, 3);
19709 assert_eq!(
19710 connection.core.recovery.phase(),
19711 SessionRecoveryPhase::Ready,
19712 "a completed LOB response must disarm its guard"
19713 );
19714
19715 let reused = connection
19716 .execute_raw(&cx, FRESH_SQL, 2, &[], ExecuteOptions::default(), None)
19717 .await?;
19718 assert_eq!(reused, sequential_op_decode(&execute_response));
19719 assert_eq!(
19720 connection.core.recovery.phase(),
19721 SessionRecoveryPhase::Ready
19722 );
19723 Ok::<_, Error>(())
19724 })?;
19725
19726 server.join().expect("successful LOB read server joins")?;
19727 Ok(())
19728 }
19729
19730 #[test]
19731 fn precancelled_lob_read_writes_nothing_and_keeps_wire_ready() -> Result<()> {
19732 let listener = TcpListener::bind("127.0.0.1:0")?;
19733 let addr = listener.local_addr()?;
19734 let server = thread::spawn(move || -> std::io::Result<bool> {
19735 let (mut socket, _) = listener.accept()?;
19736 socket.set_read_timeout(Some(Duration::from_millis(300)))?;
19737 let mut byte = [0u8; 1];
19738 match socket.read(&mut byte) {
19739 Ok(0) => Ok(false),
19740 Ok(_) => Ok(true),
19741 Err(err)
19742 if matches!(
19743 err.kind(),
19744 std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut
19745 ) =>
19746 {
19747 Ok(false)
19748 }
19749 Err(err) => Err(err),
19750 }
19751 });
19752
19753 let runtime = build_io_runtime()?;
19754 let (phase, sequence_before, sequence_after) = runtime.block_on(async {
19755 let cx = test_cx()?;
19756 let stream = TcpStream::connect(addr).await?;
19757 let (read, write) = transport::plain_split(stream);
19758 let mut connection = loopback_connection(read, write);
19759 let sequence_before = connection.ttc_seq_num;
19760 cx.cancel_fast(CancelKind::User);
19761
19762 let err = connection
19763 .read_lob(&cx, &[0x11; 4], 1, 1)
19764 .await
19765 .expect_err("pending cancellation stops before LOB READ");
19766 assert!(matches!(err, Error::Cancelled), "{err:?}");
19767 Ok::<_, Error>((
19768 connection.core.recovery.phase(),
19769 sequence_before,
19770 connection.ttc_seq_num,
19771 ))
19772 })?;
19773
19774 assert!(
19775 !server.join().expect("pre-cancel LOB read server joins")?,
19776 "pre-cancelled LOB READ must not write any wire bytes"
19777 );
19778 assert_eq!(phase, SessionRecoveryPhase::Ready);
19779 assert_eq!(sequence_after, sequence_before);
19780 Ok(())
19781 }
19782
19783 #[test]
19784 fn precancelled_define_fetch_writes_nothing_and_keeps_wire_ready() -> Result<()> {
19785 const CURSOR_ID: u32 = 42;
19786 let listener = TcpListener::bind("127.0.0.1:0")?;
19787 let addr = listener.local_addr()?;
19788 let server = thread::spawn(move || -> std::io::Result<bool> {
19789 let (mut socket, _) = listener.accept()?;
19790 socket.set_read_timeout(Some(Duration::from_millis(300)))?;
19791 let mut byte = [0u8; 1];
19792 match socket.read(&mut byte) {
19793 Ok(0) => Ok(false),
19794 Ok(_) => Ok(true),
19795 Err(err)
19796 if matches!(
19797 err.kind(),
19798 std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut
19799 ) =>
19800 {
19801 Ok(false)
19802 }
19803 Err(err) => Err(err),
19804 }
19805 });
19806
19807 let runtime = build_io_runtime()?;
19808 let (phase, sequence_before, sequence_after) = runtime.block_on(async {
19809 let cx = test_cx()?;
19810 let stream = TcpStream::connect(addr).await?;
19811 let (read, write) = transport::plain_split(stream);
19812 let mut connection = loopback_connection(read, write);
19813 let sequence_before = connection.ttc_seq_num;
19814 cx.cancel_fast(CancelKind::User);
19815
19816 let err = connection
19817 .define_and_fetch_rows_with_columns(
19818 &cx,
19819 CURSOR_ID,
19820 10,
19821 &[ColumnMetadata::new(
19822 "DOC",
19823 oracledb_protocol::thin::ORA_TYPE_NUM_JSON,
19824 )],
19825 None,
19826 )
19827 .await
19828 .expect_err("pending cancellation stops before DEFINE-FETCH");
19829 assert!(matches!(err, Error::Cancelled), "{err:?}");
19830 Ok::<_, Error>((
19831 connection.core.recovery.phase(),
19832 sequence_before,
19833 connection.ttc_seq_num,
19834 ))
19835 })?;
19836
19837 assert!(
19838 !server.join().expect("pre-cancel server joins")?,
19839 "pre-cancelled DEFINE-FETCH must not write any wire bytes"
19840 );
19841 assert_eq!(phase, SessionRecoveryPhase::Ready);
19842 assert_eq!(sequence_after, sequence_before);
19843 Ok(())
19844 }
19845
19846 #[test]
19847 fn successful_define_fetch_disarms_recovery_before_reuse() -> Result<()> {
19848 const CURSOR_ID: u32 = 42;
19849 const FRESH_SQL: &str = "select value from define_fetch_success_fixture";
19850 let response = synthetic_pipeline_execute_response_payload();
19851 let server_response = response.clone();
19852 let listener = TcpListener::bind("127.0.0.1:0")?;
19853 let addr = listener.local_addr()?;
19854 let server = thread::spawn(move || -> std::io::Result<()> {
19855 use std::io::Write as _;
19856 let (mut socket, _) = listener.accept()?;
19857 socket.set_read_timeout(Some(Duration::from_secs(2)))?;
19858 assert_eq!(read_one_wire_packet(&mut socket), TNS_PACKET_TYPE_DATA);
19859 socket.write_all(&data_packet(&server_response, true))?;
19860 socket.flush()?;
19861
19862 assert_eq!(
19863 read_one_wire_packet(&mut socket),
19864 TNS_PACKET_TYPE_DATA,
19865 "a successful DEFINE-FETCH must not cause a spurious BREAK on reuse"
19866 );
19867 socket.write_all(&data_packet(&server_response, true))?;
19868 socket.flush()
19869 });
19870
19871 let runtime = build_io_runtime()?;
19872 runtime.block_on(async {
19873 let cx = test_cx()?;
19874 let stream = TcpStream::connect(addr).await?;
19875 let (read, write) = transport::plain_split(stream);
19876 let mut connection = loopback_connection(read, write);
19877 let columns = vec![ColumnMetadata::new(
19878 "VALUE",
19879 oracledb_protocol::thin::ORA_TYPE_NUM_NUMBER,
19880 )];
19881
19882 let defined = connection
19883 .define_and_fetch_rows_with_columns(&cx, CURSOR_ID, 10, &columns, None)
19884 .await?;
19885 assert_eq!(defined.rows.len(), 1);
19886 assert_eq!(
19887 connection.core.recovery.phase(),
19888 SessionRecoveryPhase::Ready
19889 );
19890
19891 let reused = connection
19892 .execute_raw(&cx, FRESH_SQL, 2, &[], ExecuteOptions::default(), None)
19893 .await?;
19894 assert_eq!(reused, sequential_op_decode(&response));
19895 assert_eq!(
19896 connection.core.recovery.phase(),
19897 SessionRecoveryPhase::Ready
19898 );
19899 Ok::<_, Error>(())
19900 })?;
19901
19902 server
19903 .join()
19904 .expect("successful define-fetch server joins")?;
19905 Ok(())
19906 }
19907
19908 #[test]
19909 fn oob_cancel_on_one_lane_leaves_other_lane_undisturbed() -> Result<()> {
19910 const A_INFLIGHT: &[u8] = b"lane-A in-flight response";
19918 const A_CANCEL_ERROR: &[u8] = &[0x04, 0x01, 0x0d]; let listener_a = TcpListener::bind("127.0.0.1:0").expect("bind lane A");
19922 let addr_a = listener_a.local_addr().expect("lane A address");
19923 let server_a = thread::spawn(move || {
19924 use std::io::Write as _;
19925 let (mut socket, _) = listener_a.accept().expect("accept lane A");
19926 socket
19927 .set_read_timeout(Some(Duration::from_secs(5)))
19928 .expect("set read timeout");
19929 assert_eq!(
19930 read_marker_type(&mut socket),
19931 TNS_MARKER_TYPE_BREAK,
19932 "lane A must receive the out-of-band BREAK"
19933 );
19934 socket
19935 .write_all(&data_packet(A_INFLIGHT, true))
19936 .expect("write lane A in-flight response");
19937 socket
19938 .write_all(&marker_packet(TNS_MARKER_TYPE_BREAK))
19939 .expect("write lane A break-ack marker");
19940 assert_eq!(
19941 read_marker_type(&mut socket),
19942 TNS_MARKER_TYPE_RESET,
19943 "lane A owner drain answers the break marker with RESET"
19944 );
19945 socket
19946 .write_all(&marker_packet(TNS_MARKER_TYPE_RESET))
19947 .expect("write lane A reset-confirm marker");
19948 socket
19949 .write_all(&data_packet(A_CANCEL_ERROR, true))
19950 .expect("write lane A trailing cancel error");
19951 });
19952
19953 let listener_b = TcpListener::bind("127.0.0.1:0").expect("bind lane B");
19955 let addr_b = listener_b.local_addr().expect("lane B address");
19956 let server_b = thread::spawn(move || {
19957 use std::io::Write as _;
19958 let (mut socket, _) = listener_b.accept().expect("accept lane B");
19959 socket
19960 .set_read_timeout(Some(Duration::from_secs(5)))
19961 .expect("set read timeout");
19962 let _query_request = read_one_wire_packet(&mut socket);
19963 socket
19964 .write_all(&data_packet(
19965 &synthetic_pipeline_execute_response_payload(),
19966 true,
19967 ))
19968 .expect("write lane B query response");
19969 socket.flush().expect("flush lane B");
19970 });
19971
19972 let runtime = build_io_runtime()?;
19973 runtime.block_on(async {
19974 let cx = test_cx()?;
19975 let (read_a, write_a) = transport::plain_split(TcpStream::connect(addr_a).await?);
19976 let mut conn_a = loopback_connection(read_a, write_a);
19977 let (read_b, write_b) = transport::plain_split(TcpStream::connect(addr_b).await?);
19978 let mut conn_b = loopback_connection(read_b, write_b);
19979
19980 let mut handle = conn_a.cancel_handle()?;
19983 handle.cancel(&cx).await?;
19984 assert_eq!(
19985 conn_a.core.recovery.phase(),
19986 SessionRecoveryPhase::BreakSent,
19987 "the out-of-band handle only requests the break"
19988 );
19989 conn_a.drain_cancel_response().await?;
19990 assert_eq!(
19991 conn_a.core.recovery.phase(),
19992 SessionRecoveryPhase::Ready,
19993 "lane A recovers cleanly from the out-of-band cancel"
19994 );
19995 assert!(
19996 !conn_a.is_dead(),
19997 "an out-of-band cancel keeps lane A's session alive"
19998 );
19999
20000 let result_b = conn_b
20003 .execute_raw(
20004 &cx,
20005 "select value from synthetic_fixture",
20006 2,
20007 &[],
20008 ExecuteOptions::default(),
20009 None,
20010 )
20011 .await?;
20012 assert_eq!(
20013 result_b,
20014 sequential_op_decode(&synthetic_pipeline_execute_response_payload()),
20015 "lane B's query is undisturbed by lane A's out-of-band cancel"
20016 );
20017 assert_eq!(
20018 conn_b.core.recovery.phase(),
20019 SessionRecoveryPhase::Ready,
20020 "lane B never entered recovery"
20021 );
20022 Ok::<_, Error>(())
20023 })?;
20024
20025 server_a.join().expect("lane A server joins");
20026 server_b.join().expect("lane B server joins");
20027 Ok(())
20028 }
20029
20030 #[test]
20031 fn missing_tls_close_notify_is_tolerated_only_at_terminal_session_boundary() -> Result<()> {
20032 fn scripted_read(actions: Vec<ReadAction>) -> ScriptedRead {
20033 ScriptedRead::from_state(Arc::new(std::sync::Mutex::new(ScriptedIoState::new(
20034 actions,
20035 Vec::new(),
20036 ScriptedClock::default(),
20037 ))))
20038 }
20039
20040 fn scripted_write() -> Arc<AsyncMutex<ScriptedWrite>> {
20041 Arc::new(AsyncMutex::with_name(
20042 "session_close_notify_test_write",
20043 ScriptedWrite::from_state(Arc::new(std::sync::Mutex::new(ScriptedIoState::new(
20044 Vec::new(),
20045 Vec::new(),
20046 ScriptedClock::default(),
20047 )))),
20048 ))
20049 }
20050
20051 fn missing_notify() -> ReadAction {
20052 ReadAction::ErrorKind(
20053 std::io::ErrorKind::UnexpectedEof,
20054 ASUPERSYNC_TLS_MISSING_CLOSE_NOTIFY,
20055 )
20056 }
20057
20058 let runtime = build_io_runtime()?;
20059
20060 let mut strict_read = scripted_read(vec![missing_notify()]);
20063 let strict_error = runtime
20064 .block_on(read_packet_with_limits(
20065 &mut strict_read,
20066 PacketLengthWidth::Legacy16,
20067 ProtocolLimits::DEFAULT,
20068 ))
20069 .expect_err("pre-ACCEPT missing close_notify must remain an error");
20070 assert!(
20071 matches!(&strict_error, Error::Io(error)
20072 if error.kind() == std::io::ErrorKind::UnexpectedEof
20073 && error.to_string() == ASUPERSYNC_TLS_MISSING_CLOSE_NOTIFY),
20074 "strict packet read must preserve the typed TLS EOF, got {strict_error:?}"
20075 );
20076
20077 let mut clean_close = scripted_read(vec![missing_notify()]);
20080 let clean_disposition = runtime.block_on(async {
20081 let cx = test_cx()?;
20082 let probe = |_bytes: &[u8]| false;
20083 read_session_close_response_with_limits(
20084 &mut clean_close,
20085 &cx,
20086 &scripted_write(),
20087 false,
20088 &probe,
20089 ProtocolLimits::DEFAULT,
20090 )
20091 .await
20092 })?;
20093 assert_eq!(
20094 clean_disposition,
20095 SessionCloseDisposition::PeerHardClosed,
20096 "a hard close at the empty terminal-response boundary is distinct from a response"
20097 );
20098
20099 for classic in [false, true] {
20104 let state = Arc::new(std::sync::Mutex::new(ScriptedIoState::new(
20105 vec![missing_notify()],
20106 Vec::new(),
20107 ScriptedClock::default(),
20108 )));
20109 let mut core = ConnectionCore::<ScriptedTransport>::from_halves(
20110 ScriptedRead::from_state(Arc::clone(&state)),
20111 ScriptedWrite::from_state(Arc::clone(&state)),
20112 "session_close_hard_close_write",
20113 );
20114 runtime.block_on(async {
20115 let cx = test_cx()?;
20116 core.finish_session_close(&cx, classic, |_bytes| false)
20117 .await
20118 })?;
20119 assert!(
20120 state
20121 .lock()
20122 .expect("session-close scripted state")
20123 .is_consumed(),
20124 "peer hard-close must consume the read without attempting a write (classic={classic})"
20125 );
20126 }
20127
20128 let complete = data_packet(&[TNS_MSG_TYPE_END_OF_RESPONSE], true);
20131 let mut complete_close =
20132 scripted_read(vec![ReadAction::bytes(complete, Some(3)), missing_notify()]);
20133 let complete_disposition = runtime.block_on(async {
20134 let cx = test_cx()?;
20135 let probe = |_bytes: &[u8]| true;
20136 read_session_close_response_with_limits(
20137 &mut complete_close,
20138 &cx,
20139 &scripted_write(),
20140 false,
20141 &probe,
20142 ProtocolLimits::DEFAULT,
20143 )
20144 .await
20145 })?;
20146 assert_eq!(
20147 complete_disposition,
20148 SessionCloseDisposition::ResponseComplete,
20149 "a framed terminal response must not be classified as a peer hard-close"
20150 );
20151
20152 let complete = data_packet(&[TNS_MSG_TYPE_END_OF_RESPONSE], true);
20154 let eof = encode_packet(
20155 TNS_PACKET_TYPE_DATA,
20156 0,
20157 Some(oracledb_protocol::thin::TNS_DATA_FLAGS_EOF),
20158 &[],
20159 PacketLengthWidth::Large32,
20160 )?;
20161 let state = Arc::new(std::sync::Mutex::new(ScriptedIoState::new(
20162 vec![ReadAction::bytes(complete, Some(3))],
20163 vec![WriteAction::expect_bytes(eof, Some(2))],
20164 ScriptedClock::default(),
20165 )));
20166 let mut core = ConnectionCore::<ScriptedTransport>::from_halves(
20167 ScriptedRead::from_state(Arc::clone(&state)),
20168 ScriptedWrite::from_state(Arc::clone(&state)),
20169 "session_close_complete_response_write",
20170 );
20171 runtime.block_on(async {
20172 let cx = test_cx()?;
20173 core.finish_session_close(&cx, false, |_bytes| true).await
20174 })?;
20175 assert!(
20176 state
20177 .lock()
20178 .expect("session-close scripted state")
20179 .is_consumed(),
20180 "a complete terminal response must retain the final TNS EOF write"
20181 );
20182
20183 let mut truncated = data_packet(b"truncated", true);
20186 truncated.pop();
20187 let mut truncated_close = scripted_read(vec![
20188 ReadAction::bytes(truncated, Some(3)),
20189 missing_notify(),
20190 ]);
20191 let truncated_error = runtime
20192 .block_on(async {
20193 let cx = test_cx()?;
20194 let probe = |_bytes: &[u8]| false;
20195 read_session_close_response_with_limits(
20196 &mut truncated_close,
20197 &cx,
20198 &scripted_write(),
20199 false,
20200 &probe,
20201 ProtocolLimits::DEFAULT,
20202 )
20203 .await
20204 })
20205 .expect_err("partial application response must not be accepted as clean close");
20206 assert!(
20207 matches!(&truncated_error, Error::Io(error)
20208 if error.kind() == std::io::ErrorKind::UnexpectedEof
20209 && error.to_string() == ASUPERSYNC_TLS_MISSING_CLOSE_NOTIFY),
20210 "truncated response must preserve the typed TLS EOF, got {truncated_error:?}"
20211 );
20212
20213 Ok(())
20214 }
20215
20216 #[test]
20217 fn scripted_transport_injects_errors_and_eof() -> Result<()> {
20218 let payload = encode_packet(
20219 TNS_PACKET_TYPE_CONNECT,
20220 0,
20221 None,
20222 b"FAULT",
20223 PacketLengthWidth::Legacy16,
20224 )?;
20225
20226 let runtime = build_io_runtime()?;
20227
20228 let read_error = Arc::new(std::sync::Mutex::new(ScriptedIoState::new(
20229 vec![ReadAction::Error("scripted read fault")],
20230 Vec::new(),
20231 ScriptedClock::default(),
20232 )));
20233 let mut core = ConnectionCore::<ScriptedTransport>::from_halves(
20234 ScriptedRead::from_state(read_error),
20235 ScriptedWrite::from_state(Arc::new(std::sync::Mutex::new(ScriptedIoState::new(
20236 Vec::new(),
20237 Vec::new(),
20238 ScriptedClock::default(),
20239 )))),
20240 "scripted_read_error_write",
20241 );
20242 let read_err = runtime.block_on(async {
20243 match core.read_packet(PacketLengthWidth::Legacy16).await {
20244 Ok(_) => Err(Error::Runtime(
20245 "scripted read fault unexpectedly succeeded".into(),
20246 )),
20247 Err(err) => Ok(err),
20248 }
20249 })?;
20250 assert!(
20251 read_err.to_string().contains("scripted read fault"),
20252 "scripted read error should keep its diagnostic"
20253 );
20254
20255 let read_eof = Arc::new(std::sync::Mutex::new(ScriptedIoState::new(
20256 vec![ReadAction::Eof],
20257 Vec::new(),
20258 ScriptedClock::default(),
20259 )));
20260 let mut core = ConnectionCore::<ScriptedTransport>::from_halves(
20261 ScriptedRead::from_state(read_eof),
20262 ScriptedWrite::from_state(Arc::new(std::sync::Mutex::new(ScriptedIoState::new(
20263 Vec::new(),
20264 Vec::new(),
20265 ScriptedClock::default(),
20266 )))),
20267 "scripted_read_eof_write",
20268 );
20269 let eof_err = runtime.block_on(async {
20270 match core.read_packet(PacketLengthWidth::Legacy16).await {
20271 Ok(_) => Err(Error::Runtime(
20272 "scripted read EOF unexpectedly succeeded".into(),
20273 )),
20274 Err(err) => Ok(err),
20275 }
20276 })?;
20277 let eof_message = eof_err.to_string().to_ascii_lowercase();
20278 assert!(
20279 matches!(&eof_err, Error::Io(_))
20280 && (eof_message.contains("failed to fill whole buffer")
20281 || eof_message.contains("early eof")
20282 || eof_message.contains("unexpected eof")
20283 || eof_message.contains("end of file")),
20284 "scripted EOF should surface as an incomplete read, got {eof_err:?}"
20285 );
20286
20287 let write_error = Arc::new(std::sync::Mutex::new(ScriptedIoState::new(
20288 Vec::new(),
20289 vec![WriteAction::Error("scripted write fault")],
20290 ScriptedClock::default(),
20291 )));
20292 let core = ConnectionCore::<ScriptedTransport>::from_halves(
20293 ScriptedRead::from_state(Arc::new(std::sync::Mutex::new(ScriptedIoState::new(
20294 Vec::new(),
20295 Vec::new(),
20296 ScriptedClock::default(),
20297 )))),
20298 ScriptedWrite::from_state(write_error),
20299 "scripted_write_error_write",
20300 );
20301 let write_err = runtime.block_on(async {
20302 let cx = test_cx()?;
20303 match core.write_all(&cx, &payload).await {
20304 Ok(()) => Err(Error::Runtime(
20305 "scripted write error unexpectedly succeeded".into(),
20306 )),
20307 Err(err) => Ok(err),
20308 }
20309 })?;
20310 assert!(
20311 write_err.to_string().contains("scripted write fault"),
20312 "scripted write error should keep its diagnostic"
20313 );
20314
20315 let write_eof = Arc::new(std::sync::Mutex::new(ScriptedIoState::new(
20316 Vec::new(),
20317 vec![WriteAction::Eof],
20318 ScriptedClock::default(),
20319 )));
20320 let core = ConnectionCore::<ScriptedTransport>::from_halves(
20321 ScriptedRead::from_state(Arc::new(std::sync::Mutex::new(ScriptedIoState::new(
20322 Vec::new(),
20323 Vec::new(),
20324 ScriptedClock::default(),
20325 )))),
20326 ScriptedWrite::from_state(write_eof),
20327 "scripted_write_eof_write",
20328 );
20329 let write_eof_err = runtime.block_on(async {
20330 let cx = test_cx()?;
20331 match core.write_all(&cx, &payload).await {
20332 Ok(()) => Err(Error::Runtime(
20333 "scripted write EOF unexpectedly succeeded".into(),
20334 )),
20335 Err(err) => Ok(err),
20336 }
20337 })?;
20338 assert!(
20339 write_eof_err
20340 .to_string()
20341 .contains("failed to write whole buffer")
20342 || write_eof_err.to_string().contains("write zero"),
20343 "scripted write EOF should surface as an incomplete write"
20344 );
20345
20346 Ok(())
20347 }
20348
20349 #[test]
20350 fn scripted_transport_rejects_mismatched_and_extra_writes() -> Result<()> {
20351 let expected = encode_packet(
20352 TNS_PACKET_TYPE_CONNECT,
20353 0,
20354 None,
20355 b"EXPECTED",
20356 PacketLengthWidth::Legacy16,
20357 )?;
20358 let actual = encode_packet(
20359 TNS_PACKET_TYPE_CONNECT,
20360 0,
20361 None,
20362 b"ACTUAL",
20363 PacketLengthWidth::Legacy16,
20364 )?;
20365
20366 let runtime = build_io_runtime()?;
20367 let mismatch = Arc::new(std::sync::Mutex::new(ScriptedIoState::new(
20368 Vec::new(),
20369 vec![WriteAction::expect_bytes(expected, None)],
20370 ScriptedClock::default(),
20371 )));
20372 let core = ConnectionCore::<ScriptedTransport>::from_halves(
20373 ScriptedRead::from_state(Arc::new(std::sync::Mutex::new(ScriptedIoState::new(
20374 Vec::new(),
20375 Vec::new(),
20376 ScriptedClock::default(),
20377 )))),
20378 ScriptedWrite::from_state(mismatch),
20379 "scripted_mismatch_write",
20380 );
20381 let mismatch_err = runtime.block_on(async {
20382 let cx = test_cx()?;
20383 match core.write_all(&cx, &actual).await {
20384 Ok(()) => Err(Error::Runtime(
20385 "scripted mismatched write unexpectedly succeeded".into(),
20386 )),
20387 Err(err) => Ok(err),
20388 }
20389 })?;
20390 assert!(
20391 mismatch_err.to_string().contains("write mismatch"),
20392 "scripted write mismatch should be explicit"
20393 );
20394
20395 let extra = Arc::new(std::sync::Mutex::new(ScriptedIoState::new(
20396 Vec::new(),
20397 Vec::new(),
20398 ScriptedClock::default(),
20399 )));
20400 let core = ConnectionCore::<ScriptedTransport>::from_halves(
20401 ScriptedRead::from_state(Arc::new(std::sync::Mutex::new(ScriptedIoState::new(
20402 Vec::new(),
20403 Vec::new(),
20404 ScriptedClock::default(),
20405 )))),
20406 ScriptedWrite::from_state(extra),
20407 "scripted_extra_write",
20408 );
20409 let extra_err = runtime.block_on(async {
20410 let cx = test_cx()?;
20411 match core.write_all(&cx, &actual).await {
20412 Ok(()) => Err(Error::Runtime(
20413 "scripted extra write unexpectedly succeeded".into(),
20414 )),
20415 Err(err) => Ok(err),
20416 }
20417 })?;
20418 assert!(
20419 extra_err.to_string().contains("unexpected write"),
20420 "scripted extra write should be rejected"
20421 );
20422
20423 Ok(())
20424 }
20425
20426 #[cfg(feature = "cassette")]
20427 #[test]
20428 fn connection_core_routes_connect_execute_fetch_over_replay_transport() -> Result<()> {
20429 use oracledb_protocol::net::cassette::{self, Direction};
20430
20431 const EXECUTE_BODY: &[u8] = b"replay execute payload";
20432 const FETCH_BODY: &[u8] = b"replay fetch payload";
20433 const EXECUTE_RESPONSE: &[u8] = b"replay execute response";
20434 const FETCH_RESPONSE: &[u8] = b"replay fetch response";
20435
20436 let connect_packet = encode_packet(
20437 TNS_PACKET_TYPE_CONNECT,
20438 0,
20439 None,
20440 b"REPLAY-CONNECT",
20441 PacketLengthWidth::Legacy16,
20442 )?;
20443 let accept_packet = encode_packet(
20444 TNS_PACKET_TYPE_ACCEPT,
20445 0,
20446 None,
20447 b"REPLAY-ACCEPT",
20448 PacketLengthWidth::Legacy16,
20449 )?;
20450 let execute_packet = encode_packet(
20451 TNS_PACKET_TYPE_DATA,
20452 0,
20453 Some(0),
20454 EXECUTE_BODY,
20455 PacketLengthWidth::Large32,
20456 )?;
20457 let fetch_packet = encode_packet(
20458 TNS_PACKET_TYPE_DATA,
20459 0,
20460 Some(0),
20461 FETCH_BODY,
20462 PacketLengthWidth::Large32,
20463 )?;
20464
20465 let mut cassette_bytes = Vec::new();
20466 cassette::write_header(&mut cassette_bytes);
20467 cassette::write_frame(
20468 &mut cassette_bytes,
20469 Direction::ClientToServer,
20470 0,
20471 &connect_packet,
20472 );
20473 cassette::write_frame(
20474 &mut cassette_bytes,
20475 Direction::ServerToClient,
20476 1,
20477 &accept_packet,
20478 );
20479 cassette::write_frame(
20480 &mut cassette_bytes,
20481 Direction::ClientToServer,
20482 2,
20483 &execute_packet,
20484 );
20485 cassette::write_frame(
20486 &mut cassette_bytes,
20487 Direction::ServerToClient,
20488 3,
20489 &data_packet(EXECUTE_RESPONSE, true),
20490 );
20491 cassette::write_frame(
20492 &mut cassette_bytes,
20493 Direction::ClientToServer,
20494 4,
20495 &fetch_packet,
20496 );
20497 cassette::write_frame(
20498 &mut cassette_bytes,
20499 Direction::ServerToClient,
20500 5,
20501 &data_packet(FETCH_RESPONSE, true),
20502 );
20503
20504 let (read, write) =
20505 transport::replay_split(&cassette_bytes, transport::ReplayWriteMode::Check)
20506 .map_err(|err| Error::Runtime(format!("invalid replay cassette: {err}")))?;
20507 let mut core =
20508 ConnectionCore::<DriverTransport>::from_halves(read, write, "replay_core_write");
20509
20510 let runtime = build_io_runtime()?;
20511 runtime.block_on(async {
20512 let cx = test_cx()?;
20513 core.write_all(&cx, &connect_packet).await?;
20514 let accept = core.read_packet(PacketLengthWidth::Legacy16).await?;
20515 assert_eq!(accept.packet_type, TNS_PACKET_TYPE_ACCEPT);
20516 assert_eq!(accept.payload, b"REPLAY-ACCEPT");
20517
20518 core.send_data_packet(&cx, EXECUTE_BODY, 8192).await?;
20519 let execute_response = core.read_data_response(&cx).await?;
20520 assert_eq!(execute_response, EXECUTE_RESPONSE);
20521
20522 core.send_data_packet(&cx, FETCH_BODY, 8192).await?;
20523 let fetch_response = core.read_data_response(&cx).await?;
20524 assert_eq!(fetch_response, FETCH_RESPONSE);
20525 Ok::<_, Error>(())
20526 })?;
20527 Ok(())
20528 }
20529
20530 #[cfg(feature = "cassette")]
20534 #[test]
20535 fn support_capture_scrubbed_cassette_replays_query_failure() -> Result<()> {
20536 use oracledb_protocol::net::cassette::{self, Direction};
20537
20538 const QUERY_BODY: &[u8] = b"SELECT * FROM missing_table";
20539 const ERROR_RESPONSE: &[u8] = b"ORA-00942: table or view does not exist";
20541
20542 let connect_packet = encode_packet(
20543 TNS_PACKET_TYPE_CONNECT,
20544 0,
20545 None,
20546 b"K6-CONNECT",
20547 PacketLengthWidth::Legacy16,
20548 )?;
20549 let accept_packet = encode_packet(
20550 TNS_PACKET_TYPE_ACCEPT,
20551 0,
20552 None,
20553 b"K6-ACCEPT",
20554 PacketLengthWidth::Legacy16,
20555 )?;
20556 let mut auth_body = b"AUTH_PASSWORD=hunter2 AUTH_SESSKEY=".to_vec();
20559 auth_body.extend_from_slice(&[0x5A_u8; 48]);
20560 let auth_request = encode_packet(
20561 TNS_PACKET_TYPE_DATA,
20562 0,
20563 Some(0),
20564 &auth_body,
20565 PacketLengthWidth::Large32,
20566 )?;
20567 let auth_response_body = b"AUTH_SVR SESSION_KEY=deadbeefcafef00ddeadbeefcafef00d".to_vec();
20568 let query_packet = encode_packet(
20569 TNS_PACKET_TYPE_DATA,
20570 0,
20571 Some(0),
20572 QUERY_BODY,
20573 PacketLengthWidth::Large32,
20574 )?;
20575
20576 let mut raw = Vec::new();
20578 cassette::write_header(&mut raw);
20579 cassette::write_frame(&mut raw, Direction::ClientToServer, 0, &connect_packet);
20580 cassette::write_frame(&mut raw, Direction::ServerToClient, 1, &accept_packet);
20581 cassette::write_frame(&mut raw, Direction::ClientToServer, 2, &auth_request);
20582 cassette::write_frame(
20583 &mut raw,
20584 Direction::ServerToClient,
20585 3,
20586 &data_packet(&auth_response_body, true),
20587 );
20588 cassette::write_frame(&mut raw, Direction::ClientToServer, 4, &query_packet);
20589 cassette::write_frame(
20590 &mut raw,
20591 Direction::ServerToClient,
20592 5,
20593 &data_packet(ERROR_RESPONSE, true),
20594 );
20595
20596 assert!(
20598 !transport::scan_for_secret_fields(&raw).is_empty(),
20599 "raw capture is expected to contain auth secrets"
20600 );
20601
20602 let (scrubbed, report) = transport::scrub_and_gate(&raw)
20604 .map_err(|e| Error::Runtime(format!("scrub gate refused unexpectedly: {e}")))?;
20605 assert!(report.redacted_frames >= 1, "auth frames must be redacted");
20606 assert!(
20608 transport::scan_for_secret_fields(&scrubbed).is_empty(),
20609 "scrubbed artifact must pass the secret scan"
20610 );
20611
20612 let path = std::env::temp_dir().join(format!(
20614 "oracledb-k6-replay-{}.tns-cassette",
20615 std::process::id()
20616 ));
20617 std::fs::write(&path, &scrubbed).map_err(|e| Error::Runtime(e.to_string()))?;
20618 let from_disk = std::fs::read(&path).map_err(|e| Error::Runtime(e.to_string()))?;
20619 assert!(transport::scan_for_secret_fields(&from_disk).is_empty());
20620
20621 let (read, write) = transport::replay_split(&from_disk, transport::ReplayWriteMode::Ignore)
20623 .map_err(|err| Error::Runtime(format!("invalid replay cassette: {err}")))?;
20624 let mut core =
20625 ConnectionCore::<DriverTransport>::from_halves(read, write, "k6_replay_write");
20626 let runtime = build_io_runtime()?;
20627 let replay = runtime.block_on(async {
20628 let cx = test_cx()?;
20629 core.write_all(&cx, &connect_packet).await?;
20630 let accept = core.read_packet(PacketLengthWidth::Legacy16).await?;
20631 assert_eq!(accept.packet_type, TNS_PACKET_TYPE_ACCEPT);
20632
20633 core.send_data_packet(&cx, &auth_body, 8192).await?;
20636 let _auth_response = core.read_data_response(&cx).await?;
20637
20638 core.send_data_packet(&cx, QUERY_BODY, 8192).await?;
20639 core.read_data_response(&cx).await
20640 });
20641 let _ = std::fs::remove_file(&path);
20642 let failure = replay?;
20643 assert_eq!(
20644 failure, ERROR_RESPONSE,
20645 "offline replay must reproduce the recorded query failure"
20646 );
20647 Ok(())
20648 }
20649
20650 #[cfg(feature = "cassette")]
20651 #[test]
20652 fn connection_core_routes_connect_execute_fetch_over_recording_transport() -> Result<()> {
20653 use oracledb_protocol::net::cassette::{self, Direction};
20654 use std::io::Write as _;
20655
20656 const EXECUTE_BODY: &[u8] = b"recording execute payload";
20657 const FETCH_BODY: &[u8] = b"recording fetch payload";
20658 const EXECUTE_RESPONSE: &[u8] = b"recording execute response";
20659 const FETCH_RESPONSE: &[u8] = b"recording fetch response";
20660
20661 let connect_packet = encode_packet(
20662 TNS_PACKET_TYPE_CONNECT,
20663 0,
20664 None,
20665 b"RECORDING-CONNECT",
20666 PacketLengthWidth::Legacy16,
20667 )?;
20668 let accept_packet = encode_packet(
20669 TNS_PACKET_TYPE_ACCEPT,
20670 0,
20671 None,
20672 b"RECORDING-ACCEPT",
20673 PacketLengthWidth::Legacy16,
20674 )?;
20675 let execute_packet = encode_packet(
20676 TNS_PACKET_TYPE_DATA,
20677 0,
20678 Some(0),
20679 EXECUTE_BODY,
20680 PacketLengthWidth::Large32,
20681 )?;
20682 let fetch_packet = encode_packet(
20683 TNS_PACKET_TYPE_DATA,
20684 0,
20685 Some(0),
20686 FETCH_BODY,
20687 PacketLengthWidth::Large32,
20688 )?;
20689 let execute_response_packet = data_packet(EXECUTE_RESPONSE, true);
20690 let fetch_response_packet = data_packet(FETCH_RESPONSE, true);
20691
20692 let listener = TcpListener::bind("127.0.0.1:0")?;
20693 let addr = listener.local_addr()?;
20694 let server_connect = connect_packet.clone();
20695 let server_accept = accept_packet.clone();
20696 let server_execute = execute_packet.clone();
20697 let server_fetch = fetch_packet.clone();
20698 let server_execute_response = execute_response_packet.clone();
20699 let server_fetch_response = fetch_response_packet.clone();
20700 let server = thread::spawn(move || -> std::io::Result<()> {
20701 let (mut socket, _) = listener.accept()?;
20702 socket.set_read_timeout(Some(Duration::from_secs(5)))?;
20703
20704 let mut got = vec![0u8; server_connect.len()];
20705 socket.read_exact(&mut got)?;
20706 assert_eq!(got, server_connect);
20707 socket.write_all(&server_accept)?;
20708
20709 let mut got = vec![0u8; server_execute.len()];
20710 socket.read_exact(&mut got)?;
20711 assert_eq!(got, server_execute);
20712 socket.write_all(&server_execute_response)?;
20713
20714 let mut got = vec![0u8; server_fetch.len()];
20715 socket.read_exact(&mut got)?;
20716 assert_eq!(got, server_fetch);
20717 socket.write_all(&server_fetch_response)?;
20718 Ok(())
20719 });
20720
20721 let runtime = build_io_runtime()?;
20722 let cassette_bytes = runtime.block_on(async {
20723 let cx = test_cx()?;
20724 let scope = transport::capture_scope();
20725 let stream = TcpStream::connect(addr).await?;
20726 let (read, write) = transport::plain_split(stream);
20727 let mut core =
20728 ConnectionCore::<DriverTransport>::from_halves(read, write, "recording_core_write");
20729
20730 core.write_all(&cx, &connect_packet).await?;
20731 let accept = core.read_packet(PacketLengthWidth::Legacy16).await?;
20732 assert_eq!(accept.packet_type, TNS_PACKET_TYPE_ACCEPT);
20733 assert_eq!(accept.payload, b"RECORDING-ACCEPT");
20734
20735 core.send_data_packet(&cx, EXECUTE_BODY, 8192).await?;
20736 let execute_response = core.read_data_response(&cx).await?;
20737 assert_eq!(execute_response, EXECUTE_RESPONSE);
20738
20739 core.send_data_packet(&cx, FETCH_BODY, 8192).await?;
20740 let fetch_response = core.read_data_response(&cx).await?;
20741 assert_eq!(fetch_response, FETCH_RESPONSE);
20742
20743 Ok::<_, Error>(scope.to_cassette_bytes())
20744 })?;
20745
20746 server
20747 .join()
20748 .map_err(|_| Error::Runtime("recording test server thread panicked".into()))??;
20749 let frames = cassette::decode_all(&cassette_bytes)
20750 .map_err(|err| Error::Runtime(format!("invalid recorded cassette: {err}")))?;
20751 let (accept_header, accept_payload) = accept_packet.split_at(8);
20752 let (execute_response_header, execute_response_payload) =
20753 execute_response_packet.split_at(8);
20754 let (fetch_response_header, fetch_response_payload) = fetch_response_packet.split_at(8);
20755
20756 assert_eq!(frames.len(), 9);
20757 assert_eq!(frames[0].direction, Direction::ClientToServer);
20758 assert_eq!(frames[0].bytes, connect_packet);
20759 assert_eq!(frames[1].direction, Direction::ServerToClient);
20760 assert_eq!(frames[1].bytes, accept_header);
20761 assert_eq!(frames[2].direction, Direction::ServerToClient);
20762 assert_eq!(frames[2].bytes, accept_payload);
20763 assert_eq!(frames[3].direction, Direction::ClientToServer);
20764 assert_eq!(frames[3].bytes, execute_packet);
20765 assert_eq!(frames[4].direction, Direction::ServerToClient);
20766 assert_eq!(frames[4].bytes, execute_response_header);
20767 assert_eq!(frames[5].direction, Direction::ServerToClient);
20768 assert_eq!(frames[5].bytes, execute_response_payload);
20769 assert_eq!(frames[6].direction, Direction::ClientToServer);
20770 assert_eq!(frames[6].bytes, fetch_packet);
20771 assert_eq!(frames[7].direction, Direction::ServerToClient);
20772 assert_eq!(frames[7].bytes, fetch_response_header);
20773 assert_eq!(frames[8].direction, Direction::ServerToClient);
20774 assert_eq!(frames[8].bytes, fetch_response_payload);
20775 Ok(())
20776 }
20777
20778 fn read_marker_type(socket: &mut std::net::TcpStream) -> u8 {
20782 let mut packet = [0u8; 11];
20783 socket.read_exact(&mut packet).expect("read marker packet");
20784 assert_eq!(
20785 packet[4], TNS_PACKET_TYPE_MARKER,
20786 "expected a MARKER packet"
20787 );
20788 packet[10]
20789 }
20790
20791 #[test]
20803 fn break_and_drain_consumes_inflight_response_and_reset_then_next_read_is_fresh() {
20804 const INFLIGHT_BODY: &[u8] = &[0xDE, 0xAD, 0xBE, 0xEF];
20807 const ERROR_BODY: &[u8] = &[0x04, 0x01, 0x02];
20809 const FRESH_BODY: &[u8] = &[0x11, 0x22, 0x33, 0x44, 0x55];
20811
20812 let listener = TcpListener::bind("127.0.0.1:0").expect("bind local listener");
20813 let addr = listener.local_addr().expect("listener address");
20814 let server = thread::spawn(move || {
20815 let (mut socket, _) = listener.accept().expect("accept test client");
20816 socket
20817 .set_read_timeout(Some(Duration::from_secs(5)))
20818 .expect("set read timeout");
20819 use std::io::Write as _;
20820
20821 assert_eq!(
20823 read_marker_type(&mut socket),
20824 TNS_MARKER_TYPE_BREAK,
20825 "client must send a BREAK marker first"
20826 );
20827 socket
20831 .write_all(&data_packet(INFLIGHT_BODY, true))
20832 .expect("write in-flight response");
20833 socket
20835 .write_all(&marker_packet(TNS_MARKER_TYPE_BREAK))
20836 .expect("write break-ack marker");
20837 assert_eq!(
20839 read_marker_type(&mut socket),
20840 TNS_MARKER_TYPE_RESET,
20841 "client must answer the marker with a RESET"
20842 );
20843 socket
20844 .write_all(&marker_packet(TNS_MARKER_TYPE_RESET))
20845 .expect("write reset-confirm marker");
20846 socket
20848 .write_all(&data_packet(ERROR_BODY, true))
20849 .expect("write trailing error packet");
20850 socket
20852 .write_all(&data_packet(FRESH_BODY, true))
20853 .expect("write fresh response");
20854 });
20855
20856 let runtime = build_io_runtime().expect("asupersync runtime");
20857 let next = runtime.block_on(async {
20858 let cx = Cx::current().expect("ambient Cx");
20859 let stream = TcpStream::connect(addr).await.expect("connect to listener");
20860 let (mut read, write) = transport::plain_split(stream);
20861 let write: SharedWriteHalf = Arc::new(AsyncMutex::with_name("drain_test_write", write));
20862
20863 break_and_drain_wire(&mut read, &write, Duration::from_secs(5))
20865 .await
20866 .expect("drain must succeed and leave the stream clean");
20867
20868 read_data_response(&mut read, &cx, &write)
20870 .await
20871 .expect("next read after drain must decode cleanly")
20872 });
20873
20874 assert_eq!(
20875 next, FRESH_BODY,
20876 "after break_and_drain the reused connection must read the FRESH response, \
20877 not the stale in-flight response ({INFLIGHT_BODY:?}) or error body ({ERROR_BODY:?})"
20878 );
20879 server.join().expect("server thread joins");
20880 }
20881
20882 #[test]
20892 fn classic_pre23ai_break_drain_completes_on_terminal_message_not_flag() {
20893 const CLASSIC_STATUS_TERMINAL: &[u8] = &[9, 0, 0];
20898 const FRESH_BODY: &[u8] = &[0x11, 0x22, 0x33, 0x44, 0x55];
20899
20900 assert!(
20904 !data_packet_ends_response(0, CLASSIC_STATUS_TERMINAL),
20905 "flag-framed detector must not terminate a flagless classic response"
20906 );
20907 assert!(
20908 !post_reset_packet_ends_response(CLASSIC_STATUS_TERMINAL),
20909 "post-reset marker-byte detector must not terminate a STATUS terminal"
20910 );
20911 assert!(
20912 classic_connect_response_is_complete(CLASSIC_STATUS_TERMINAL, ProtocolLimits::DEFAULT)
20913 .unwrap(),
20914 "the classic reader must recognise the STATUS terminal as complete"
20915 );
20916
20917 let listener = TcpListener::bind("127.0.0.1:0").expect("bind local listener");
20918 let addr = listener.local_addr().expect("listener address");
20919 let server = thread::spawn(move || {
20920 let (mut socket, _) = listener.accept().expect("accept test client");
20921 socket
20922 .set_read_timeout(Some(Duration::from_secs(5)))
20923 .expect("set read timeout");
20924 use std::io::Write as _;
20925
20926 socket
20930 .write_all(&marker_packet(TNS_MARKER_TYPE_BREAK))
20931 .expect("write break-ack marker");
20932 assert_eq!(
20933 read_marker_type(&mut socket),
20934 TNS_MARKER_TYPE_RESET,
20935 "client must answer the marker with a RESET"
20936 );
20937 socket
20938 .write_all(&marker_packet(TNS_MARKER_TYPE_RESET))
20939 .expect("write reset-confirm marker");
20940 socket
20942 .write_all(&data_packet(CLASSIC_STATUS_TERMINAL, false))
20943 .expect("write flagless classic terminal");
20944 socket
20946 .write_all(&data_packet(FRESH_BODY, true))
20947 .expect("write fresh response");
20948 });
20949
20950 let runtime = build_io_runtime().expect("asupersync runtime");
20951 let next = runtime.block_on(async {
20952 let cx = Cx::current().expect("ambient Cx");
20953 let stream = TcpStream::connect(addr).await.expect("connect to listener");
20954 let (mut read, write) = transport::plain_split(stream);
20955 let write: SharedWriteHalf =
20956 Arc::new(AsyncMutex::with_name("classic_drain_test_write", write));
20957
20958 drain_break_response_recovery_with_limits(
20960 &mut read,
20961 &write,
20962 ProtocolLimits::DEFAULT,
20963 true,
20964 )
20965 .await
20966 .expect("classic drain must complete on the terminal message");
20967
20968 read_data_response(&mut read, &cx, &write)
20969 .await
20970 .expect("next read after classic drain must decode cleanly")
20971 });
20972
20973 assert_eq!(
20974 next, FRESH_BODY,
20975 "after the classic drain the reused connection must read the FRESH response"
20976 );
20977 server.join().expect("server thread joins");
20978 }
20979
20980 #[test]
20985 fn core_break_and_drain_runs_after_caller_context_timeout() {
20986 const INFLIGHT_BODY: &[u8] = &[0xD1, 0xA1, 0xB1];
20987 const ERROR_BODY: &[u8] = &[0x04, 0x01, 0x0d];
20988
20989 let listener = TcpListener::bind("127.0.0.1:0").expect("bind local listener");
20990 let addr = listener.local_addr().expect("listener address");
20991 let server = thread::spawn(move || {
20992 let (mut socket, _) = listener.accept().expect("accept test client");
20993 socket
20994 .set_read_timeout(Some(Duration::from_secs(5)))
20995 .expect("set read timeout");
20996 use std::io::Write as _;
20997
20998 assert_eq!(
20999 read_marker_type(&mut socket),
21000 TNS_MARKER_TYPE_BREAK,
21001 "recovery must send BREAK even after caller context timeout"
21002 );
21003 socket
21004 .write_all(&data_packet(INFLIGHT_BODY, true))
21005 .expect("write in-flight response");
21006 socket
21007 .write_all(&marker_packet(TNS_MARKER_TYPE_BREAK))
21008 .expect("write break-ack marker");
21009 assert_eq!(
21010 read_marker_type(&mut socket),
21011 TNS_MARKER_TYPE_RESET,
21012 "recovery must answer break marker with RESET"
21013 );
21014 socket
21015 .write_all(&marker_packet(TNS_MARKER_TYPE_RESET))
21016 .expect("write reset-confirm marker");
21017 socket
21018 .write_all(&data_packet(ERROR_BODY, true))
21019 .expect("write trailing error packet");
21020 });
21021
21022 let runtime = build_io_runtime().expect("asupersync runtime");
21023 runtime.block_on(async {
21024 let cx = Cx::current().expect("ambient Cx");
21025 let stream = TcpStream::connect(addr).await.expect("connect to listener");
21026 let (read, write) = transport::plain_split(stream);
21027 let mut core = ConnectionCore::<DriverTransport>::from_halves(
21028 read,
21029 write,
21030 "timeout_drain_test_write",
21031 );
21032
21033 cx.cancel_fast(asupersync::CancelKind::Timeout);
21034 assert!(
21035 cx.checkpoint().is_err(),
21036 "test must start from an expired caller context"
21037 );
21038
21039 core.break_and_drain_wire(Duration::from_secs(5))
21040 .expect("recovery drain must ignore the expired caller context");
21041 });
21042
21043 server.join().expect("server thread joins");
21044 }
21045
21046 #[test]
21047 fn preexpired_query_deadline_does_not_break_idle_connection() -> Result<()> {
21048 const INFLIGHT_BODY: &[u8] = &[0xd1, 0xa1, 0xb1];
21049 const CANCEL_ERROR: &[u8] = &[0x04, 0x01, 0x0d];
21050
21051 let commit_packet = encode_packet(
21052 TNS_PACKET_TYPE_DATA,
21053 0,
21054 Some(0),
21055 &build_function_payload_with_seq(
21056 TNS_FUNC_COMMIT,
21057 1,
21058 ClientCapabilities::default().ttc_field_version,
21059 ),
21060 PacketLengthWidth::Large32,
21061 )?;
21062
21063 let listener = TcpListener::bind("127.0.0.1:0")?;
21064 let addr = listener.local_addr()?;
21065 let server = thread::spawn(move || -> std::io::Result<usize> {
21066 let (mut socket, _) = listener.accept()?;
21067 socket.set_read_timeout(Some(Duration::from_secs(5)))?;
21068 use std::io::Write as _;
21069
21070 let mut break_count = 0usize;
21071 loop {
21072 let mut header = [0u8; 8];
21073 socket.read_exact(&mut header)?;
21074 let declared =
21075 u32::from_be_bytes([header[0], header[1], header[2], header[3]]) as usize;
21076 let mut packet = header.to_vec();
21077 let mut body = vec![0u8; declared - header.len()];
21078 socket.read_exact(&mut body)?;
21079 packet.extend_from_slice(&body);
21080 if packet[4] == TNS_PACKET_TYPE_MARKER {
21081 assert_eq!(packet[10], TNS_MARKER_TYPE_BREAK);
21082 break_count += 1;
21083 socket.write_all(&data_packet(INFLIGHT_BODY, true))?;
21084 socket.write_all(&marker_packet(TNS_MARKER_TYPE_BREAK))?;
21085 assert_eq!(read_marker_type(&mut socket), TNS_MARKER_TYPE_RESET);
21086 socket.write_all(&marker_packet(TNS_MARKER_TYPE_RESET))?;
21087 socket.write_all(&data_packet(CANCEL_ERROR, true))?;
21088 socket.flush()?;
21089 continue;
21090 }
21091
21092 assert_eq!(
21093 packet, commit_packet,
21094 "the first ordinary request after both local expiries must be COMMIT"
21095 );
21096 socket.write_all(&data_packet(&[TNS_MSG_TYPE_END_OF_RESPONSE], true))?;
21097 socket.flush()?;
21098 return Ok(break_count);
21099 }
21100 });
21101
21102 let runtime = build_io_runtime()?;
21103 runtime.block_on(async {
21104 let cx = Cx::current().expect("runtime installs an ambient Cx");
21105 let socket = TcpStream::connect(addr).await?;
21106 let (read, write) = transport::plain_split(socket);
21107 let mut connection = loopback_connection(read, write);
21108
21109 fn assert_send<T: Send>(_: &T) {}
21110 let zero_timeout = connection.execute_with(
21111 &cx,
21112 Execute::new("begin null; end;").timeout(Duration::ZERO),
21113 );
21114 assert_send(&zero_timeout);
21115 let err = zero_timeout
21116 .await
21117 .expect_err("an already-expired query deadline must fail");
21118 assert!(
21119 matches!(err, Error::CallTimeout(0)),
21120 "pre-expired deadline must surface CallTimeout(0), got {err:?}"
21121 );
21122
21123 let ambient_deadline = QueryDeadline::from_budget(
21124 asupersync::time::wall_now(),
21125 Budget::new().with_deadline(Time::ZERO),
21126 None,
21127 );
21128 let ambient_err = connection
21129 .execute_with_deadline(&cx, Execute::new("begin null; end;"), ambient_deadline)
21130 .await
21131 .expect_err("an expired ambient deadline must fail before polling");
21132 assert!(
21133 matches!(ambient_err, Error::CallTimeout(0)),
21134 "expired ambient deadline must surface CallTimeout(0), got {ambient_err:?}"
21135 );
21136 assert_eq!(
21137 connection.core.recovery.phase(),
21138 SessionRecoveryPhase::Ready,
21139 "a future that was never polled must not arm wire recovery"
21140 );
21141 assert!(
21142 !connection.dead,
21143 "local before-start expiry must not mark a healthy connection dead"
21144 );
21145
21146 connection.commit(&cx).await?;
21147 Ok::<_, Error>(())
21148 })?;
21149
21150 let break_count = server.join().expect("server thread joins")?;
21151 assert_eq!(
21152 break_count, 0,
21153 "neither a zero request timeout nor an expired ambient deadline may send BREAK"
21154 );
21155 Ok(())
21156 }
21157
21158 #[test]
21159 fn prestart_expiry_preserves_structured_cancel_without_wire_io() -> Result<()> {
21160 let listener = TcpListener::bind("127.0.0.1:0")?;
21161 let addr = listener.local_addr()?;
21162 let server = thread::spawn(move || -> std::io::Result<Vec<usize>> {
21163 let mut received = Vec::with_capacity(2);
21164 for _ in 0..2 {
21165 let (mut socket, _) = listener.accept()?;
21166 socket.set_read_timeout(Some(Duration::from_secs(5)))?;
21167 let mut bytes = Vec::new();
21168 socket.read_to_end(&mut bytes)?;
21169 received.push(bytes.len());
21170 }
21171 Ok(received)
21172 });
21173
21174 for kind in [CancelKind::User, CancelKind::Shutdown] {
21175 let runtime = build_io_runtime()?;
21176 runtime.block_on(async {
21177 let socket = TcpStream::connect(addr).await?;
21178 let (read, write) = transport::plain_split(socket);
21179 let mut connection = loopback_connection(read, write);
21180 let cx = Cx::current().expect("runtime installs an ambient Cx");
21181 cx.cancel_with(kind, Some("pre-start cancellation test"));
21182
21183 let err = connection
21184 .execute_with(
21185 &cx,
21186 Execute::new("begin null; end;").timeout(Duration::ZERO),
21187 )
21188 .await
21189 .expect_err("an already-expired operation must not start");
21190
21191 match kind {
21192 CancelKind::User => {
21193 assert!(
21194 matches!(err, Error::Cancelled),
21195 "user cancellation must remain distinct, got {err:?}"
21196 );
21197 assert!(
21198 !connection.is_dead(),
21199 "user cancellation before start leaves the connection reusable"
21200 );
21201 assert_eq!(
21202 connection.core.recovery.phase(),
21203 SessionRecoveryPhase::Ready
21204 );
21205 }
21206 CancelKind::Shutdown => {
21207 assert!(
21208 matches!(err, Error::ConnectionClosed(_)),
21209 "shutdown cancellation must close the connection, got {err:?}"
21210 );
21211 assert!(
21212 connection.is_dead(),
21213 "shutdown cancellation before start must mark the connection dead"
21214 );
21215 assert_eq!(connection.core.recovery.phase(), SessionRecoveryPhase::Dead);
21216 }
21217 _ => unreachable!("the test covers exactly User and Shutdown"),
21218 }
21219
21220 drop(connection);
21221 Ok::<_, Error>(())
21222 })?;
21223 }
21224
21225 let received = server.join().expect("server thread joins")?;
21226 assert_eq!(
21227 received,
21228 vec![0, 0],
21229 "before-start cancellation must emit neither a request nor BREAK"
21230 );
21231 Ok(())
21232 }
21233
21234 #[test]
21241 fn reset_after_marker_drains_multiple_trailing_markers_no_duplicate_reset() {
21242 const INFLIGHT_BODY: &[u8] = &[0xDE, 0xAD];
21243 const ERROR_BODY: &[u8] = &[0x04, 0x01, 0x02];
21244 const FRESH_BODY: &[u8] = &[0x11, 0x22, 0x33];
21245
21246 let listener = TcpListener::bind("127.0.0.1:0").expect("bind local listener");
21247 let addr = listener.local_addr().expect("listener address");
21248 let server = thread::spawn(move || {
21249 let (mut socket, _) = listener.accept().expect("accept test client");
21250 socket
21251 .set_read_timeout(Some(Duration::from_secs(5)))
21252 .expect("set read timeout");
21253 use std::io::Write as _;
21254
21255 assert_eq!(
21256 read_marker_type(&mut socket),
21257 TNS_MARKER_TYPE_BREAK,
21258 "client must send a BREAK marker first"
21259 );
21260 socket
21261 .write_all(&data_packet(INFLIGHT_BODY, true))
21262 .expect("write in-flight response");
21263 socket
21264 .write_all(&marker_packet(TNS_MARKER_TYPE_BREAK))
21265 .expect("write break-ack marker");
21266 assert_eq!(
21268 read_marker_type(&mut socket),
21269 TNS_MARKER_TYPE_RESET,
21270 "client must answer with a RESET"
21271 );
21272 socket
21275 .write_all(&marker_packet(TNS_MARKER_TYPE_RESET))
21276 .expect("write reset marker #1");
21277 socket
21278 .write_all(&marker_packet(TNS_MARKER_TYPE_RESET))
21279 .expect("write reset marker #2");
21280 socket
21281 .write_all(&data_packet(ERROR_BODY, true))
21282 .expect("write trailing error packet");
21283 socket
21284 .write_all(&data_packet(FRESH_BODY, true))
21285 .expect("write fresh response");
21286 socket
21290 .set_read_timeout(Some(Duration::from_millis(750)))
21291 .expect("set short read timeout");
21292 let mut extra = [0u8; 11];
21293 if socket.read_exact(&mut extra).is_ok() {
21294 panic!(
21295 "client sent a DUPLICATE marker (type {}): the drain did not \
21296 consume all trailing RESET markers (bead rust-oracledb-yhz)",
21297 extra[10]
21298 );
21299 }
21300 });
21301
21302 let runtime = build_io_runtime().expect("asupersync runtime");
21303 let next = runtime.block_on(async {
21304 let cx = Cx::current().expect("ambient Cx");
21305 let stream = TcpStream::connect(addr).await.expect("connect to listener");
21306 let (mut read, write) = transport::plain_split(stream);
21307 let write: SharedWriteHalf = Arc::new(AsyncMutex::with_name("yhz_test_write", write));
21308 break_and_drain_wire(&mut read, &write, Duration::from_secs(5))
21309 .await
21310 .expect("drain must succeed even with multiple RESET markers");
21311 read_data_response(&mut read, &cx, &write)
21312 .await
21313 .expect("next read after drain must decode cleanly")
21314 });
21315
21316 assert_eq!(
21317 next, FRESH_BODY,
21318 "after draining multiple RESET markers the reused connection must read \
21319 the FRESH response"
21320 );
21321 server.join().expect("server thread joins");
21322 }
21323
21324 #[test]
21331 fn break_without_drain_leaves_stale_bytes_for_next_read() {
21332 const STALE_BODY: &[u8] = &[0x53, 0x54, 0x41, 0x4c, 0x45]; const FRESH_BODY: &[u8] = &[0x11, 0x22, 0x33];
21334
21335 let listener = TcpListener::bind("127.0.0.1:0").expect("bind local listener");
21336 let addr = listener.local_addr().expect("listener address");
21337 let server = thread::spawn(move || {
21338 let (mut socket, _) = listener.accept().expect("accept test client");
21339 socket
21340 .set_read_timeout(Some(Duration::from_secs(5)))
21341 .expect("set read timeout");
21342 use std::io::Write as _;
21343 assert_eq!(read_marker_type(&mut socket), TNS_MARKER_TYPE_BREAK);
21345 socket
21348 .write_all(&data_packet(STALE_BODY, true))
21349 .expect("write stale in-flight response");
21350 socket
21352 .write_all(&data_packet(FRESH_BODY, true))
21353 .expect("write fresh response");
21354 });
21355
21356 let runtime = build_io_runtime().expect("asupersync runtime");
21357 let first_read = runtime.block_on(async {
21358 let cx = Cx::current().expect("ambient Cx");
21359 let stream = TcpStream::connect(addr).await.expect("connect to listener");
21360 let (mut read, write) = transport::plain_split(stream);
21361 let write: SharedWriteHalf =
21362 Arc::new(AsyncMutex::with_name("nodrain_test_write", write));
21363
21364 send_marker_shared(&cx, &write, TNS_MARKER_TYPE_BREAK)
21366 .await
21367 .expect("send break");
21368
21369 read_data_response(&mut read, &cx, &write)
21371 .await
21372 .expect("read after bare break")
21373 });
21374
21375 assert_eq!(
21376 first_read, STALE_BODY,
21377 "without the drain, the next read misframes onto the stale in-flight bytes — \
21378 this is the bug break_and_drain fixes"
21379 );
21380 server.join().expect("server thread joins");
21381 }
21382
21383 #[test]
21416 fn dml_returning_error_flush_out_binds_after_reset_completes_without_hang() {
21417 const ERROR_BODY: &[u8] = &[0x04, 0x01, 0x02, 0x37];
21419 const FLUSH_REQUEST_BODY: &[u8] = &[0x07, 0x00, 0x00, TNS_MSG_TYPE_FLUSH_OUT_BINDS];
21423
21424 let listener = TcpListener::bind("127.0.0.1:0").expect("bind local listener");
21425 let addr = listener.local_addr().expect("listener address");
21426 let server = thread::spawn(move || {
21427 let (mut socket, _) = listener.accept().expect("accept test client");
21428 socket
21429 .set_read_timeout(Some(Duration::from_secs(5)))
21430 .expect("set read timeout");
21431 use std::io::Write as _;
21432
21433 socket
21435 .write_all(&marker_packet(TNS_MARKER_TYPE_BREAK))
21436 .expect("write break marker");
21437 assert_eq!(
21439 read_marker_type(&mut socket),
21440 TNS_MARKER_TYPE_RESET,
21441 "client must answer the BREAK with a RESET"
21442 );
21443 socket
21444 .write_all(&marker_packet(TNS_MARKER_TYPE_RESET))
21445 .expect("write reset-confirm marker");
21446 socket
21450 .write_all(&data_packet(FLUSH_REQUEST_BODY, false))
21451 .expect("write flush-out-binds request");
21452 let mut header = [0u8; 8];
21455 socket
21456 .read_exact(&mut header)
21457 .expect("read flush-out-binds reply header");
21458 assert_eq!(
21459 header[4], TNS_PACKET_TYPE_DATA,
21460 "client's flush-out-binds reply must be a DATA packet"
21461 );
21462 let len = u32::from_be_bytes([header[0], header[1], header[2], header[3]]) as usize;
21463 let mut body = vec![0u8; len - 8];
21464 socket
21465 .read_exact(&mut body)
21466 .expect("read flush-out-binds reply body");
21467 assert_eq!(
21468 body.last().copied(),
21469 Some(TNS_MSG_TYPE_FLUSH_OUT_BINDS),
21470 "client must reply with a FLUSH_OUT_BINDS message"
21471 );
21472 socket
21474 .write_all(&marker_packet(TNS_MARKER_TYPE_BREAK))
21475 .expect("write second break marker");
21476 assert_eq!(
21477 read_marker_type(&mut socket),
21478 TNS_MARKER_TYPE_RESET,
21479 "client must answer the second BREAK with a RESET"
21480 );
21481 socket
21482 .write_all(&marker_packet(TNS_MARKER_TYPE_RESET))
21483 .expect("write second reset-confirm marker");
21484 socket
21486 .write_all(&data_packet(ERROR_BODY, true))
21487 .expect("write trailing ORA-12899 error packet");
21488 });
21489
21490 let runtime = build_io_runtime().expect("asupersync runtime");
21491 let payload = runtime.block_on(async {
21492 let cx = Cx::current().expect("ambient Cx");
21493 let stream = TcpStream::connect(addr).await.expect("connect to listener");
21494 let (mut read, write) = transport::plain_split(stream);
21495 let write: SharedWriteHalf =
21496 Arc::new(AsyncMutex::with_name("returning_err_test_write", write));
21497
21498 time::timeout(
21501 time::wall_now(),
21502 Duration::from_secs(10),
21503 read_data_response_flushing_out_binds(&mut read, &cx, &write, 8192),
21504 )
21505 .await
21506 .expect("must NOT hang on the DML-RETURNING error path (flush-out-binds after reset)")
21507 .expect("read must complete and yield the trailing error payload")
21508 });
21509
21510 assert!(
21513 payload.ends_with(ERROR_BODY),
21514 "the reassembled response must end with the ORA-12899 error payload, got {payload:?}"
21515 );
21516 server.join().expect("server thread joins");
21517 }
21518
21519 #[test]
21533 fn cancel_and_drain_wire_leaves_connection_reusable() {
21534 const INFLIGHT_BODY: &[u8] = &[0xCA, 0xFE];
21537 const ERROR_BODY: &[u8] = &[0x04, 0x01, 0x0d];
21539 const FRESH_BODY: &[u8] = &[0x07, 0x05, 0x0c];
21541
21542 let listener = TcpListener::bind("127.0.0.1:0").expect("bind local listener");
21543 let addr = listener.local_addr().expect("listener address");
21544 let server = thread::spawn(move || {
21545 let (mut socket, _) = listener.accept().expect("accept test client");
21546 socket
21547 .set_read_timeout(Some(Duration::from_secs(5)))
21548 .expect("set read timeout");
21549 use std::io::Write as _;
21550
21551 assert_eq!(
21553 read_marker_type(&mut socket),
21554 TNS_MARKER_TYPE_BREAK,
21555 "cancel must send a BREAK marker first"
21556 );
21557 socket
21559 .write_all(&data_packet(INFLIGHT_BODY, true))
21560 .expect("write in-flight response");
21561 socket
21563 .write_all(&marker_packet(TNS_MARKER_TYPE_BREAK))
21564 .expect("write break-ack marker");
21565 assert_eq!(
21567 read_marker_type(&mut socket),
21568 TNS_MARKER_TYPE_RESET,
21569 "cancel must answer the marker with a RESET"
21570 );
21571 socket
21572 .write_all(&marker_packet(TNS_MARKER_TYPE_RESET))
21573 .expect("write reset-confirm marker");
21574 socket
21576 .write_all(&data_packet(ERROR_BODY, true))
21577 .expect("write trailing error packet");
21578 socket
21580 .write_all(&data_packet(FRESH_BODY, true))
21581 .expect("write fresh response");
21582 });
21583
21584 let runtime = build_io_runtime().expect("asupersync runtime");
21585 let next = runtime.block_on(async {
21586 let cx = Cx::current().expect("ambient Cx");
21587 let stream = TcpStream::connect(addr).await.expect("connect to listener");
21588 let (mut read, write) = transport::plain_split(stream);
21589 let write: SharedWriteHalf =
21590 Arc::new(AsyncMutex::with_name("cancel_test_write", write));
21591
21592 cancel_and_drain_wire(&mut read, &write, Duration::from_secs(5))
21594 .await
21595 .expect("cancel drain must succeed and leave the stream clean");
21596
21597 read_data_response(&mut read, &cx, &write)
21599 .await
21600 .expect("next read after cancel must decode cleanly")
21601 });
21602
21603 assert_eq!(
21604 next, FRESH_BODY,
21605 "after cancel the reused connection must read the FRESH response, not the \
21606 stale in-flight response ({INFLIGHT_BODY:?}) or error body ({ERROR_BODY:?})"
21607 );
21608 server.join().expect("server thread joins");
21609 }
21610
21611 #[test]
21621 fn ensure_clean_drains_inflight_bare_request_before_reuse() {
21622 const INFLIGHT_BODY: &[u8] = &[0xCA, 0xFE];
21625 const ERROR_BODY: &[u8] = &[0x04, 0x01, 0x0d];
21627 const FRESH_BODY: &[u8] = &[0x07, 0x05, 0x0c];
21629
21630 let listener = TcpListener::bind("127.0.0.1:0").expect("bind local listener");
21631 let addr = listener.local_addr().expect("listener address");
21632 let server = thread::spawn(move || {
21633 let (mut socket, _) = listener.accept().expect("accept test client");
21634 socket
21635 .set_read_timeout(Some(Duration::from_secs(5)))
21636 .expect("set read timeout");
21637 use std::io::Write as _;
21638
21639 assert_eq!(
21643 read_marker_type(&mut socket),
21644 TNS_MARKER_TYPE_BREAK,
21645 "reuse after a bare request must send a BREAK to drain the stranded page"
21646 );
21647 socket
21648 .write_all(&data_packet(INFLIGHT_BODY, true))
21649 .expect("write stranded page");
21650 socket
21651 .write_all(&marker_packet(TNS_MARKER_TYPE_BREAK))
21652 .expect("write break-ack marker");
21653 assert_eq!(
21654 read_marker_type(&mut socket),
21655 TNS_MARKER_TYPE_RESET,
21656 "drain must answer the break-ack marker with a RESET"
21657 );
21658 socket
21659 .write_all(&marker_packet(TNS_MARKER_TYPE_RESET))
21660 .expect("write reset-confirm marker");
21661 socket
21662 .write_all(&data_packet(ERROR_BODY, true))
21663 .expect("write trailing error packet");
21664 socket
21665 .write_all(&data_packet(FRESH_BODY, true))
21666 .expect("write fresh response");
21667 });
21668
21669 let runtime = build_io_runtime().expect("asupersync runtime");
21670 let next = runtime
21671 .block_on(async {
21672 let cx = Cx::current().expect("ambient Cx");
21673 let stream = TcpStream::connect(addr).await.expect("connect to listener");
21674 let (read, write) = transport::plain_split(stream);
21675 let mut conn = loopback_connection(read, write);
21676
21677 conn.core
21680 .recovery
21681 .begin_operation()
21682 .expect("enter InFlight");
21683 assert_eq!(conn.core.recovery.phase(), SessionRecoveryPhase::InFlight);
21684
21685 conn.ensure_clean_before_request()
21687 .await
21688 .expect("a stranded bare request must be drained, not wedge the connection");
21689 assert_eq!(
21690 conn.core.recovery.phase(),
21691 SessionRecoveryPhase::Ready,
21692 "after draining the stranded page the session is Ready for reuse"
21693 );
21694
21695 conn.core.read_data_response(&cx).await
21696 })
21697 .expect("the reused connection must read its fresh response");
21698
21699 assert_eq!(
21700 next, FRESH_BODY,
21701 "after draining the stranded bare request the reused connection must read \
21702 the FRESH response, not the stranded page ({INFLIGHT_BODY:?})"
21703 );
21704 server.join().expect("server thread joins");
21705 }
21706
21707 #[test]
21717 fn drain_cancel_wire_drains_without_sending_a_break() {
21718 const INFLIGHT_BODY: &[u8] = &[0xCA, 0xFE];
21719 const ERROR_BODY: &[u8] = &[0x04, 0x01, 0x0d];
21720 const FRESH_BODY: &[u8] = &[0x07, 0x05, 0x0c];
21721
21722 let listener = TcpListener::bind("127.0.0.1:0").expect("bind local listener");
21723 let addr = listener.local_addr().expect("listener address");
21724 let server = thread::spawn(move || {
21725 let (mut socket, _) = listener.accept().expect("accept test client");
21726 socket
21727 .set_read_timeout(Some(Duration::from_secs(5)))
21728 .expect("set read timeout");
21729 use std::io::Write as _;
21730
21731 socket
21734 .write_all(&data_packet(INFLIGHT_BODY, true))
21735 .expect("write in-flight response");
21736 socket
21737 .write_all(&marker_packet(TNS_MARKER_TYPE_BREAK))
21738 .expect("write break-ack marker");
21739 assert_eq!(
21741 read_marker_type(&mut socket),
21742 TNS_MARKER_TYPE_RESET,
21743 "drain must answer the break-ack marker with a RESET"
21744 );
21745 socket
21746 .write_all(&marker_packet(TNS_MARKER_TYPE_RESET))
21747 .expect("write reset-confirm marker");
21748 socket
21749 .write_all(&data_packet(ERROR_BODY, true))
21750 .expect("write trailing error packet");
21751 socket
21752 .write_all(&data_packet(FRESH_BODY, true))
21753 .expect("write fresh response");
21754 socket
21756 .set_read_timeout(Some(Duration::from_millis(500)))
21757 .expect("set short read timeout");
21758 let mut extra = [0u8; 11];
21759 if let Ok(()) = socket.read_exact(&mut extra) {
21760 assert_ne!(
21761 extra[10], TNS_MARKER_TYPE_BREAK,
21762 "drain-only cancel must NOT send a BREAK marker"
21763 );
21764 }
21765 });
21766
21767 let runtime = build_io_runtime().expect("asupersync runtime");
21768 let next = runtime.block_on(async {
21769 let cx = Cx::current().expect("ambient Cx");
21770 let stream = TcpStream::connect(addr).await.expect("connect to listener");
21771 let (mut read, write) = transport::plain_split(stream);
21772 let write: SharedWriteHalf =
21773 Arc::new(AsyncMutex::with_name("drain_cancel_test_write", write));
21774
21775 drain_cancel_wire(&mut read, &write, Duration::from_secs(5))
21776 .await
21777 .expect("drain-only cancel must succeed and leave the stream clean");
21778
21779 read_data_response(&mut read, &cx, &write)
21780 .await
21781 .expect("next read after drain must decode cleanly")
21782 });
21783
21784 assert_eq!(
21785 next, FRESH_BODY,
21786 "after drain-only cancel the reused connection must read the FRESH response"
21787 );
21788 server.join().expect("server thread joins");
21789 }
21790
21791 #[test]
21800 fn cancel_drain_guard_transitions_recovery_phase_only_when_dropped_in_flight() -> Result<()> {
21801 let recovery = Arc::new(SessionRecovery::new());
21802
21803 {
21806 let _guard = CancelDrainGuard::arm(Arc::clone(&recovery))?;
21807 assert_eq!(recovery.phase(), SessionRecoveryPhase::InFlight);
21808 }
21809 assert_eq!(
21810 recovery.phase(),
21811 SessionRecoveryPhase::BreakSent,
21812 "dropping an armed guard (cancelled in flight) must require recovery"
21813 );
21814 assert!(recovery.begin_pending_drain()?);
21815 assert_eq!(recovery.phase(), SessionRecoveryPhase::Draining);
21816 assert!(
21817 recovery.begin_pending_drain().is_err(),
21818 "a drain that is already running must not start a second drain"
21819 );
21820 recovery.finish_drain_ready();
21821 assert_eq!(recovery.phase(), SessionRecoveryPhase::Ready);
21822
21823 recovery.begin_operation()?;
21826 match recovery.begin_operation() {
21827 Err(Error::ConnectionClosed(message)) => {
21828 assert!(message.contains("still in flight"));
21829 }
21830 other => {
21831 return Err(Error::Runtime(format!(
21832 "second operation start should fail while InFlight, got {other:?}"
21833 )));
21834 }
21835 }
21836 recovery.complete_operation();
21837 assert_eq!(recovery.phase(), SessionRecoveryPhase::Ready);
21838
21839 recovery.begin_operation()?;
21842 {
21843 let mut guard = CancelDrainGuard::arm(Arc::clone(&recovery))?;
21844 assert_eq!(recovery.phase(), SessionRecoveryPhase::InFlight);
21845 guard.disarm();
21846 }
21847 assert_eq!(recovery.phase(), SessionRecoveryPhase::Ready);
21848
21849 {
21852 let mut guard = CancelDrainGuard::arm(Arc::clone(&recovery))?;
21853 assert_eq!(recovery.phase(), SessionRecoveryPhase::InFlight);
21854 guard.disarm();
21855 }
21856 assert_eq!(
21857 recovery.phase(),
21858 SessionRecoveryPhase::Ready,
21859 "a disarmed guard (clean completion) must NOT require recovery"
21860 );
21861 Ok(())
21862 }
21863
21864 #[test]
21865 fn cancelled_error_is_not_connection_lost_but_is_transient() {
21866 let cancelled = Error::Cancelled;
21867 assert!(
21868 !cancelled.is_connection_lost(),
21869 "a user cancel leaves the session alive (ORA-01013 / DPY-4024 semantics)"
21870 );
21871 assert!(
21872 cancelled.is_transient(),
21873 "a cancelled operation may be retried on the same clean connection"
21874 );
21875 assert_eq!(cancelled.ora_code(), Some(1013));
21877 }
21878
21879 fn read_tns_packet_sync(
21886 socket: &mut std::net::TcpStream,
21887 ) -> std::io::Result<(u8, u8, Vec<u8>)> {
21888 let mut header = [0u8; 8];
21889 socket.read_exact(&mut header)?;
21890 let declared = usize::from(u16::from_be_bytes([header[0], header[1]]));
21891 let mut payload = vec![0u8; declared.saturating_sub(header.len())];
21892 socket.read_exact(&mut payload)?;
21893 Ok((header[4], header[5], payload))
21894 }
21895
21896 fn send_tns_packet_sync(
21897 socket: &mut std::net::TcpStream,
21898 packet_type: u8,
21899 payload: &[u8],
21900 ) -> std::io::Result<()> {
21901 use std::io::Write as _;
21902 let packet = encode_packet(packet_type, 0, None, payload, PacketLengthWidth::Legacy16)
21903 .expect("encode test packet");
21904 socket.write_all(&packet)
21905 }
21906
21907 fn redirect_packet_payload(redirect_data: &str) -> Vec<u8> {
21910 let bytes = redirect_data.as_bytes();
21911 let mut payload = Vec::with_capacity(2 + bytes.len());
21912 payload.extend_from_slice(
21913 &u16::try_from(bytes.len())
21914 .expect("test data fits")
21915 .to_be_bytes(),
21916 );
21917 payload.extend_from_slice(bytes);
21918 payload
21919 }
21920
21921 #[test]
21922 fn redirect_payload_prefix_accepts_inline_partial_and_rejects_truncated() {
21923 let payload = redirect_packet_payload("abc");
21925 assert_eq!(redirect_payload_prefix(&payload).unwrap(), (3, &b"abc"[..]));
21926 assert_eq!(
21929 redirect_payload_prefix(&[0x00, 0x10, b'x']).unwrap(),
21930 (16, &b"x"[..])
21931 );
21932 assert_eq!(
21934 redirect_payload_prefix(&[0x00, 0x05]).unwrap(),
21935 (5, &[][..])
21936 );
21937 assert_eq!(
21939 redirect_payload_prefix(&[0x00, 0x01, b'a', b'b']).unwrap(),
21940 (1, &b"a"[..])
21941 );
21942 for bad in [&[][..], &[0x00][..]] {
21944 assert!(
21945 matches!(
21946 redirect_payload_prefix(bad),
21947 Err(Error::InvalidRedirectData(_))
21948 ),
21949 "payload {bad:?} must be rejected as malformed"
21950 );
21951 }
21952 }
21953
21954 #[test]
21955 fn parse_redirect_target_well_formed_and_malformed() {
21956 let data = "(ADDRESS=(PROTOCOL=tcp)(HOST=dispatcher.example)(PORT=1621))\0\
21958 (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=dispatcher.example)(PORT=1621))\
21959 (CONNECT_DATA=(SERVICE_NAME=svc)))";
21960 let target = parse_redirect_target(data, NetProtocol::Tcp).expect("well-formed redirect");
21961 assert_eq!(target.host, "dispatcher.example");
21962 assert_eq!(target.port, 1621);
21963 assert!(target.connect_data.starts_with("(DESCRIPTION="));
21964 assert!(matches!(
21966 parse_redirect_target("(ADDRESS=(PROTOCOL=tcp)(HOST=h)(PORT=1))", NetProtocol::Tcp),
21967 Err(Error::InvalidRedirectData(_))
21968 ));
21969 assert!(matches!(
21971 parse_redirect_target("(ADDRESS=(PROTOCOL=tcp)(PORT=1621))\0x", NetProtocol::Tcp),
21972 Err(Error::InvalidRedirectData(_))
21973 ));
21974 assert!(matches!(
21976 parse_redirect_target("(((\0x", NetProtocol::Tcp),
21977 Err(Error::InvalidRedirectData(_))
21978 ));
21979 }
21980
21981 #[test]
21986 fn parse_redirect_target_refuses_transport_protocol_change() {
21987 let tcp_addr = "(ADDRESS=(PROTOCOL=tcp)(HOST=h)(PORT=1521))\0cd";
21988 let tcps_addr = "(ADDRESS=(PROTOCOL=tcps)(HOST=h)(PORT=2484))\0cd";
21989 let no_protocol = "(ADDRESS=(HOST=h)(PORT=1521))\0cd";
21990 assert!(matches!(
21993 parse_redirect_target(tcp_addr, NetProtocol::Tcps),
21994 Err(Error::RedirectUnsupported)
21995 ));
21996 assert!(matches!(
21997 parse_redirect_target(no_protocol, NetProtocol::Tcps),
21998 Err(Error::RedirectUnsupported)
21999 ));
22000 assert!(matches!(
22002 parse_redirect_target(tcps_addr, NetProtocol::Tcp),
22003 Err(Error::RedirectUnsupported)
22004 ));
22005 assert!(parse_redirect_target(tcps_addr, NetProtocol::Tcps).is_ok());
22007 assert!(parse_redirect_target(no_protocol, NetProtocol::Tcp).is_ok());
22008 assert!(parse_redirect_target(tcp_addr, NetProtocol::Tcp).is_ok());
22009 }
22010
22011 #[test]
22018 fn connect_follows_listener_redirect_and_flags_the_new_connect() -> Result<()> {
22019 let redirect_listener = TcpListener::bind("127.0.0.1:0").expect("bind redirect listener");
22020 let target_listener = TcpListener::bind("127.0.0.1:0").expect("bind target listener");
22021 let redirect_addr = redirect_listener.local_addr().expect("redirect addr");
22022 let target_addr = target_listener.local_addr().expect("target addr");
22023 let redirect_connect_data = format!(
22024 "(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=127.0.0.1)(PORT={}))\
22025 (CONNECT_DATA=(SERVICE_NAME=redirsvc)))",
22026 target_addr.port()
22027 );
22028 let redirect_data = format!(
22029 "(ADDRESS=(PROTOCOL=tcp)(HOST=127.0.0.1)(PORT={}))\0{}",
22030 target_addr.port(),
22031 redirect_connect_data
22032 );
22033
22034 let first = thread::spawn(move || -> std::io::Result<(u8, u8)> {
22035 let (mut socket, _) = redirect_listener.accept()?;
22036 socket.set_read_timeout(Some(Duration::from_secs(5)))?;
22037 let (packet_type, packet_flags, _payload) = read_tns_packet_sync(&mut socket)?;
22038 send_tns_packet_sync(
22039 &mut socket,
22040 TNS_PACKET_TYPE_REDIRECT,
22041 &redirect_packet_payload(&redirect_data),
22042 )?;
22043 Ok((packet_type, packet_flags))
22044 });
22045 let expected_connect_data = redirect_connect_data;
22046 let second = thread::spawn(move || -> std::io::Result<(u8, u8, bool)> {
22047 let (mut socket, _) = target_listener.accept()?;
22048 socket.set_read_timeout(Some(Duration::from_secs(5)))?;
22049 let (packet_type, packet_flags, payload) = read_tns_packet_sync(&mut socket)?;
22050 let carries_connect_data = payload
22051 .windows(expected_connect_data.len())
22052 .any(|window| window == expected_connect_data.as_bytes());
22053 send_tns_packet_sync(&mut socket, TNS_PACKET_TYPE_REFUSE, b"(ERR=12514)")?;
22054 Ok((packet_type, packet_flags, carries_connect_data))
22055 });
22056
22057 let options = ConnectOptions::new(
22058 format!("127.0.0.1:{}/redirsvc", redirect_addr.port()),
22059 "user",
22060 "password",
22061 identity(),
22062 );
22063 let runtime = build_io_runtime().expect("asupersync runtime");
22064 let err = runtime
22065 .block_on(async {
22066 let cx = Cx::current().expect("ambient Cx");
22067 Connection::connect(&cx, options).await
22068 })
22069 .expect_err("target listener refuses; the refusal must surface");
22070 assert!(
22071 matches!(&err, Error::ListenerRefused(msg) if msg.contains("ERR=12514")),
22072 "expected the TARGET listener's refusal, got {err:?}"
22073 );
22074 let (first_type, first_flags) = first.join().expect("redirect listener thread")?;
22075 assert_eq!(first_type, TNS_PACKET_TYPE_CONNECT);
22076 assert_eq!(first_flags, 0, "initial CONNECT carries no redirect flag");
22077 let (second_type, second_flags, carries_connect_data) =
22078 second.join().expect("target listener thread")?;
22079 assert_eq!(second_type, TNS_PACKET_TYPE_CONNECT);
22080 assert_eq!(
22081 second_flags, TNS_PACKET_FLAG_REDIRECT,
22082 "the CONNECT resent to the redirect target must carry the REDIRECT packet flag"
22083 );
22084 assert!(
22085 carries_connect_data,
22086 "the redirected CONNECT must carry the redirect-supplied connect data"
22087 );
22088 Ok(())
22089 }
22090
22091 #[test]
22099 fn connect_redirect_interleaves_with_resend_and_chunked_redirect_data() -> Result<()> {
22100 let redirect_listener = TcpListener::bind("127.0.0.1:0").expect("bind redirect listener");
22101 let target_listener = TcpListener::bind("127.0.0.1:0").expect("bind target listener");
22102 let redirect_addr = redirect_listener.local_addr().expect("redirect addr");
22103 let target_addr = target_listener.local_addr().expect("target addr");
22104 let redirect_data = format!(
22105 "(ADDRESS=(PROTOCOL=tcp)(HOST=127.0.0.1)(PORT={port}))\0\
22106 (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=127.0.0.1)(PORT={port}))\
22107 (CONNECT_DATA=(SERVICE_NAME=redirsvc)))",
22108 port = target_addr.port()
22109 );
22110
22111 let first = thread::spawn(move || -> std::io::Result<()> {
22112 let (mut socket, _) = redirect_listener.accept()?;
22113 socket.set_read_timeout(Some(Duration::from_secs(5)))?;
22114 let _ = read_tns_packet_sync(&mut socket)?;
22115 send_tns_packet_sync(&mut socket, TNS_PACKET_TYPE_RESEND, &[])?;
22118 let _ = read_tns_packet_sync(&mut socket)?;
22119 let length = u16::try_from(redirect_data.len()).expect("test redirect data fits u16");
22122 send_tns_packet_sync(&mut socket, TNS_PACKET_TYPE_REDIRECT, &length.to_be_bytes())?;
22123 send_tns_packet_sync(
22124 &mut socket,
22125 TNS_PACKET_TYPE_REDIRECT,
22126 redirect_data.as_bytes(),
22127 )?;
22128 Ok(())
22129 });
22130 let second = thread::spawn(move || -> std::io::Result<(u8, u8)> {
22131 let (mut socket, _) = target_listener.accept()?;
22132 socket.set_read_timeout(Some(Duration::from_secs(5)))?;
22133 let (_, first_flags, _) = read_tns_packet_sync(&mut socket)?;
22134 send_tns_packet_sync(&mut socket, TNS_PACKET_TYPE_RESEND, &[])?;
22137 let (_, resent_flags, _) = read_tns_packet_sync(&mut socket)?;
22138 send_tns_packet_sync(&mut socket, TNS_PACKET_TYPE_REFUSE, b"(ERR=12514)")?;
22139 Ok((first_flags, resent_flags))
22140 });
22141
22142 let options = ConnectOptions::new(
22143 format!("127.0.0.1:{}/redirsvc", redirect_addr.port()),
22144 "user",
22145 "password",
22146 identity(),
22147 );
22148 let runtime = build_io_runtime().expect("asupersync runtime");
22149 let err = runtime
22150 .block_on(async {
22151 let cx = Cx::current().expect("ambient Cx");
22152 Connection::connect(&cx, options).await
22153 })
22154 .expect_err("target listener refuses; the refusal must surface");
22155 assert!(
22156 matches!(&err, Error::ListenerRefused(msg) if msg.contains("ERR=12514")),
22157 "expected the TARGET listener's refusal, got {err:?}"
22158 );
22159 first.join().expect("redirect listener thread")?;
22160 let (first_flags, resent_flags) = second.join().expect("target listener thread")?;
22161 assert_eq!(
22162 first_flags, TNS_PACKET_FLAG_REDIRECT,
22163 "redirected CONNECT must be flagged"
22164 );
22165 assert_eq!(
22166 resent_flags, TNS_PACKET_FLAG_REDIRECT,
22167 "a RESEND on the redirected connection must keep the redirect flag"
22168 );
22169 Ok(())
22170 }
22171
22172 #[test]
22176 fn connect_redirect_loop_is_bounded() -> Result<()> {
22177 let listener = TcpListener::bind("127.0.0.1:0").expect("bind listener");
22178 let addr = listener.local_addr().expect("listener addr");
22179 let redirect_data = format!(
22180 "(ADDRESS=(PROTOCOL=tcp)(HOST=127.0.0.1)(PORT={port}))\0\
22181 (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=127.0.0.1)(PORT={port}))\
22182 (CONNECT_DATA=(SERVICE_NAME=loopsvc)))",
22183 port = addr.port()
22184 );
22185 let hops = u32::from(MAX_CONNECT_REDIRECT_ROUNDS) + 1;
22186 let server = thread::spawn(move || -> std::io::Result<u32> {
22187 let mut served = 0;
22188 for _ in 0..hops {
22191 let (mut socket, _) = listener.accept()?;
22192 socket.set_read_timeout(Some(Duration::from_secs(5)))?;
22193 let _ = read_tns_packet_sync(&mut socket)?;
22194 send_tns_packet_sync(
22195 &mut socket,
22196 TNS_PACKET_TYPE_REDIRECT,
22197 &redirect_packet_payload(&redirect_data),
22198 )?;
22199 served += 1;
22200 }
22201 Ok(served)
22202 });
22203
22204 let options = ConnectOptions::new(
22205 format!("127.0.0.1:{}/loopsvc", addr.port()),
22206 "user",
22207 "password",
22208 identity(),
22209 );
22210 let runtime = build_io_runtime().expect("asupersync runtime");
22211 let err = runtime
22212 .block_on(async {
22213 let cx = Cx::current().expect("ambient Cx");
22214 Connection::connect(&cx, options).await
22215 })
22216 .expect_err("a redirect loop must terminate with a structured error");
22217 assert!(
22218 matches!(err, Error::ConnectRedirectLoop(rounds)
22219 if rounds == MAX_CONNECT_REDIRECT_ROUNDS + 1),
22220 "expected ConnectRedirectLoop, got {err:?}"
22221 );
22222 assert_eq!(err.kind(), ErrorKind::Protocol);
22223 let served = server.join().expect("listener thread")?;
22224 assert_eq!(served, hops, "every hop reached the listener");
22225 Ok(())
22226 }
22227
22228 #[test]
22233 fn connect_resend_loop_is_bounded() -> Result<()> {
22234 let listener = TcpListener::bind("127.0.0.1:0").expect("bind listener");
22235 let addr = listener.local_addr().expect("listener addr");
22236 let hops = u32::from(MAX_CONNECT_RESEND_ROUNDS) + 1;
22237 let server = thread::spawn(move || -> std::io::Result<u32> {
22240 let (mut socket, _) = listener.accept()?;
22241 socket.set_read_timeout(Some(Duration::from_secs(5)))?;
22242 let mut served = 0;
22243 for _ in 0..hops {
22244 let _ = read_tns_packet_sync(&mut socket)?;
22245 send_tns_packet_sync(&mut socket, TNS_PACKET_TYPE_RESEND, &[])?;
22246 served += 1;
22247 }
22248 Ok(served)
22249 });
22250
22251 let options = ConnectOptions::new(
22252 format!("127.0.0.1:{}/resendsvc", addr.port()),
22253 "user",
22254 "password",
22255 identity(),
22256 );
22257 let runtime = build_io_runtime().expect("asupersync runtime");
22258 let err = runtime
22259 .block_on(async {
22260 let cx = Cx::current().expect("ambient Cx");
22261 Connection::connect(&cx, options).await
22262 })
22263 .expect_err("a resend loop must terminate with a structured error");
22264 assert!(
22265 matches!(err, Error::ConnectResendLoop(rounds)
22266 if rounds == MAX_CONNECT_RESEND_ROUNDS + 1),
22267 "expected ConnectResendLoop, got {err:?}"
22268 );
22269 assert_eq!(err.kind(), ErrorKind::Protocol);
22270 let served = server.join().expect("listener thread")?;
22271 assert_eq!(served, hops, "every round reached the listener");
22272 Ok(())
22273 }
22274
22275 #[test]
22283 fn connect_all_addresses_failed_when_every_address_refuses() {
22284 let closed_port = |listener: TcpListener| -> u16 {
22285 let port = listener.local_addr().expect("addr").port();
22286 drop(listener);
22287 port
22288 };
22289 let port_a = closed_port(TcpListener::bind("127.0.0.1:0").expect("bind a"));
22290 let port_b = closed_port(TcpListener::bind("127.0.0.1:0").expect("bind b"));
22291
22292 let connect_string = format!(
22293 "(DESCRIPTION=(RETRY_COUNT=0)\
22294 (ADDRESS_LIST=(ADDRESS=(PROTOCOL=tcp)(HOST=127.0.0.1)(PORT={port_a}))\
22295 (ADDRESS=(PROTOCOL=tcp)(HOST=127.0.0.1)(PORT={port_b})))\
22296 (CONNECT_DATA=(SERVICE_NAME=svc)))"
22297 );
22298 let options = ConnectOptions::new(connect_string, "user", "password", identity());
22299 let runtime = build_io_runtime().expect("asupersync runtime");
22300 let err = runtime
22301 .block_on(async {
22302 let cx = Cx::current().expect("ambient Cx");
22303 Connection::connect(&cx, options).await
22304 })
22305 .expect_err("every address refuses; the aggregate must surface");
22306 assert!(
22307 matches!(&err, Error::AllAddressesFailed(detail) if detail.contains(&port_a.to_string())
22308 && detail.contains(&port_b.to_string())),
22309 "expected AllAddressesFailed naming both addresses, got {err:?}"
22310 );
22311 assert_eq!(err.kind(), ErrorKind::Network);
22312 }
22313
22314 #[test]
22320 fn connection_connect_returns_tls_configuration_error_without_dialling() {
22321 let listener = TcpListener::bind("127.0.0.1:0").expect("bind dial sentinel");
22322 listener
22323 .set_nonblocking(true)
22324 .expect("make dial sentinel nonblocking");
22325 let port = listener.local_addr().expect("dial sentinel address").port();
22326 let wallet = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
22327 .join("tests/fixtures/tls/invalid_client_key");
22328 let options = ConnectOptions::new(
22329 format!("tcps://127.0.0.1:{port}/svc"),
22330 "user",
22331 "password",
22332 identity(),
22333 )
22334 .with_wallet_location(wallet.display().to_string());
22335
22336 let runtime = build_io_runtime().expect("asupersync runtime");
22337 let error = runtime
22338 .block_on(async {
22339 let cx = Cx::current().expect("ambient Cx");
22340 Connection::connect(&cx, options).await
22341 })
22342 .expect_err("the invalid client key must fail TLS preparation");
22343 assert!(
22344 matches!(&error, Error::Tls(detail) if detail.contains("private key")),
22345 "the original typed TLS configuration error must surface, got {error:?}"
22346 );
22347
22348 match listener.accept() {
22349 Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => {}
22350 Ok(_) => panic!("deterministic TLS configuration failure dialled the listener"),
22351 Err(error) => panic!("dial sentinel accept failed unexpectedly: {error}"),
22352 }
22353 }
22354
22355 #[test]
22365 fn unexpected_packet_error_names_tns_packet_types() {
22366 assert_eq!(
22367 Error::UnexpectedPacket(TNS_PACKET_TYPE_RESEND).to_string(),
22368 "unexpected TNS packet type 11 (RESEND)"
22369 );
22370 assert_eq!(
22371 Error::UnexpectedPacket(99).to_string(),
22372 "unexpected TNS packet type 99 (unknown)"
22373 );
22374 }
22375
22376 #[test]
22381 fn flag_framed_reader_labels_non_data_packet_as_packet_layer_error() -> Result<()> {
22382 let listener = TcpListener::bind("127.0.0.1:0").expect("bind local listener");
22383 let addr = listener.local_addr().expect("listener address");
22384 let server = thread::spawn(move || {
22385 let (mut socket, _) = listener.accept().expect("accept test client");
22386 use std::io::Write as _;
22387 let packet = encode_packet(
22388 TNS_PACKET_TYPE_RESEND,
22389 0,
22390 None,
22391 &[],
22392 PacketLengthWidth::Large32,
22393 )
22394 .expect("encode unexpected packet");
22395 socket.write_all(&packet).expect("write unexpected packet");
22396 });
22397
22398 let runtime = build_io_runtime().expect("asupersync runtime");
22399 let err = runtime.block_on(async {
22400 let cx = Cx::current().expect("ambient Cx");
22401 let stream = TcpStream::connect(addr).await.expect("connect to listener");
22402 let (read, write) = transport::plain_split(stream);
22403 let mut conn = loopback_connection(read, write);
22404 conn.core
22405 .read_data_response(&cx)
22406 .await
22407 .expect_err("non-DATA packet must fail closed")
22408 });
22409
22410 assert!(
22411 matches!(err, Error::UnexpectedPacket(TNS_PACKET_TYPE_RESEND)),
22412 "expected the packet-layer error, got {err:?}"
22413 );
22414 assert!(
22415 err.to_string().contains("TNS packet type 11 (RESEND)"),
22416 "error text must name the TNS packet type: {err}"
22417 );
22418 server.join().expect("server thread joins");
22419 Ok(())
22420 }
22421
22422 #[test]
22427 fn break_drain_labels_non_data_packet_as_packet_layer_error() -> Result<()> {
22428 let listener = TcpListener::bind("127.0.0.1:0").expect("bind local listener");
22429 let addr = listener.local_addr().expect("listener address");
22430 let server = thread::spawn(move || {
22431 let (mut socket, _) = listener.accept().expect("accept test client");
22432 use std::io::Write as _;
22433 let packet = encode_packet(
22434 TNS_PACKET_TYPE_ACCEPT,
22435 0,
22436 None,
22437 &[0x00],
22438 PacketLengthWidth::Large32,
22439 )
22440 .expect("encode unexpected packet");
22441 socket.write_all(&packet).expect("write unexpected packet");
22442 });
22443
22444 let runtime = build_io_runtime().expect("asupersync runtime");
22445 let err = runtime.block_on(async {
22446 let stream = TcpStream::connect(addr).await.expect("connect to listener");
22447 let (mut read, write) = transport::plain_split(stream);
22448 let write = Arc::new(AsyncMutex::with_name("break_drain_test_write", write));
22449 drain_break_response_recovery(&mut read, &write)
22450 .await
22451 .expect_err("non-DATA/non-MARKER packet must fail closed")
22452 });
22453
22454 assert!(
22455 matches!(err, Error::UnexpectedPacket(TNS_PACKET_TYPE_ACCEPT)),
22456 "expected the packet-layer error, got {err:?}"
22457 );
22458 assert!(
22459 err.to_string().contains("TNS packet type 2 (ACCEPT)"),
22460 "error text must name the TNS packet type: {err}"
22461 );
22462 server.join().expect("server thread joins");
22463 Ok(())
22464 }
22465}