1#![forbid(unsafe_code)]
2
3use super::*;
4use crate::wire::ProtocolLimits;
5
6#[inline]
17fn validate_utf8(slice: &[u8]) -> core::result::Result<&str, ()> {
18 #[cfg(feature = "simd-decode")]
19 {
20 simdutf8::basic::from_utf8(slice).map_err(|_| ())
21 }
22 #[cfg(not(feature = "simd-decode"))]
23 {
24 core::str::from_utf8(slice).map_err(|_| ())
25 }
26}
27
28#[derive(Clone, Copy, Debug, Eq, PartialEq)]
29pub(crate) enum LobDecodeMode {
30 PlainLocator,
31 DefineMetadata,
32}
33
34pub fn build_fetch_payload(cursor_id: u32, arraysize: u32, ttc_field_version: u8) -> Vec<u8> {
35 build_fetch_payload_with_seq(cursor_id, arraysize, 1, ttc_field_version)
36}
37
38pub fn build_fetch_payload_with_seq(
39 cursor_id: u32,
40 arraysize: u32,
41 seq_num: u8,
42 ttc_field_version: u8,
43) -> Vec<u8> {
44 let mut writer = TtcWriter::with_capacity(32);
48 writer.write_function_header(TNS_FUNC_FETCH, seq_num, ttc_field_version);
49 writer.write_ub4(cursor_id);
50 writer.write_ub4(arraysize);
51 writer.into_bytes()
52}
53
54pub fn build_define_fetch_payload_with_seq(
55 cursor_id: u32,
56 arraysize: u32,
57 seq_num: u8,
58 define_columns: &[ColumnMetadata],
59 ttc_field_version: u8,
60) -> Result<Vec<u8>> {
61 let define_count =
62 u32::try_from(define_columns.len()).map_err(|_| ProtocolError::InvalidPacketLength {
63 length: define_columns.len(),
64 minimum: 0,
65 })?;
66 let mut writer = TtcWriter::new();
67 writer.write_function_header(TNS_FUNC_EXECUTE, seq_num, ttc_field_version);
68 writer.write_ub4(TNS_EXEC_OPTION_DEFINE | TNS_EXEC_OPTION_NOT_PLSQL);
69 writer.write_ub4(cursor_id);
70 writer.write_u8(0);
71 writer.write_ub4(0);
72 writer.write_u8(1);
73 writer.write_ub4(13);
74 writer.write_u8(0);
75 writer.write_u8(0);
76 writer.write_ub4(0);
77 writer.write_ub4(arraysize);
78 writer.write_ub4(TNS_MAX_LONG_LENGTH);
79 writer.write_u8(0);
80 writer.write_ub4(0);
81 writer.write_u8(0);
82 writer.write_u8(0);
83 writer.write_u8(0);
84 writer.write_u8(0);
85 writer.write_u8(0);
86 writer.write_u8(1);
87 writer.write_ub4(define_count);
88 writer.write_ub4(0);
89 writer.write_u8(0);
90 writer.write_u8(1);
91 writer.write_u8(0);
92 writer.write_ub4(0);
93 writer.write_u8(0);
94 writer.write_ub4(0);
95 writer.write_ub4(0);
96 writer.write_u8(0);
97 writer.write_ub4(0);
98 writer.write_u8(0);
99 writer.write_u8(0);
100 writer.write_ub4(0);
101 writer.write_ub4(0);
102 writer.write_ub4(0);
103 writer.write_ub4(0);
104 writer.write_ub4(0);
105 writer.write_ub4(0);
106 writer.write_ub4(0);
107 writer.write_ub4(arraysize);
108 writer.write_ub4(0);
109 writer.write_ub4(0);
110 writer.write_ub4(0);
111 writer.write_ub4(0);
112 writer.write_ub4(0);
113 writer.write_ub4(1);
114 writer.write_ub4(0);
115 writer.write_ub4(0);
116 writer.write_ub4(0);
117 writer.write_ub4(0);
118 writer.write_ub4(0);
119 for metadata in define_columns {
120 write_define_column_metadata(&mut writer, metadata);
121 }
122 Ok(writer.into_bytes())
123}
124
125pub(crate) fn write_define_column_metadata(writer: &mut TtcWriter, metadata: &ColumnMetadata) {
126 let (mut buffer_size, cont_flags, lob_prefetch_length) = match metadata.ora_type_num {
130 ORA_TYPE_NUM_CLOB | ORA_TYPE_NUM_BLOB => (metadata.buffer_size, TNS_LOB_PREFETCH_FLAG, 0),
131 ORA_TYPE_NUM_VECTOR => (
132 TNS_VECTOR_MAX_LENGTH,
133 TNS_LOB_PREFETCH_FLAG,
134 TNS_VECTOR_MAX_LENGTH,
135 ),
136 ORA_TYPE_NUM_JSON => (
137 TNS_JSON_MAX_LENGTH,
138 TNS_LOB_PREFETCH_FLAG,
139 TNS_JSON_MAX_LENGTH,
140 ),
141 _ => (metadata.buffer_size, 0, 0),
142 };
143 buffer_size = buffer_size.max(1);
144 writer.write_u8(metadata.ora_type_num);
145 writer.write_u8(TNS_BIND_USE_INDICATORS);
146 writer.write_u8(0);
147 writer.write_u8(0);
148 writer.write_ub4(buffer_size);
149 writer.write_ub4(0);
150 writer.write_ub8(cont_flags);
151 writer.write_ub4(0);
152 writer.write_ub2(0);
153 if metadata.csfrm != 0 {
154 writer.write_ub2(TNS_CHARSET_UTF8);
155 } else {
156 writer.write_ub2(0);
157 }
158 writer.write_u8(metadata.csfrm);
159 writer.write_ub4(lob_prefetch_length);
160 writer.write_ub4(0);
161}
162
163pub fn parse_query_response(
164 payload: &[u8],
165 capabilities: ClientCapabilities,
166) -> Result<QueryResult> {
167 parse_query_response_with_previous(payload, capabilities, None)
168}
169
170pub fn parse_query_response_with_limits(
171 payload: &[u8],
172 capabilities: ClientCapabilities,
173 limits: ProtocolLimits,
174) -> Result<QueryResult> {
175 parse_query_response_with_context_binds_options_and_limits(
176 payload,
177 capabilities,
178 &[],
179 None,
180 &[],
181 &[],
182 false,
183 ExecuteOptions::default(),
184 limits,
185 )
186}
187
188pub fn parse_query_response_with_binds(
189 payload: &[u8],
190 capabilities: ClientCapabilities,
191 binds: &[BindValue],
192) -> Result<QueryResult> {
193 parse_query_response_with_binds_and_options(
194 payload,
195 capabilities,
196 binds,
197 ExecuteOptions::default(),
198 )
199}
200
201pub fn parse_query_response_with_binds_and_options(
202 payload: &[u8],
203 capabilities: ClientCapabilities,
204 binds: &[BindValue],
205 exec_options: ExecuteOptions,
206) -> Result<QueryResult> {
207 parse_query_response_with_binds_options_and_columns(
208 payload,
209 capabilities,
210 binds,
211 exec_options,
212 &[],
213 )
214}
215
216pub fn parse_query_response_with_binds_options_and_columns(
220 payload: &[u8],
221 capabilities: ClientCapabilities,
222 binds: &[BindValue],
223 exec_options: ExecuteOptions,
224 known_columns: &[ColumnMetadata],
225) -> Result<QueryResult> {
226 let bind_columns = binds.iter().map(bind_column_metadata).collect::<Vec<_>>();
227 let output_bind_indexes = binds
228 .iter()
229 .enumerate()
230 .filter_map(|(index, value)| value.is_return_output().then_some(index))
231 .collect::<Vec<_>>();
232 parse_query_response_with_context_binds_and_options(
233 payload,
234 capabilities,
235 known_columns,
236 None,
237 &bind_columns,
238 &output_bind_indexes,
239 false,
240 exec_options,
241 )
242}
243
244pub fn parse_query_response_with_binds_options_columns_and_limits(
245 payload: &[u8],
246 capabilities: ClientCapabilities,
247 binds: &[BindValue],
248 exec_options: ExecuteOptions,
249 known_columns: &[ColumnMetadata],
250 limits: ProtocolLimits,
251) -> Result<QueryResult> {
252 limits.check_binds(binds.len())?;
253 let bind_columns = binds.iter().map(bind_column_metadata).collect::<Vec<_>>();
254 let output_bind_indexes = binds
255 .iter()
256 .enumerate()
257 .filter_map(|(index, value)| value.is_return_output().then_some(index))
258 .collect::<Vec<_>>();
259 parse_query_response_with_context_binds_options_and_limits(
260 payload,
261 capabilities,
262 known_columns,
263 None,
264 &bind_columns,
265 &output_bind_indexes,
266 false,
267 exec_options,
268 limits,
269 )
270}
271
272pub fn parse_query_response_with_previous(
273 payload: &[u8],
274 capabilities: ClientCapabilities,
275 previous_row: Option<&[Option<QueryValue>]>,
276) -> Result<QueryResult> {
277 parse_query_response_with_context(payload, capabilities, &[], previous_row)
278}
279
280pub fn parse_query_response_with_context(
281 payload: &[u8],
282 capabilities: ClientCapabilities,
283 previous_columns: &[ColumnMetadata],
284 previous_row: Option<&[Option<QueryValue>]>,
285) -> Result<QueryResult> {
286 parse_query_response_with_context_and_binds(
287 payload,
288 capabilities,
289 previous_columns,
290 previous_row,
291 &[],
292 &[],
293 false,
294 ProtocolLimits::DEFAULT,
295 )
296}
297
298pub fn parse_fetch_response_with_context(
299 payload: &[u8],
300 capabilities: ClientCapabilities,
301 previous_columns: &[ColumnMetadata],
302 previous_row: Option<&[Option<QueryValue>]>,
303) -> Result<QueryResult> {
304 parse_fetch_response_with_context_and_limits(
305 payload,
306 capabilities,
307 previous_columns,
308 previous_row,
309 ProtocolLimits::DEFAULT,
310 )
311}
312
313pub fn parse_fetch_response_with_context_and_limits(
314 payload: &[u8],
315 capabilities: ClientCapabilities,
316 previous_columns: &[ColumnMetadata],
317 previous_row: Option<&[Option<QueryValue>]>,
318 limits: ProtocolLimits,
319) -> Result<QueryResult> {
320 parse_query_response_with_context_binds_options_lob_mode_and_limits(
321 payload,
322 capabilities,
323 previous_columns,
324 previous_row,
325 &[],
326 &[],
327 true,
328 ExecuteOptions::default(),
329 LobDecodeMode::PlainLocator,
330 limits,
331 )
332}
333
334pub fn parse_define_fetch_response_with_context_and_limits(
335 payload: &[u8],
336 capabilities: ClientCapabilities,
337 previous_columns: &[ColumnMetadata],
338 previous_row: Option<&[Option<QueryValue>]>,
339 limits: ProtocolLimits,
340) -> Result<QueryResult> {
341 parse_query_response_with_context_binds_options_lob_mode_and_limits(
342 payload,
343 capabilities,
344 previous_columns,
345 previous_row,
346 &[],
347 &[],
348 true,
349 ExecuteOptions::default(),
350 LobDecodeMode::DefineMetadata,
351 limits,
352 )
353}
354
355#[allow(clippy::too_many_arguments)] pub(crate) fn parse_query_response_with_context_and_binds(
357 payload: &[u8],
358 capabilities: ClientCapabilities,
359 previous_columns: &[ColumnMetadata],
360 previous_row: Option<&[Option<QueryValue>]>,
361 bind_columns: &[ColumnMetadata],
362 output_bind_indexes: &[usize],
363 fetch_long_status: bool,
364 limits: ProtocolLimits,
365) -> Result<QueryResult> {
366 parse_query_response_with_context_binds_options_and_limits(
367 payload,
368 capabilities,
369 previous_columns,
370 previous_row,
371 bind_columns,
372 output_bind_indexes,
373 fetch_long_status,
374 ExecuteOptions::default(),
375 limits,
376 )
377}
378
379#[allow(clippy::too_many_arguments)] pub(crate) fn parse_query_response_with_context_binds_and_options(
381 payload: &[u8],
382 capabilities: ClientCapabilities,
383 previous_columns: &[ColumnMetadata],
384 previous_row: Option<&[Option<QueryValue>]>,
385 bind_columns: &[ColumnMetadata],
386 output_bind_indexes: &[usize],
387 fetch_long_status: bool,
388 exec_options: ExecuteOptions,
389) -> Result<QueryResult> {
390 parse_query_response_with_context_binds_options_and_limits(
391 payload,
392 capabilities,
393 previous_columns,
394 previous_row,
395 bind_columns,
396 output_bind_indexes,
397 fetch_long_status,
398 exec_options,
399 ProtocolLimits::DEFAULT,
400 )
401}
402
403#[allow(clippy::too_many_arguments)] pub(crate) fn parse_query_response_with_context_binds_options_and_limits(
405 payload: &[u8],
406 capabilities: ClientCapabilities,
407 previous_columns: &[ColumnMetadata],
408 previous_row: Option<&[Option<QueryValue>]>,
409 bind_columns: &[ColumnMetadata],
410 output_bind_indexes: &[usize],
411 fetch_long_status: bool,
412 exec_options: ExecuteOptions,
413 limits: ProtocolLimits,
414) -> Result<QueryResult> {
415 parse_query_response_with_context_binds_options_lob_mode_and_limits(
416 payload,
417 capabilities,
418 previous_columns,
419 previous_row,
420 bind_columns,
421 output_bind_indexes,
422 fetch_long_status,
423 exec_options,
424 LobDecodeMode::DefineMetadata,
425 limits,
426 )
427}
428
429#[allow(clippy::too_many_arguments)] fn parse_query_response_with_context_binds_options_lob_mode_and_limits(
431 payload: &[u8],
432 capabilities: ClientCapabilities,
433 previous_columns: &[ColumnMetadata],
434 previous_row: Option<&[Option<QueryValue>]>,
435 bind_columns: &[ColumnMetadata],
436 output_bind_indexes: &[usize],
437 fetch_long_status: bool,
438 exec_options: ExecuteOptions,
439 lob_decode_mode: LobDecodeMode,
440 limits: ProtocolLimits,
441) -> Result<QueryResult> {
442 let mut reader = TtcReader::with_limits(payload, limits)?;
443 let mut result = QueryResult {
444 columns: previous_columns.to_vec(),
445 more_rows: true,
446 ..QueryResult::default()
447 };
448 let mut fetch_long_status = fetch_long_status;
456 let mut bit_vector: Option<Vec<u8>> = None;
457 let mut out_bind_indexes: Vec<usize> = Vec::new();
458 while reader.remaining() > 0 {
459 let message_type = reader.read_u8()?;
460 match message_type {
461 0 => {}
462 TNS_MSG_TYPE_DESCRIBE_INFO => {
463 let _describe_name = reader.read_bytes()?;
464 let previous = std::mem::take(&mut result.columns);
465 parse_describe_info(&mut reader, capabilities, &mut result)?;
466 for (index, column) in result.columns.iter_mut().enumerate() {
473 if let Some(prev) = previous.get(index) {
474 if adjust_refetch_metadata(prev, column) {
475 fetch_long_status = true;
478 }
479 }
480 }
481 }
482 TNS_MSG_TYPE_ROW_HEADER => {
483 bit_vector = parse_row_header(&mut reader)?;
484 }
485 TNS_MSG_TYPE_ROW_DATA => {
486 if result.columns.is_empty() && !out_bind_indexes.is_empty() {
487 parse_out_bind_row_data(
488 &mut reader,
489 &mut result,
490 bind_columns,
491 &out_bind_indexes,
492 )?;
493 } else if result.columns.is_empty() && !output_bind_indexes.is_empty() {
494 parse_returning_row_data(
495 &mut reader,
496 &mut result,
497 bind_columns,
498 output_bind_indexes,
499 )?;
500 } else {
501 parse_row_data(
502 &mut reader,
503 &mut result,
504 bit_vector.as_deref(),
505 previous_row,
506 fetch_long_status,
507 lob_decode_mode,
508 )?;
509 }
510 bit_vector = None;
511 }
512 TNS_MSG_TYPE_BIT_VECTOR => {
513 bit_vector = Some(parse_bit_vector(&mut reader, result.columns.len())?);
514 }
515 TNS_MSG_TYPE_PARAMETER => {
516 let params =
517 parse_query_return_parameters(&mut reader, exec_options.arraydmlrowcounts)?;
518 if exec_options.arraydmlrowcounts {
519 result.array_dml_row_counts = Some(params.row_counts.unwrap_or_default());
520 }
521 if params.query_id.is_some() {
522 result.query_id = params.query_id;
523 }
524 }
525 TNS_MSG_TYPE_STATUS => {
526 let call_status = reader.read_ub4()?;
527 let _seq = reader.read_ub2()?;
528 result.txn_in_progress = Some(call_status & TNS_EOCS_FLAGS_TXN_IN_PROGRESS != 0);
529 }
530 TNS_MSG_TYPE_IO_VECTOR => {
531 out_bind_indexes = parse_io_vector(&mut reader, bind_columns.len())?
532 .into_iter()
533 .filter(|index| !output_bind_indexes.contains(index))
534 .collect();
535 }
536 TNS_MSG_TYPE_FLUSH_OUT_BINDS => break,
537 TNS_MSG_TYPE_SERVER_SIDE_PIGGYBACK => {
538 if let Some(update) = skip_server_side_piggyback(&mut reader)? {
539 result.sessionless_txn_state = Some(update);
540 }
541 }
542 TNS_MSG_TYPE_IMPLICIT_RESULTSET => {
543 let num_results = reader.read_ub4()?;
545 let mut resultsets: Vec<QueryValue> = reader.with_capacity_limited(
552 num_results as usize,
553 1,
554 ProtocolLimits::check_length_prefixed_elements,
555 )?;
556 for _ in 0..num_results {
557 let num_bytes = reader.read_u8()?;
558 reader.skip(usize::from(num_bytes))?;
559 let mut child = QueryResult::default();
560 parse_describe_info(&mut reader, capabilities, &mut child)?;
561 let child_cursor_id = u32::from(reader.read_ub2()?);
562 resultsets.push(QueryValue::Cursor(Box::new(CursorValue {
563 columns: child.columns,
564 cursor_id: child_cursor_id,
565 })));
566 }
567 result.implicit_resultsets = Some(resultsets);
568 }
569 TNS_MSG_TYPE_END_OF_RESPONSE => break,
570 TNS_MSG_TYPE_TOKEN => {
574 result.token_num = Some(reader.read_ub8()?);
575 }
576 TNS_MSG_TYPE_ERROR => {
577 let info = parse_server_error_info(&mut reader, capabilities.ttc_field_version)?;
578 result.txn_in_progress =
582 Some(info.call_status & TNS_EOCS_FLAGS_TXN_IN_PROGRESS != 0);
583 if info.cursor_id != 0 {
584 result.cursor_id = u32::from(info.cursor_id);
585 }
586 result.row_count = info.row_count;
587 result.compilation_error_warning |= info.compilation_error_warning;
588 result.last_rowid = info.rowid.clone();
589 if info.number == TNS_ERR_NO_DATA_FOUND && !result.columns.is_empty() {
590 result.more_rows = false;
591 } else if info.number == TNS_ERR_ARRAY_DML_ERRORS {
592 result.batch_errors = info.batch_errors;
596 } else if info.number != 0 {
597 let mut details = info.into_details();
598 details.array_dml_row_counts = result.array_dml_row_counts.take();
599 return Err(ProtocolError::ServerErrorInfo(Box::new(details)));
600 }
601 }
602 _ => {
603 let position = reader.position().saturating_sub(1);
604 if let Some(message) =
605 find_embedded_server_error(payload, capabilities.ttc_field_version, position)
606 {
607 return Err(ProtocolError::ServerError(message));
608 }
609 return Err(ProtocolError::UnknownMessageType {
610 message_type,
611 position,
612 });
613 }
614 }
615 }
616 Ok(result)
617}
618
619pub(crate) fn bind_column_metadata(value: &BindValue) -> ColumnMetadata {
620 let (ora_type_num, csfrm, buffer_size) = bind_metadata(value);
621 let object_schema = match value {
622 BindValue::ObjectOutput { schema, .. } | BindValue::ObjectInput { schema, .. } => {
623 Some(schema.clone())
624 }
625 _ => None,
626 };
627 let object_type_name = match value {
628 BindValue::ObjectOutput { type_name, .. } | BindValue::ObjectInput { type_name, .. } => {
629 Some(type_name.clone())
630 }
631 _ => None,
632 };
633 ColumnMetadata {
634 name: String::new(),
635 ora_type_num,
636 csfrm,
637 precision: 0,
638 scale: 0,
639 buffer_size,
640 max_size: buffer_size,
641 nulls_allowed: true,
642 is_json: false,
643 is_oson: false,
644 object_schema,
645 object_type_name,
646 is_array: matches!(value, BindValue::Array { .. }),
647 vector_dimensions: None,
648 vector_format: 0,
649 vector_flags: 0,
650 domain_schema: None,
651 domain_name: None,
652 annotations: None,
653 }
654}
655
656pub(crate) fn parse_io_vector(reader: &mut TtcReader<'_>, bind_count: usize) -> Result<Vec<usize>> {
657 let _flags = reader.read_u8()?;
658 let temp16 = reader.read_ub2()?;
659 let temp32 = reader.read_ub4()?;
660 let num_binds = usize::try_from(temp32)
661 .map_err(|_| ProtocolError::InvalidPacketLength {
662 length: usize::MAX,
663 minimum: 0,
664 })?
665 .checked_mul(256)
666 .and_then(|value| value.checked_add(usize::from(temp16)))
667 .ok_or(ProtocolError::InvalidPacketLength {
668 length: usize::MAX,
669 minimum: 0,
670 })?;
671 let _num_iters_this_time = reader.read_ub4()?;
672 let _uac_buffer_length = reader.read_ub2()?;
673 let fast_fetch_len = reader.read_ub2()?;
674 if fast_fetch_len > 0 {
675 reader.skip(usize::from(fast_fetch_len))?;
676 }
677 let rowid_len = reader.read_ub2()?;
678 if rowid_len > 0 {
679 reader.skip(usize::from(rowid_len))?;
680 }
681 let mut out_indexes = Vec::new();
682 reader.limits().check_binds(num_binds)?;
683 for index in 0..num_binds {
684 let direction = reader.read_u8()?;
685 if index < bind_count && direction != TNS_BIND_DIR_INPUT {
686 out_indexes.push(index);
687 }
688 }
689 Ok(out_indexes)
690}
691
692pub(crate) fn find_embedded_server_error(
693 payload: &[u8],
694 ttc_field_version: u8,
695 position: usize,
696) -> Option<String> {
697 let start = position.saturating_sub(64);
698 for candidate in start..=position {
699 if !matches!(payload.get(candidate).copied(), Some(TNS_MSG_TYPE_ERROR)) {
700 continue;
701 }
702 let mut reader = TtcReader::new(payload.get(candidate + 1..)?);
703 let info = parse_server_error_info(&mut reader, ttc_field_version).ok()?;
704 if info.number != 0 && info.message.starts_with("ORA-") {
705 return Some(info.message);
706 }
707 }
708 None
709}
710
711pub(crate) fn parse_describe_info(
712 reader: &mut TtcReader<'_>,
713 capabilities: ClientCapabilities,
714 result: &mut QueryResult,
715) -> Result<()> {
716 let _max_row_size = reader.read_ub4()?;
717 let num_columns = reader.read_ub4()?;
718 reader.limits().check_columns(num_columns as usize)?;
719 result.columns.clear();
720 if num_columns > 0 {
721 reader.skip(1)?;
722 }
723 for _ in 0..num_columns {
724 result
725 .columns
726 .push(parse_column_metadata(reader, capabilities)?);
727 }
728 let _current_date = reader.read_bytes_with_length()?;
729 let _dcbflag = reader.read_ub4()?;
730 let _dcbmdbz = reader.read_ub4()?;
731 let _dcbmnpr = reader.read_ub4()?;
732 let _dcbmxpr = reader.read_ub4()?;
733 let _dcbqcky = reader.read_bytes_with_length()?;
734 Ok(())
735}
736
737pub(crate) fn parse_column_metadata(
738 reader: &mut TtcReader<'_>,
739 capabilities: ClientCapabilities,
740) -> Result<ColumnMetadata> {
741 let ora_type_num = reader.read_u8()?;
742 reader.skip(1)?;
743 let precision = reader.read_i8()?;
744 let scale = reader.read_i8()?;
745 let buffer_size = reader.read_ub4()?;
746 let _max_array_elements = reader.read_ub4()?;
747 let _cont_flags = reader.read_ub8()?;
748 let _oid = reader.read_bytes_with_length()?;
749 let _version = reader.read_ub2()?;
750 let _charset_id = reader.read_ub2()?;
751 let csfrm = reader.read_u8()?;
752 let mut max_size = reader.read_ub4()?;
753 if ora_type_num == ORA_TYPE_NUM_RAW {
754 max_size = buffer_size;
755 }
756 if version_gates::carries_oaccolid(capabilities.ttc_field_version) {
757 let _oaccolid = reader.read_ub4()?;
758 }
759 let nulls_allowed = reader.read_u8()? != 0;
760 reader.skip(1)?;
761 let name = reader.read_string_with_length()?.unwrap_or_default();
762 let object_schema = reader.read_string_with_length()?;
763 let object_type_name = reader.read_string_with_length()?;
764 let _column_position = reader.read_ub2()?;
765 let uds_flags = reader.read_ub4()?;
766 let mut domain_schema = None;
767 let mut domain_name = None;
768 let mut annotations: Option<Vec<(String, String)>> = None;
769 if version_gates::reads_column_domain(capabilities.ttc_field_version) {
770 domain_schema = reader.read_string_with_length()?;
771 domain_name = reader.read_string_with_length()?;
772 }
773 if version_gates::reads_column_annotations(capabilities.ttc_field_version) {
774 let num_annotations = reader.read_ub4()?;
775 if num_annotations > 0 {
776 reader.skip(1)?;
777 let num_annotations = reader.read_ub4()?;
778 reader.skip(1)?;
779 let mut collected: Vec<(String, String)> = reader.with_capacity_limited(
783 num_annotations as usize,
784 1,
785 ProtocolLimits::check_object_elements,
786 )?;
787 for _ in 0..num_annotations {
788 let key = reader.read_string_with_length()?.unwrap_or_default();
789 let value = reader.read_string_with_length()?.unwrap_or_default();
792 let _flags = reader.read_ub4()?;
793 collected.push((key, value));
794 }
795 let _flags = reader.read_ub4()?;
796 annotations = Some(collected);
797 }
798 }
799 let mut vector_dimensions = None;
800 let mut vector_format = 0u8;
801 let mut vector_flags = 0u8;
802 if version_gates::reads_column_vector_metadata(capabilities.ttc_field_version) {
803 let dims = reader.read_ub4()?;
805 reader.limits().check_vector_dimensions(dims as usize)?;
806 vector_format = reader.read_u8()?;
807 vector_flags = reader.read_u8()?;
808 if ora_type_num == ORA_TYPE_NUM_VECTOR {
809 vector_dimensions = Some(dims);
810 }
811 }
812
813 Ok(ColumnMetadata {
814 name,
815 ora_type_num,
816 csfrm,
817 precision,
818 scale,
819 buffer_size,
820 max_size,
821 nulls_allowed,
822 is_json: uds_flags & TNS_UDS_FLAGS_IS_JSON != 0,
823 is_oson: uds_flags & TNS_UDS_FLAGS_IS_OSON != 0,
824 object_schema,
825 object_type_name,
826 is_array: false,
827 vector_dimensions,
828 vector_format,
829 vector_flags,
830 domain_schema,
831 domain_name,
832 annotations,
833 })
834}
835
836pub(crate) fn parse_row_header(reader: &mut TtcReader<'_>) -> Result<Option<Vec<u8>>> {
837 reader.skip(1)?;
838 let _num_requests = reader.read_ub2()?;
839 let _iteration_number = reader.read_ub4()?;
840 let _num_iters = reader.read_ub4()?;
841 let _buffer_length = reader.read_ub2()?;
842 let num_bytes = reader.read_ub4()?;
843 let bit_vector = if num_bytes > 0 {
844 reader.skip(1)?;
845 Some(reader.read_raw(num_bytes as usize)?.to_vec())
846 } else {
847 None
848 };
849 let _rxhrid = reader.read_bytes_with_length()?;
850 Ok(bit_vector)
851}
852
853pub(crate) fn parse_bit_vector(reader: &mut TtcReader<'_>, num_columns: usize) -> Result<Vec<u8>> {
854 let _num_columns_sent = reader.read_ub2()?;
855 let num_bytes = num_columns.div_ceil(8);
856 Ok(reader.read_raw(num_bytes)?.to_vec())
857}
858
859pub(crate) fn parse_row_data(
860 reader: &mut TtcReader<'_>,
861 result: &mut QueryResult,
862 bit_vector: Option<&[u8]>,
863 previous_row: Option<&[Option<QueryValue>]>,
864 fetch_long_status: bool,
865 lob_decode_mode: LobDecodeMode,
866) -> Result<()> {
867 let mut row = Vec::with_capacity(result.columns.len());
868 for (index, metadata) in result.columns.iter().enumerate() {
869 if is_duplicate_column(bit_vector, index) {
870 let previous = result
871 .rows
872 .last()
873 .map(Vec::as_slice)
874 .or(previous_row)
875 .and_then(|last| last.get(index))
876 .cloned()
877 .ok_or(ProtocolError::TtcDecode(
878 "duplicate row data without previous row",
879 ))?;
880 row.push(previous);
881 continue;
882 }
883 row.push(parse_column_value_with_lob_mode(
884 reader,
885 metadata,
886 lob_decode_mode,
887 )?);
888 if fetch_long_status
889 && matches!(
890 metadata.ora_type_num,
891 ORA_TYPE_NUM_LONG | ORA_TYPE_NUM_LONG_RAW
892 )
893 {
894 let _null_indicator = reader.read_sb4()?;
895 let _return_code = reader.read_ub4()?;
896 }
897 }
898 result.rows.push(row);
899 Ok(())
900}
901
902pub(crate) fn parse_out_bind_row_data(
903 reader: &mut TtcReader<'_>,
904 result: &mut QueryResult,
905 bind_columns: &[ColumnMetadata],
906 out_bind_indexes: &[usize],
907) -> Result<()> {
908 for index in out_bind_indexes {
909 let metadata = bind_columns.get(*index).ok_or(ProtocolError::TtcDecode(
910 "out bind index without bind metadata",
911 ))?;
912 if metadata.is_array {
913 let num_elements = usize::try_from(reader.read_ub4()?).map_err(|_| {
914 ProtocolError::InvalidPacketLength {
915 length: usize::MAX,
916 minimum: 0,
917 }
918 })?;
919 reader.limits().check_batch_rows(num_elements)?;
920 let mut values: Vec<Option<QueryValue>> =
923 reader.with_capacity_limited(num_elements, 1, ProtocolLimits::check_batch_rows)?;
924 for _ in 0..num_elements {
925 let value = parse_column_value(reader, metadata)?;
926 let actual_num_bytes = reader.read_sb4()?;
927 values.push(apply_out_bind_actual_num_bytes(
928 metadata,
929 value,
930 actual_num_bytes,
931 "truncated array OUT bind value",
932 )?);
933 }
934 result
935 .out_values
936 .push((*index, Some(QueryValue::Array(values))));
937 continue;
938 }
939 let value = parse_column_value(reader, metadata)?;
940 let actual_num_bytes = reader.read_sb4()?;
941 result.out_values.push((
942 *index,
943 apply_out_bind_actual_num_bytes(
944 metadata,
945 value,
946 actual_num_bytes,
947 "truncated OUT bind value",
948 )?,
949 ));
950 }
951 Ok(())
952}
953
954pub(crate) fn parse_returning_row_data(
955 reader: &mut TtcReader<'_>,
956 result: &mut QueryResult,
957 bind_columns: &[ColumnMetadata],
958 output_bind_indexes: &[usize],
959) -> Result<()> {
960 for index in output_bind_indexes {
961 let metadata = bind_columns.get(*index).ok_or(ProtocolError::TtcDecode(
962 "return bind index without bind metadata",
963 ))?;
964 let num_rows = usize::try_from(reader.read_ub4()?).map_err(|_| {
965 ProtocolError::InvalidPacketLength {
966 length: usize::MAX,
967 minimum: 0,
968 }
969 })?;
970 reader.limits().check_batch_rows(num_rows)?;
971 let mut values: Vec<Option<QueryValue>> =
973 reader.with_capacity_limited(num_rows, 1, ProtocolLimits::check_batch_rows)?;
974 for _ in 0..num_rows {
975 let value = parse_column_value(reader, metadata)?;
976 let actual_num_bytes = reader.read_sb4()?;
977 values.push(apply_out_bind_actual_num_bytes(
978 metadata,
979 value,
980 actual_num_bytes,
981 "truncated DML RETURNING value",
982 )?);
983 }
984 result.return_values.push((*index, values));
985 }
986 Ok(())
987}
988
989fn apply_out_bind_actual_num_bytes(
990 metadata: &ColumnMetadata,
991 value: Option<QueryValue>,
992 actual_num_bytes: i32,
993 truncation_error: &'static str,
994) -> Result<Option<QueryValue>> {
995 if actual_num_bytes < 0 && metadata.ora_type_num == ORA_TYPE_NUM_BOOLEAN {
996 return Ok(None);
997 }
998 if actual_num_bytes != 0 && value.is_some() {
999 return Err(ProtocolError::TtcDecode(truncation_error));
1000 }
1001 Ok(value)
1002}
1003
1004pub(crate) fn is_duplicate_column(bit_vector: Option<&[u8]>, column_num: usize) -> bool {
1005 let Some(bit_vector) = bit_vector else {
1006 return false;
1007 };
1008 let byte_num = column_num / 8;
1009 let bit_num = column_num % 8;
1010 bit_vector
1011 .get(byte_num)
1012 .is_some_and(|byte| byte & (1 << bit_num) == 0)
1013}
1014
1015pub(crate) fn parse_column_value(
1016 reader: &mut TtcReader<'_>,
1017 metadata: &ColumnMetadata,
1018) -> Result<Option<QueryValue>> {
1019 parse_column_value_with_lob_mode(reader, metadata, LobDecodeMode::DefineMetadata)
1020}
1021
1022fn parse_column_value_with_lob_mode(
1023 reader: &mut TtcReader<'_>,
1024 metadata: &ColumnMetadata,
1025 lob_decode_mode: LobDecodeMode,
1026) -> Result<Option<QueryValue>> {
1027 if metadata.buffer_size == 0
1028 && !matches!(
1029 metadata.ora_type_num,
1030 ORA_TYPE_NUM_LONG | ORA_TYPE_NUM_LONG_RAW | ORA_TYPE_NUM_UROWID
1031 )
1032 {
1033 return Ok(None);
1034 }
1035 match metadata.ora_type_num {
1036 ORA_TYPE_NUM_VARCHAR | ORA_TYPE_NUM_CHAR | ORA_TYPE_NUM_LONG => {
1037 let Some(bytes) = reader.read_bytes()? else {
1038 return Ok(None);
1039 };
1040 match decode_text_value(&bytes, metadata.csfrm) {
1041 Ok(value) => Ok(Some(QueryValue::Text(value))),
1042 Err(ProtocolError::TtcDecode(_)) => Ok(Some(QueryValue::TextRaw {
1046 bytes,
1047 csfrm: metadata.csfrm,
1048 })),
1049 Err(err) => Err(err),
1050 }
1051 }
1052 ORA_TYPE_NUM_RAW | ORA_TYPE_NUM_LONG_RAW => Ok(reader.read_bytes()?.map(QueryValue::Raw)),
1053 ORA_TYPE_NUM_ROWID => parse_rowid_value(reader).map(|value| value.map(QueryValue::Rowid)),
1054 ORA_TYPE_NUM_UROWID => parse_urowid_value(reader).map(|value| value.map(QueryValue::Rowid)),
1055 ORA_TYPE_NUM_NUMBER | ORA_TYPE_NUM_BINARY_INTEGER => {
1056 let Some(bytes) = reader.read_bytes()? else {
1057 return Ok(None);
1058 };
1059 decode_number_value(&bytes).map(Some)
1060 }
1061 ORA_TYPE_NUM_BINARY_DOUBLE => {
1062 let Some(bytes) = reader.read_bytes()? else {
1063 return Ok(None);
1064 };
1065 decode_binary_double(&bytes)
1066 .map(|value| Some(QueryValue::BinaryDouble(value.to_string())))
1067 }
1068 ORA_TYPE_NUM_BINARY_FLOAT => {
1069 let Some(bytes) = reader.read_bytes()? else {
1070 return Ok(None);
1071 };
1072 decode_binary_float(&bytes)
1074 .map(|value| Some(QueryValue::BinaryDouble(f64::from(value).to_string())))
1075 }
1076 ORA_TYPE_NUM_BOOLEAN => {
1077 let Some(bytes) = reader.read_bytes()? else {
1078 return Ok(None);
1079 };
1080 let is_true = matches!(bytes.last(), Some(&1));
1083 Ok(Some(QueryValue::Boolean(is_true)))
1084 }
1085 ORA_TYPE_NUM_INTERVAL_DS => {
1086 let Some(bytes) = reader.read_bytes()? else {
1087 return Ok(None);
1088 };
1089 decode_interval_ds(&bytes).map(Some)
1090 }
1091 ORA_TYPE_NUM_INTERVAL_YM => {
1092 let Some(bytes) = reader.read_bytes()? else {
1093 return Ok(None);
1094 };
1095 decode_interval_ym(&bytes).map(Some)
1096 }
1097 ORA_TYPE_NUM_DATE
1098 | ORA_TYPE_NUM_TIMESTAMP
1099 | ORA_TYPE_NUM_TIMESTAMP_LTZ
1100 | ORA_TYPE_NUM_TIMESTAMP_TZ => {
1101 let Some(bytes) = reader.read_bytes()? else {
1102 return Ok(None);
1103 };
1104 decode_datetime_value(&bytes).map(Some)
1105 }
1106 ORA_TYPE_NUM_CLOB | ORA_TYPE_NUM_BLOB | ORA_TYPE_NUM_BFILE => {
1107 parse_lob_value(reader, metadata, lob_decode_mode)
1108 }
1109 ORA_TYPE_NUM_VECTOR => parse_vector_value(reader),
1110 ORA_TYPE_NUM_JSON => parse_json_value(reader),
1111 ORA_TYPE_NUM_CURSOR => parse_cursor_value(reader).map(Some),
1112 ORA_TYPE_NUM_OBJECT => parse_object_value(reader, metadata),
1113 _ => Err(ProtocolError::UnsupportedFeature("query column type")),
1114 }
1115}
1116
1117enum ColumnSlot<'buf> {
1125 Null,
1127 Wire(QueryValueRef<'buf>),
1129 Number {
1132 range: core::ops::Range<usize>,
1133 is_integer: bool,
1134 },
1135 Owned(usize),
1138}
1139
1140fn parse_column_slot<'buf>(
1150 reader: &mut TtcReader<'buf>,
1151 metadata: &ColumnMetadata,
1152 number_arena: &mut String,
1153 owned_arena: &mut Vec<QueryValue>,
1154 digits: &mut Vec<u8>,
1155 lob_decode_mode: LobDecodeMode,
1156) -> Result<ColumnSlot<'buf>> {
1157 fn park(owned_arena: &mut Vec<QueryValue>, value: Option<QueryValue>) -> ColumnSlot<'static> {
1160 match value {
1161 None => ColumnSlot::Null,
1162 Some(value) => {
1163 owned_arena.push(value);
1164 ColumnSlot::Owned(owned_arena.len() - 1)
1165 }
1166 }
1167 }
1168
1169 if metadata.buffer_size == 0
1170 && !matches!(
1171 metadata.ora_type_num,
1172 ORA_TYPE_NUM_LONG | ORA_TYPE_NUM_LONG_RAW | ORA_TYPE_NUM_UROWID
1173 )
1174 {
1175 return Ok(ColumnSlot::Null);
1176 }
1177 match metadata.ora_type_num {
1178 ORA_TYPE_NUM_VARCHAR | ORA_TYPE_NUM_CHAR | ORA_TYPE_NUM_LONG => {
1179 match reader.read_bytes_borrowed()? {
1180 BorrowedBytes::Null => Ok(ColumnSlot::Null),
1181 BorrowedBytes::Slice(slice) if metadata.csfrm != CS_FORM_NCHAR => {
1184 match validate_utf8(slice) {
1185 Ok(text) => Ok(ColumnSlot::Wire(QueryValueRef::Text(text))),
1186 Err(_) => Ok(park(
1187 owned_arena,
1188 Some(QueryValue::TextRaw {
1189 bytes: slice.to_vec(),
1190 csfrm: metadata.csfrm,
1191 }),
1192 )),
1193 }
1194 }
1195 other => {
1198 let bytes = other.into_vec();
1199 let value = match decode_text_value(&bytes, metadata.csfrm) {
1200 Ok(text) => QueryValue::Text(text),
1201 Err(ProtocolError::TtcDecode(_)) => QueryValue::TextRaw {
1202 bytes,
1203 csfrm: metadata.csfrm,
1204 },
1205 Err(err) => return Err(err),
1206 };
1207 Ok(park(owned_arena, Some(value)))
1208 }
1209 }
1210 }
1211 ORA_TYPE_NUM_RAW | ORA_TYPE_NUM_LONG_RAW => match reader.read_bytes_borrowed()? {
1212 BorrowedBytes::Null => Ok(ColumnSlot::Null),
1213 BorrowedBytes::Slice(slice) => Ok(ColumnSlot::Wire(QueryValueRef::Raw(slice))),
1214 BorrowedBytes::Chunked(bytes) => Ok(park(owned_arena, Some(QueryValue::Raw(bytes)))),
1215 },
1216 ORA_TYPE_NUM_NUMBER | ORA_TYPE_NUM_BINARY_INTEGER => {
1217 with_small_bytes(reader, |bytes| match bytes {
1223 None => Ok(ColumnSlot::Null),
1224 Some(bytes) => {
1225 let start = number_arena.len();
1226 let is_integer = decode_number_text_into(bytes, digits, number_arena)?;
1227 Ok(ColumnSlot::Number {
1228 range: start..number_arena.len(),
1229 is_integer,
1230 })
1231 }
1232 })
1233 }
1234 ORA_TYPE_NUM_BOOLEAN => with_small_bytes(reader, |bytes| match bytes {
1235 None => Ok(ColumnSlot::Null),
1236 Some(bytes) => Ok(ColumnSlot::Wire(QueryValueRef::Boolean(matches!(
1237 bytes.last(),
1238 Some(&1)
1239 )))),
1240 }),
1241 ORA_TYPE_NUM_INTERVAL_DS => with_small_bytes(reader, |bytes| match bytes {
1242 None => Ok(ColumnSlot::Null),
1243 Some(bytes) => match decode_interval_ds(bytes)? {
1244 QueryValue::IntervalDS {
1245 days,
1246 hours,
1247 minutes,
1248 seconds,
1249 fseconds,
1250 } => Ok(ColumnSlot::Wire(QueryValueRef::IntervalDS {
1251 days,
1252 hours,
1253 minutes,
1254 seconds,
1255 fseconds,
1256 })),
1257 other => Ok(park(owned_arena, Some(other))),
1258 },
1259 }),
1260 ORA_TYPE_NUM_INTERVAL_YM => with_small_bytes(reader, |bytes| match bytes {
1261 None => Ok(ColumnSlot::Null),
1262 Some(bytes) => match decode_interval_ym(bytes)? {
1263 QueryValue::IntervalYM { years, months } => {
1264 Ok(ColumnSlot::Wire(QueryValueRef::IntervalYM {
1265 years,
1266 months,
1267 }))
1268 }
1269 other => Ok(park(owned_arena, Some(other))),
1270 },
1271 }),
1272 ORA_TYPE_NUM_DATE
1273 | ORA_TYPE_NUM_TIMESTAMP
1274 | ORA_TYPE_NUM_TIMESTAMP_LTZ
1275 | ORA_TYPE_NUM_TIMESTAMP_TZ => with_small_bytes(reader, |bytes| match bytes {
1276 None => Ok(ColumnSlot::Null),
1277 Some(bytes) => match decode_datetime_value(bytes)? {
1278 QueryValue::DateTime {
1279 year,
1280 month,
1281 day,
1282 hour,
1283 minute,
1284 second,
1285 nanosecond,
1286 } => Ok(ColumnSlot::Wire(QueryValueRef::DateTime {
1287 year,
1288 month,
1289 day,
1290 hour,
1291 minute,
1292 second,
1293 nanosecond,
1294 })),
1295 QueryValue::TimestampTz {
1296 year,
1297 month,
1298 day,
1299 hour,
1300 minute,
1301 second,
1302 nanosecond,
1303 offset_minutes,
1304 } => Ok(ColumnSlot::Wire(QueryValueRef::TimestampTz {
1305 year,
1306 month,
1307 day,
1308 hour,
1309 minute,
1310 second,
1311 nanosecond,
1312 offset_minutes,
1313 })),
1314 other => Ok(park(owned_arena, Some(other))),
1315 },
1316 }),
1317 _ => {
1321 let value = parse_column_value_with_lob_mode(reader, metadata, lob_decode_mode)?;
1322 Ok(park(owned_arena, value))
1323 }
1324 }
1325}
1326
1327fn with_small_bytes<'buf, T>(
1333 reader: &mut TtcReader<'buf>,
1334 f: impl FnOnce(Option<&[u8]>) -> Result<T>,
1335) -> Result<T> {
1336 match reader.read_bytes_borrowed()? {
1337 BorrowedBytes::Null => f(None),
1338 BorrowedBytes::Slice(slice) => f(Some(slice)),
1339 BorrowedBytes::Chunked(owned) => f(Some(&owned)),
1340 }
1341}
1342
1343impl BorrowedBytes<'_> {
1344 fn into_vec(self) -> Vec<u8> {
1347 match self {
1348 BorrowedBytes::Null => Vec::new(),
1349 BorrowedBytes::Slice(slice) => slice.to_vec(),
1350 BorrowedBytes::Chunked(owned) => owned,
1351 }
1352 }
1353}
1354
1355#[derive(Clone, Debug)]
1371pub struct BorrowedRowBatch {
1372 buffer: Vec<u8>,
1373 columns: Vec<ColumnMetadata>,
1374 row_starts: Vec<usize>,
1376 row_bit_vectors: Vec<Option<Vec<u8>>>,
1381 fetch_long_status: bool,
1384 lob_decode_mode: LobDecodeMode,
1385 previous_row_seed: Option<Vec<Option<QueryValue>>>,
1389}
1390
1391impl BorrowedRowBatch {
1392 pub fn new(buffer: Vec<u8>, columns: Vec<ColumnMetadata>, row_starts: Vec<usize>) -> Self {
1398 Self {
1399 buffer,
1400 columns,
1401 row_starts,
1402 row_bit_vectors: Vec::new(),
1403 fetch_long_status: false,
1404 lob_decode_mode: LobDecodeMode::PlainLocator,
1405 previous_row_seed: None,
1406 }
1407 }
1408
1409 pub fn row_count(&self) -> usize {
1411 self.row_starts.len()
1412 }
1413
1414 pub fn columns(&self) -> &[ColumnMetadata] {
1416 &self.columns
1417 }
1418
1419 #[cfg(test)]
1422 pub fn buffer_ptr_range(&self) -> core::ops::Range<usize> {
1423 let start = self.buffer.as_ptr() as usize;
1424 start..start + self.buffer.len()
1425 }
1426
1427 pub fn for_each_row_ref<F, E>(&self, mut callback: F) -> std::result::Result<(), E>
1437 where
1438 F: FnMut(&[Option<QueryValueRef<'_>>]) -> std::result::Result<(), E>,
1439 E: From<ProtocolError>,
1440 {
1441 let mut number_arena = String::new();
1447 let mut owned_arena: Vec<QueryValue> = Vec::new();
1448 let mut digits: Vec<u8> = Vec::new();
1454 let mut slots: Vec<Option<ColumnSlot<'_>>> = Vec::with_capacity(self.columns.len());
1455 let mut previous_owned: Vec<Option<QueryValue>> =
1460 self.previous_row_seed.clone().unwrap_or_default();
1461 let uses_bit_vectors = !self.row_bit_vectors.is_empty();
1462
1463 for (row_index, &start) in self.row_starts.iter().enumerate() {
1464 number_arena.clear();
1465 owned_arena.clear();
1466 slots.clear();
1467 let bit_vector = self
1468 .row_bit_vectors
1469 .get(row_index)
1470 .and_then(|bv| bv.as_deref());
1471
1472 let mut reader = TtcReader::new(&self.buffer[start..]);
1478 for (index, metadata) in self.columns.iter().enumerate() {
1479 if is_duplicate_column(bit_vector, index) {
1480 let previous = previous_owned.get(index).and_then(Option::as_ref);
1481 match previous {
1482 None => slots.push(None),
1483 Some(value) => {
1484 owned_arena.push(value.clone());
1485 slots.push(Some(ColumnSlot::Owned(owned_arena.len() - 1)));
1486 }
1487 }
1488 continue;
1489 }
1490 let slot = parse_column_slot(
1491 &mut reader,
1492 metadata,
1493 &mut number_arena,
1494 &mut owned_arena,
1495 &mut digits,
1496 self.lob_decode_mode,
1497 )?;
1498 slots.push(match slot {
1499 ColumnSlot::Null => None,
1500 other => Some(other),
1501 });
1502 if self.fetch_long_status
1503 && matches!(
1504 metadata.ora_type_num,
1505 ORA_TYPE_NUM_LONG | ORA_TYPE_NUM_LONG_RAW
1506 )
1507 {
1508 let _null_indicator = reader.read_sb4()?;
1509 let _return_code = reader.read_ub4()?;
1510 }
1511 }
1512
1513 let row: Vec<Option<QueryValueRef<'_>>> = slots
1521 .iter()
1522 .map(|slot| {
1523 slot.as_ref().map(|slot| match *slot {
1524 ColumnSlot::Null => unreachable!("Null slots are stored as None"),
1525 ColumnSlot::Wire(value) => value,
1526 ColumnSlot::Number {
1527 ref range,
1528 is_integer,
1529 } => QueryValueRef::Number {
1530 text: &number_arena[range.clone()],
1531 is_integer,
1532 },
1533 ColumnSlot::Owned(index) => QueryValueRef::Owned(&owned_arena[index]),
1534 })
1535 })
1536 .collect();
1537
1538 callback(&row)?;
1539
1540 if uses_bit_vectors {
1544 previous_owned.clear();
1545 previous_owned.extend(row.iter().map(|cell| cell.map(|v| v.to_owned_value())));
1546 }
1547 }
1548 Ok(())
1549 }
1550}
1551
1552#[derive(Clone, Debug)]
1556pub struct BorrowedFetchResult {
1557 pub batch: BorrowedRowBatch,
1559 pub more_rows: bool,
1561 pub cursor_id: u32,
1563 pub row_count: u64,
1565}
1566
1567pub fn parse_query_response_borrowed(
1579 payload: &[u8],
1580 capabilities: ClientCapabilities,
1581 columns: &[ColumnMetadata],
1582 previous_row: Option<&[Option<QueryValue>]>,
1583) -> Result<BorrowedFetchResult> {
1584 parse_query_response_borrowed_with_limits(
1585 payload,
1586 capabilities,
1587 columns,
1588 previous_row,
1589 ProtocolLimits::DEFAULT,
1590 )
1591}
1592
1593pub fn parse_query_response_borrowed_with_limits(
1594 payload: &[u8],
1595 capabilities: ClientCapabilities,
1596 columns: &[ColumnMetadata],
1597 previous_row: Option<&[Option<QueryValue>]>,
1598 limits: ProtocolLimits,
1599) -> Result<BorrowedFetchResult> {
1600 parse_query_response_borrowed_with_lob_mode_and_limits(
1601 payload,
1602 capabilities,
1603 columns,
1604 previous_row,
1605 LobDecodeMode::PlainLocator,
1606 limits,
1607 )
1608}
1609
1610pub fn parse_define_fetch_response_borrowed_with_limits(
1611 payload: &[u8],
1612 capabilities: ClientCapabilities,
1613 columns: &[ColumnMetadata],
1614 previous_row: Option<&[Option<QueryValue>]>,
1615 limits: ProtocolLimits,
1616) -> Result<BorrowedFetchResult> {
1617 parse_query_response_borrowed_with_lob_mode_and_limits(
1618 payload,
1619 capabilities,
1620 columns,
1621 previous_row,
1622 LobDecodeMode::DefineMetadata,
1623 limits,
1624 )
1625}
1626
1627fn parse_query_response_borrowed_with_lob_mode_and_limits(
1628 payload: &[u8],
1629 capabilities: ClientCapabilities,
1630 columns: &[ColumnMetadata],
1631 previous_row: Option<&[Option<QueryValue>]>,
1632 lob_decode_mode: LobDecodeMode,
1633 limits: ProtocolLimits,
1634) -> Result<BorrowedFetchResult> {
1635 let mut reader = TtcReader::with_limits(payload, limits)?;
1636 reader.limits().check_columns(columns.len())?;
1637 let mut result_columns = columns.to_vec();
1638 let mut more_rows = true;
1639 let mut cursor_id = 0u32;
1640 let mut row_count = 0u64;
1641 let mut row_starts: Vec<usize> = Vec::new();
1642 let mut row_bit_vectors: Vec<Option<Vec<u8>>> = Vec::new();
1643 let mut any_bit_vector = false;
1644 let mut pending_bit_vector: Option<Vec<u8>> = None;
1645 let fetch_long_status = true;
1647
1648 while reader.remaining() > 0 {
1649 let message_type = reader.read_u8()?;
1650 match message_type {
1651 0 => {}
1652 TNS_MSG_TYPE_DESCRIBE_INFO => {
1653 let _describe_name = reader.read_bytes()?;
1654 let previous = std::mem::take(&mut result_columns);
1655 let mut described = QueryResult::default();
1656 parse_describe_info(&mut reader, capabilities, &mut described)?;
1657 result_columns = described.columns;
1658 for (index, column) in result_columns.iter_mut().enumerate() {
1659 if let Some(prev) = previous.get(index) {
1660 adjust_refetch_metadata(prev, column);
1661 }
1662 }
1663 }
1664 TNS_MSG_TYPE_ROW_HEADER => {
1665 pending_bit_vector = parse_row_header(&mut reader)?;
1666 }
1667 TNS_MSG_TYPE_BIT_VECTOR => {
1668 pending_bit_vector = Some(parse_bit_vector(&mut reader, result_columns.len())?);
1669 }
1670 TNS_MSG_TYPE_ROW_DATA => {
1671 reader.limits().check_batch_rows(row_starts.len() + 1)?;
1674 row_starts.push(reader.position());
1675 let bit_vector = pending_bit_vector.take();
1676 any_bit_vector |= bit_vector.is_some();
1677 row_bit_vectors.push(bit_vector.clone());
1678 skip_row_data(
1679 &mut reader,
1680 &result_columns,
1681 bit_vector.as_deref(),
1682 fetch_long_status,
1683 lob_decode_mode,
1684 )?;
1685 }
1686 TNS_MSG_TYPE_PARAMETER => {
1687 let _params = parse_query_return_parameters(&mut reader, false)?;
1688 }
1689 TNS_MSG_TYPE_STATUS => {
1690 let _call_status = reader.read_ub4()?;
1691 let _seq = reader.read_ub2()?;
1692 }
1693 TNS_MSG_TYPE_SERVER_SIDE_PIGGYBACK => {
1694 let _ = skip_server_side_piggyback(&mut reader)?;
1695 }
1696 TNS_MSG_TYPE_FLUSH_OUT_BINDS | TNS_MSG_TYPE_END_OF_RESPONSE => break,
1697 TNS_MSG_TYPE_TOKEN => {
1698 let _token = reader.read_ub8()?;
1699 }
1700 TNS_MSG_TYPE_IMPLICIT_RESULTSET => {
1701 let num_results = reader.read_ub4()?;
1707 reader
1708 .limits()
1709 .check_length_prefixed_elements(num_results as usize)?;
1710 for _ in 0..num_results {
1711 let num_bytes = reader.read_u8()?;
1712 reader.skip(usize::from(num_bytes))?;
1713 let mut child = QueryResult::default();
1714 parse_describe_info(&mut reader, capabilities, &mut child)?;
1715 let _child_cursor_id = reader.read_ub2()?;
1716 }
1717 }
1718 TNS_MSG_TYPE_ERROR => {
1719 let info = parse_server_error_info(&mut reader, capabilities.ttc_field_version)?;
1720 if info.cursor_id != 0 {
1721 cursor_id = u32::from(info.cursor_id);
1722 }
1723 row_count = info.row_count;
1724 if info.number == TNS_ERR_NO_DATA_FOUND && !result_columns.is_empty() {
1725 more_rows = false;
1726 } else if info.number != 0 && info.number != TNS_ERR_ARRAY_DML_ERRORS {
1727 return Err(ProtocolError::ServerErrorInfo(Box::new(
1728 info.into_details(),
1729 )));
1730 }
1731 }
1732 _ => {
1733 let position = reader.position().saturating_sub(1);
1734 if let Some(message) =
1735 find_embedded_server_error(payload, capabilities.ttc_field_version, position)
1736 {
1737 return Err(ProtocolError::ServerError(message));
1738 }
1739 return Err(ProtocolError::UnknownMessageType {
1740 message_type,
1741 position,
1742 });
1743 }
1744 }
1745 }
1746
1747 if !any_bit_vector {
1751 row_bit_vectors.clear();
1752 }
1753
1754 let batch = BorrowedRowBatch {
1755 buffer: payload.to_vec(),
1756 columns: result_columns,
1757 row_starts,
1758 row_bit_vectors,
1759 fetch_long_status,
1760 lob_decode_mode,
1761 previous_row_seed: any_bit_vector.then(|| {
1764 previous_row
1765 .map(<[Option<QueryValue>]>::to_vec)
1766 .unwrap_or_default()
1767 }),
1768 };
1769
1770 Ok(BorrowedFetchResult {
1771 batch,
1772 more_rows,
1773 cursor_id,
1774 row_count,
1775 })
1776}
1777
1778fn skip_row_data(
1788 reader: &mut TtcReader<'_>,
1789 columns: &[ColumnMetadata],
1790 bit_vector: Option<&[u8]>,
1791 fetch_long_status: bool,
1792 lob_decode_mode: LobDecodeMode,
1793) -> Result<()> {
1794 for (index, metadata) in columns.iter().enumerate() {
1795 if is_duplicate_column(bit_vector, index) {
1796 continue;
1797 }
1798 let consumed_byte_field = metadata.buffer_size != 0
1799 && matches!(
1800 metadata.ora_type_num,
1801 ORA_TYPE_NUM_VARCHAR
1802 | ORA_TYPE_NUM_CHAR
1803 | ORA_TYPE_NUM_LONG
1804 | ORA_TYPE_NUM_RAW
1805 | ORA_TYPE_NUM_LONG_RAW
1806 | ORA_TYPE_NUM_NUMBER
1807 | ORA_TYPE_NUM_BINARY_INTEGER
1808 | ORA_TYPE_NUM_BINARY_DOUBLE
1809 | ORA_TYPE_NUM_BINARY_FLOAT
1810 | ORA_TYPE_NUM_BOOLEAN
1811 | ORA_TYPE_NUM_INTERVAL_DS
1812 | ORA_TYPE_NUM_INTERVAL_YM
1813 | ORA_TYPE_NUM_DATE
1814 | ORA_TYPE_NUM_TIMESTAMP
1815 | ORA_TYPE_NUM_TIMESTAMP_LTZ
1816 | ORA_TYPE_NUM_TIMESTAMP_TZ
1817 );
1818 if consumed_byte_field {
1819 reader.skip_bytes_field()?;
1820 } else {
1821 let _ = parse_column_value_with_lob_mode(reader, metadata, lob_decode_mode)?;
1824 }
1825 if fetch_long_status
1826 && matches!(
1827 metadata.ora_type_num,
1828 ORA_TYPE_NUM_LONG | ORA_TYPE_NUM_LONG_RAW
1829 )
1830 {
1831 let _null_indicator = reader.read_sb4()?;
1832 let _return_code = reader.read_ub4()?;
1833 }
1834 }
1835 Ok(())
1836}
1837
1838pub(crate) fn encode_rowid_component(mut value: u32, size: usize, output: &mut String) {
1839 let mut encoded = vec![b'A'; size];
1840 for index in 0..size {
1841 let alphabet_index = usize::try_from(value & 0x3f).unwrap_or(0);
1842 encoded[size - index - 1] = TNS_BASE64_ALPHABET[alphabet_index];
1843 value >>= 6;
1844 }
1845 output.extend(encoded.into_iter().map(char::from));
1846}
1847
1848pub(crate) fn encode_physical_rowid(
1849 rba: u32,
1850 partition_id: u16,
1851 block_num: u32,
1852 slot_num: u16,
1853) -> String {
1854 let mut output = String::with_capacity(ORA_TYPE_SIZE_ROWID as usize);
1855 encode_rowid_component(rba, 6, &mut output);
1856 encode_rowid_component(u32::from(partition_id), 3, &mut output);
1857 encode_rowid_component(block_num, 6, &mut output);
1858 encode_rowid_component(u32::from(slot_num), 3, &mut output);
1859 output
1860}
1861
1862pub(crate) fn parse_rowid_value(reader: &mut TtcReader<'_>) -> Result<Option<String>> {
1863 let len = reader.read_u8()?;
1864 if len == 0 || len == crate::wire::TNS_NULL_LENGTH_INDICATOR {
1865 return Ok(None);
1866 }
1867 let rba = reader.read_ub4()?;
1868 let partition_id = reader.read_ub2()?;
1869 reader.skip(1)?;
1870 let block_num = reader.read_ub4()?;
1871 let slot_num = reader.read_ub2()?;
1872 Ok(Some(encode_physical_rowid(
1873 rba,
1874 partition_id,
1875 block_num,
1876 slot_num,
1877 )))
1878}
1879
1880pub(crate) fn encode_logical_urowid(bytes: &[u8]) -> String {
1881 let mut input_offset = 1;
1882 let mut input_len = bytes.len().saturating_sub(1);
1883 let mut output = String::with_capacity((bytes.len() / 3) * 4 + 4);
1884 output.push('*');
1885 while input_len > 0 {
1886 let mut pos = bytes[input_offset] >> 2;
1887 output.push(char::from(TNS_BASE64_ALPHABET[usize::from(pos)]));
1888
1889 pos = (bytes[input_offset] & 0x03) << 4;
1890 if input_len == 1 {
1891 output.push(char::from(TNS_BASE64_ALPHABET[usize::from(pos)]));
1892 break;
1893 }
1894 input_offset += 1;
1895 pos |= (bytes[input_offset] & 0xf0) >> 4;
1896 output.push(char::from(TNS_BASE64_ALPHABET[usize::from(pos)]));
1897
1898 pos = (bytes[input_offset] & 0x0f) << 2;
1899 if input_len == 2 {
1900 output.push(char::from(TNS_BASE64_ALPHABET[usize::from(pos)]));
1901 break;
1902 }
1903 input_offset += 1;
1904 pos |= (bytes[input_offset] & 0xc0) >> 6;
1905 output.push(char::from(TNS_BASE64_ALPHABET[usize::from(pos)]));
1906
1907 pos = bytes[input_offset] & 0x3f;
1908 output.push(char::from(TNS_BASE64_ALPHABET[usize::from(pos)]));
1909 input_offset += 1;
1910 input_len -= 3;
1911 }
1912 output
1913}
1914
1915pub(crate) fn parse_urowid_value(reader: &mut TtcReader<'_>) -> Result<Option<String>> {
1916 if reader.read_bytes()?.is_none() {
1917 return Ok(None);
1918 }
1919 let Some(bytes) = reader.read_bytes()? else {
1920 return Ok(None);
1921 };
1922 if bytes.len() < 13 {
1923 return Err(ProtocolError::TtcDecode("encoded UROWID too short"));
1924 }
1925 if bytes[0] == 1 {
1926 let rba = u32::from_be_bytes([bytes[1], bytes[2], bytes[3], bytes[4]]);
1927 let partition_id = u16::from_be_bytes([bytes[5], bytes[6]]);
1928 let block_num = u32::from_be_bytes([bytes[7], bytes[8], bytes[9], bytes[10]]);
1929 let slot_num = u16::from_be_bytes([bytes[11], bytes[12]]);
1930 Ok(Some(encode_physical_rowid(
1931 rba,
1932 partition_id,
1933 block_num,
1934 slot_num,
1935 )))
1936 } else {
1937 Ok(Some(encode_logical_urowid(&bytes)))
1938 }
1939}
1940
1941pub(crate) fn parse_lob_value(
1942 reader: &mut TtcReader<'_>,
1943 metadata: &ColumnMetadata,
1944 lob_decode_mode: LobDecodeMode,
1945) -> Result<Option<QueryValue>> {
1946 let num_bytes = reader.read_ub4()?;
1947 reader.limits().check_response_bytes(num_bytes as usize)?;
1948 if num_bytes == 0 {
1949 return Ok(None);
1950 }
1951 let (size, chunk_size) = if matches!(
1952 (lob_decode_mode, metadata.ora_type_num),
1953 (_, ORA_TYPE_NUM_BFILE) | (LobDecodeMode::PlainLocator, _)
1954 ) {
1955 (0, 0)
1956 } else {
1957 (reader.read_ub8()?, reader.read_ub4()?)
1958 };
1959 let Some(locator) = reader.read_bytes()? else {
1960 return Ok(None);
1961 };
1962 Ok(Some(QueryValue::Lob(Box::new(LobValue {
1963 ora_type_num: metadata.ora_type_num,
1964 csfrm: metadata.csfrm,
1965 locator,
1966 size,
1967 chunk_size,
1968 }))))
1969}
1970
1971#[cfg(test)]
1972mod lob_fetch_shape_tests {
1973 use super::*;
1974
1975 fn clob_column() -> ColumnMetadata {
1976 ColumnMetadata {
1977 name: "BODY".into(),
1978 ora_type_num: ORA_TYPE_NUM_CLOB,
1979 csfrm: CS_FORM_IMPLICIT,
1980 precision: 0,
1981 scale: 0,
1982 buffer_size: 4000,
1983 max_size: 4000,
1984 nulls_allowed: true,
1985 is_json: false,
1986 is_oson: false,
1987 object_schema: None,
1988 object_type_name: None,
1989 is_array: false,
1990 vector_dimensions: None,
1991 vector_format: 0,
1992 vector_flags: 0,
1993 domain_schema: None,
1994 domain_name: None,
1995 annotations: None,
1996 }
1997 }
1998
1999 fn blob_column() -> ColumnMetadata {
2000 ColumnMetadata {
2001 name: "IMAGE".into(),
2002 ora_type_num: ORA_TYPE_NUM_BLOB,
2003 csfrm: 0,
2004 precision: 0,
2005 scale: 0,
2006 buffer_size: 4000,
2007 max_size: 4000,
2008 nulls_allowed: true,
2009 is_json: false,
2010 is_oson: false,
2011 object_schema: None,
2012 object_type_name: None,
2013 is_array: false,
2014 vector_dimensions: None,
2015 vector_format: 0,
2016 vector_flags: 0,
2017 domain_schema: None,
2018 domain_name: None,
2019 annotations: None,
2020 }
2021 }
2022
2023 fn lob_row_payload(locator: &[u8], metadata: Option<(u64, u32)>) -> Vec<u8> {
2024 let mut writer = TtcWriter::new();
2025 writer.write_u8(TNS_MSG_TYPE_ROW_DATA);
2026 writer.write_ub4(u32::try_from(locator.len()).expect("locator length fits ub4"));
2027 if let Some((size, chunk_size)) = metadata {
2028 writer.write_ub8(size);
2029 writer.write_ub4(chunk_size);
2030 }
2031 writer
2032 .write_bytes_with_length(locator)
2033 .expect("synthetic locator length is encodable");
2034 writer.write_u8(TNS_MSG_TYPE_END_OF_RESPONSE);
2035 writer.into_bytes()
2036 }
2037
2038 fn write_define_lob_cell(writer: &mut TtcWriter, locator: &[u8], size: u64, chunk_size: u32) {
2039 writer.write_ub4(u32::try_from(locator.len()).expect("locator length fits ub4"));
2040 writer.write_ub8(size);
2041 writer.write_ub4(chunk_size);
2042 writer
2043 .write_bytes_with_length(locator)
2044 .expect("synthetic locator length is encodable");
2045 }
2046
2047 fn first_lob(result: &QueryResult) -> Option<&LobValue> {
2048 match result.rows.first()?.first()? {
2049 Some(QueryValue::Lob(lob)) => Some(lob.as_ref()),
2050 _ => None,
2051 }
2052 }
2053
2054 #[test]
2055 fn column_metadata_discards_server_charset_id_and_keeps_csfrm_only() {
2056 let caps = ClientCapabilities {
2057 ttc_field_version: 0,
2058 max_string_size: 32_767,
2059 charset_id: 873,
2060 };
2061 let mut writer = TtcWriter::new();
2062 writer.write_u8(ORA_TYPE_NUM_VARCHAR);
2063 writer.write_u8(0); writer.write_u8(0); writer.write_u8(0); writer.write_ub4(4000);
2067 writer.write_ub4(0); writer.write_ub8(0); writer
2070 .write_bytes_with_two_lengths(None)
2071 .expect("empty oid");
2072 writer.write_ub2(0); writer.write_ub2(0); writer.write_u8(CS_FORM_IMPLICIT);
2075 writer.write_ub4(4000);
2076 writer.write_u8(1); writer.write_u8(0); writer
2079 .write_bytes_with_two_lengths(Some(b"TXT"))
2080 .expect("name");
2081 writer
2082 .write_bytes_with_two_lengths(None)
2083 .expect("object schema");
2084 writer
2085 .write_bytes_with_two_lengths(None)
2086 .expect("object type");
2087 writer.write_ub2(1); writer.write_ub4(0); let bytes = writer.into_bytes();
2091 let mut reader = TtcReader::new(&bytes);
2092 let metadata = parse_column_metadata(&mut reader, caps).expect("metadata should parse");
2093 assert_eq!(metadata.name(), "TXT");
2094 assert_eq!(metadata.csfrm(), CS_FORM_IMPLICIT);
2095 assert_eq!(
2096 decode_text_value("ok".as_bytes(), metadata.csfrm()).expect("decode text"),
2097 "ok"
2098 );
2099 }
2100
2101 #[test]
2102 fn plain_fetch_lob_locator_omits_size_and_chunk_fields() {
2103 let locator: Vec<u8> = (0u8..114).collect();
2104 let payload = lob_row_payload(&locator, None);
2105 let columns = [clob_column()];
2106
2107 let result = parse_fetch_response_with_context(
2108 &payload,
2109 ClientCapabilities::default(),
2110 &columns,
2111 None,
2112 )
2113 .expect("plain fetch CLOB locator should decode");
2114
2115 let lob = first_lob(&result).expect("plain fetch should return a LOB value");
2116 assert_eq!(lob.locator, locator);
2117 assert_eq!(lob.size, 0);
2118 assert_eq!(lob.chunk_size, 0);
2119 }
2120
2121 #[test]
2122 fn define_fetch_lob_locator_includes_size_and_chunk_fields() {
2123 let locator: Vec<u8> = (0u8..114).collect();
2124 let payload = lob_row_payload(&locator, Some((23, 8060)));
2125 let columns = [clob_column()];
2126
2127 let result = parse_define_fetch_response_with_context_and_limits(
2128 &payload,
2129 ClientCapabilities::default(),
2130 &columns,
2131 None,
2132 ProtocolLimits::DEFAULT,
2133 )
2134 .expect("define fetch CLOB locator should decode");
2135
2136 let lob = first_lob(&result).expect("define fetch should return a LOB value");
2137 assert_eq!(lob.locator, locator);
2138 assert_eq!(lob.size, 23);
2139 assert_eq!(lob.chunk_size, 8060);
2140 }
2141
2142 #[test]
2143 fn borrowed_define_fetch_lob_page_matches_owned_parse() {
2144 let columns = [clob_column(), blob_column()];
2145 let clob_locator_a: Vec<u8> = (0u8..114).collect();
2146 let blob_locator_a: Vec<u8> = (128u8..242).collect();
2147 let clob_locator_b: Vec<u8> = (32u8..146).collect();
2148 let blob_locator_b: Vec<u8> = (64u8..178).collect();
2149 let mut writer = TtcWriter::new();
2150
2151 writer.write_u8(TNS_MSG_TYPE_ROW_DATA);
2152 write_define_lob_cell(&mut writer, &clob_locator_a, 23, 8060);
2153 write_define_lob_cell(&mut writer, &blob_locator_a, 48, 4096);
2154 writer.write_u8(TNS_MSG_TYPE_ROW_DATA);
2155 write_define_lob_cell(&mut writer, &clob_locator_b, 31, 8060);
2156 write_define_lob_cell(&mut writer, &blob_locator_b, 96, 4096);
2157 writer.write_u8(TNS_MSG_TYPE_END_OF_RESPONSE);
2158 let payload = writer.into_bytes();
2159
2160 let owned = parse_define_fetch_response_with_context_and_limits(
2161 &payload,
2162 ClientCapabilities::default(),
2163 &columns,
2164 None,
2165 ProtocolLimits::DEFAULT,
2166 )
2167 .expect("owned define fetch CLOB/BLOB page should parse");
2168 let borrowed = parse_define_fetch_response_borrowed_with_limits(
2169 &payload,
2170 ClientCapabilities::default(),
2171 &columns,
2172 None,
2173 ProtocolLimits::DEFAULT,
2174 )
2175 .expect("borrowed define fetch CLOB/BLOB page should parse");
2176
2177 let mut borrowed_rows: Vec<Vec<Option<QueryValue>>> = Vec::new();
2178 borrowed
2179 .batch
2180 .for_each_row_ref(|row| {
2181 borrowed_rows.push(
2182 row.iter()
2183 .map(|cell| cell.map(|value| value.to_owned_value()))
2184 .collect(),
2185 );
2186 Ok::<(), ProtocolError>(())
2187 })
2188 .expect("borrowed define fetch CLOB/BLOB page should iterate");
2189
2190 assert_eq!(owned.rows.len(), 2, "owned parse sees both rows");
2191 assert_eq!(
2192 borrowed.batch.row_count(),
2193 2,
2194 "borrowed parse sees both rows"
2195 );
2196 assert_eq!(
2197 borrowed_rows, owned.rows,
2198 "borrowed DefineMetadata LOB parse must match owned parse"
2199 );
2200 }
2201}
2202
2203pub(crate) fn parse_vector_value(reader: &mut TtcReader<'_>) -> Result<Option<QueryValue>> {
2207 let num_bytes = reader.read_ub4()?;
2208 reader.limits().check_response_bytes(num_bytes as usize)?;
2209 if num_bytes == 0 {
2210 return Ok(None);
2211 }
2212 reader.read_ub8()?; reader.read_ub4()?; let Some(data) = reader.read_bytes()? else {
2215 return Ok(None);
2216 };
2217 reader.read_bytes()?; if data.is_empty() {
2219 return Ok(None);
2220 }
2221 let vector = crate::vector::decode_vector_with_limits(&data, reader.limits())?;
2222 Ok(Some(QueryValue::Vector(Box::new(vector))))
2223}
2224
2225pub(crate) fn parse_json_value(reader: &mut TtcReader<'_>) -> Result<Option<QueryValue>> {
2229 let num_bytes = reader.read_ub4()?;
2230 reader.limits().check_response_bytes(num_bytes as usize)?;
2231 if num_bytes == 0 {
2232 return Ok(None);
2233 }
2234 reader.read_ub8()?; reader.read_ub4()?; let Some(data) = reader.read_bytes()? else {
2237 return Ok(None);
2238 };
2239 reader.read_bytes()?; if data.is_empty() {
2241 return Ok(None);
2242 }
2243 let value = crate::oson::decode_oson_with_limits(&data, reader.limits())?;
2244 Ok(Some(QueryValue::Json(Box::new(value))))
2245}
2246
2247pub(crate) fn parse_object_value(
2248 reader: &mut TtcReader<'_>,
2249 metadata: &ColumnMetadata,
2250) -> Result<Option<QueryValue>> {
2251 let _toid = reader.read_bytes_with_length()?;
2252 let _oid = reader.read_bytes_with_length()?;
2253 let _snapshot = reader.read_bytes_with_length()?;
2254 let _version = reader.read_ub2()?;
2255 let num_bytes = reader.read_ub4()?;
2256 reader.limits().check_response_bytes(num_bytes as usize)?;
2257 reader.skip(2)?;
2258 if num_bytes == 0 {
2259 return Ok(None);
2260 }
2261 let Some(packed_data) = reader.read_bytes()? else {
2262 return Ok(None);
2263 };
2264 Ok(Some(QueryValue::Object(Box::new(ObjectValue {
2265 schema: metadata.object_schema.clone(),
2266 type_name: metadata.object_type_name.clone(),
2267 packed_data,
2268 }))))
2269}
2270
2271pub(crate) fn parse_cursor_value(reader: &mut TtcReader<'_>) -> Result<QueryValue> {
2272 reader.skip(1)?;
2273 let mut result = QueryResult::default();
2274 parse_describe_info(reader, ClientCapabilities::default(), &mut result)?;
2275 let cursor_id = u32::from(reader.read_ub2()?);
2276 Ok(QueryValue::Cursor(Box::new(CursorValue {
2277 columns: result.columns,
2278 cursor_id,
2279 })))
2280}
2281
2282pub(crate) struct QueryReturnParameters {
2283 pub row_counts: Option<Vec<u64>>,
2284 pub query_id: Option<u64>,
2287}
2288
2289pub(crate) fn parse_query_return_parameters(
2290 reader: &mut TtcReader<'_>,
2291 arraydmlrowcounts: bool,
2292) -> Result<QueryReturnParameters> {
2293 let num_params = reader.read_ub2()?;
2294 for _ in 0..num_params {
2295 let _value = reader.read_ub4()?;
2296 }
2297 let num_bytes = reader.read_ub2()?;
2298 if num_bytes > 0 {
2299 reader.skip(usize::from(num_bytes))?;
2300 }
2301 let num_pairs = reader.read_ub2()?;
2302 skip_keyword_value_pairs(reader, num_pairs)?;
2303 let num_bytes = usize::from(reader.read_ub2()?);
2306 let mut query_id = None;
2307 if num_bytes > 0 {
2308 let block = reader.read_raw(num_bytes)?;
2309 if num_bytes >= 8 {
2310 let msb = u32::from_be_bytes([
2311 block[num_bytes - 4],
2312 block[num_bytes - 3],
2313 block[num_bytes - 2],
2314 block[num_bytes - 1],
2315 ]);
2316 let lsb = u32::from_be_bytes([
2317 block[num_bytes - 8],
2318 block[num_bytes - 7],
2319 block[num_bytes - 6],
2320 block[num_bytes - 5],
2321 ]);
2322 query_id = Some((u64::from(msb) << 32) | u64::from(lsb));
2323 }
2324 }
2325 if arraydmlrowcounts {
2326 let num_rows = reader.read_ub4()?;
2328 reader.limits().check_batch_rows(num_rows as usize)?;
2329 let mut row_counts: Vec<u64> =
2332 reader.with_capacity_limited(num_rows as usize, 1, ProtocolLimits::check_batch_rows)?;
2333 for _ in 0..num_rows {
2334 row_counts.push(reader.read_ub8()?);
2335 }
2336 return Ok(QueryReturnParameters {
2337 row_counts: Some(row_counts),
2338 query_id,
2339 });
2340 }
2341 Ok(QueryReturnParameters {
2342 row_counts: None,
2343 query_id,
2344 })
2345}
2346
2347#[cfg(test)]
2348mod return_parameter_tests {
2349 use super::*;
2350
2351 #[test]
2352 fn registration_info_block_extracts_query_id_from_lsb_msb_tail() {
2353 let mut writer = TtcWriter::new();
2354 writer.write_ub2(0); writer.write_ub2(0); writer.write_ub2(0); writer.write_ub2(8); writer.write_raw(&[
2359 0x55, 0x66, 0x77, 0x88, 0x11, 0x22, 0x33, 0x44, ]);
2362 let payload = writer.into_bytes();
2363 let mut reader = TtcReader::new(&payload);
2364
2365 let params = parse_query_return_parameters(&mut reader, false).expect("return parameters");
2366
2367 assert_eq!(params.query_id, Some(0x1122_3344_5566_7788));
2368 assert_eq!(params.row_counts, None);
2369 }
2370}
2371
2372#[cfg(test)]
2373mod mutation_decode_tests {
2374 use super::*;
2375 use crate::oson::{encode_oson, OsonValue};
2376 use crate::vector::{encode_vector, Vector, VectorValues};
2377
2378 fn caps(ttc_field_version: u8) -> ClientCapabilities {
2379 ClientCapabilities {
2380 ttc_field_version,
2381 max_string_size: 32_767,
2382 charset_id: 873,
2383 }
2384 }
2385
2386 fn column(name: &str, ora_type_num: u8, csfrm: u8, buffer_size: u32) -> ColumnMetadata {
2387 ColumnMetadata {
2388 name: name.to_string(),
2389 ora_type_num,
2390 csfrm,
2391 buffer_size,
2392 max_size: buffer_size,
2393 nulls_allowed: true,
2394 ..ColumnMetadata::default()
2395 }
2396 }
2397
2398 struct ColumnRecord<'a> {
2399 name: &'a str,
2400 ora_type_num: u8,
2401 csfrm: u8,
2402 buffer_size: u32,
2403 max_size: u32,
2404 uds_flags: u32,
2405 vector_dimensions: Option<u32>,
2406 vector_format: u8,
2407 vector_flags: u8,
2408 object_schema: Option<&'a str>,
2409 object_type_name: Option<&'a str>,
2410 annotations: &'a [(&'a str, &'a str)],
2411 }
2412
2413 impl<'a> ColumnRecord<'a> {
2414 fn scalar(name: &'a str, ora_type_num: u8, csfrm: u8, buffer_size: u32) -> Self {
2415 Self {
2416 name,
2417 ora_type_num,
2418 csfrm,
2419 buffer_size,
2420 max_size: buffer_size,
2421 uds_flags: 0,
2422 vector_dimensions: None,
2423 vector_format: 0,
2424 vector_flags: 0,
2425 object_schema: None,
2426 object_type_name: None,
2427 annotations: &[],
2428 }
2429 }
2430
2431 fn write(&self, writer: &mut TtcWriter, field_version: u8) {
2432 writer.write_u8(self.ora_type_num);
2433 writer.write_u8(0); writer.write_u8(7); writer.write_u8(2); writer.write_ub4(self.buffer_size);
2437 writer.write_ub4(0); writer.write_ub8(0); writer
2440 .write_bytes_with_two_lengths(None)
2441 .expect("column oid");
2442 writer.write_ub2(0); writer.write_ub2(0); writer.write_u8(self.csfrm);
2445 writer.write_ub4(self.max_size);
2446 if field_version >= TNS_CCAP_FIELD_VERSION_12_2 {
2447 writer.write_ub4(0x1122_3344); }
2449 writer.write_u8(1); writer.write_u8(0); writer
2452 .write_bytes_with_two_lengths(Some(self.name.as_bytes()))
2453 .expect("column name");
2454 writer
2455 .write_bytes_with_two_lengths(self.object_schema.map(str::as_bytes))
2456 .expect("object schema");
2457 writer
2458 .write_bytes_with_two_lengths(self.object_type_name.map(str::as_bytes))
2459 .expect("object type");
2460 writer.write_ub2(1); writer.write_ub4(self.uds_flags);
2462 if field_version >= TNS_CCAP_FIELD_VERSION_23_1 {
2463 writer
2464 .write_bytes_with_two_lengths(Some(b"DOMSCHEMA"))
2465 .expect("domain schema");
2466 writer
2467 .write_bytes_with_two_lengths(Some(b"DOMNAME"))
2468 .expect("domain name");
2469 }
2470 if field_version >= TNS_CCAP_FIELD_VERSION_23_1_EXT_3 {
2471 writer.write_ub4(u32::try_from(self.annotations.len()).expect("annotation count"));
2472 if !self.annotations.is_empty() {
2473 writer.write_u8(0); writer.write_ub4(
2475 u32::try_from(self.annotations.len()).expect("annotation count"),
2476 );
2477 writer.write_u8(0); for &(key, value) in self.annotations {
2479 writer
2480 .write_bytes_with_two_lengths(Some(key.as_bytes()))
2481 .expect("annotation key");
2482 writer
2483 .write_bytes_with_two_lengths(Some(value.as_bytes()))
2484 .expect("annotation value");
2485 writer.write_ub4(0); }
2487 writer.write_ub4(0); }
2489 }
2490 if field_version >= TNS_CCAP_FIELD_VERSION_23_4 {
2491 writer.write_ub4(self.vector_dimensions.unwrap_or(0));
2492 writer.write_u8(self.vector_format);
2493 writer.write_u8(self.vector_flags);
2494 }
2495 }
2496 }
2497
2498 fn write_describe_body(
2499 writer: &mut TtcWriter,
2500 field_version: u8,
2501 columns: &[ColumnRecord<'_>],
2502 ) {
2503 writer.write_ub4(4096); writer.write_ub4(u32::try_from(columns.len()).expect("column count"));
2505 if !columns.is_empty() {
2506 writer.write_u8(0); }
2508 for column in columns {
2509 column.write(writer, field_version);
2510 }
2511 writer
2512 .write_bytes_with_two_lengths(None)
2513 .expect("current date");
2514 writer.write_ub4(0); writer.write_ub4(0); writer.write_ub4(0); writer.write_ub4(0); writer.write_bytes_with_two_lengths(None).expect("dcbqcky");
2519 }
2520
2521 fn write_io_vector_body(
2522 writer: &mut TtcWriter,
2523 directions: &[u8],
2524 fast_fetch_bytes: &[u8],
2525 rowid_bytes: &[u8],
2526 ) {
2527 writer.write_u8(0); writer.write_ub2(u16::try_from(directions.len()).expect("direction count"));
2529 writer.write_ub4(0); writer.write_ub4(1); writer.write_ub2(0); writer.write_ub2(u16::try_from(fast_fetch_bytes.len()).expect("fast fetch len"));
2533 writer.write_raw(fast_fetch_bytes);
2534 writer.write_ub2(u16::try_from(rowid_bytes.len()).expect("rowid len"));
2535 writer.write_raw(rowid_bytes);
2536 for &direction in directions {
2537 writer.write_u8(direction);
2538 }
2539 }
2540
2541 struct ErrorInfo<'a> {
2542 number: u32,
2543 message: &'a str,
2544 cursor_id: u16,
2545 row_count: u64,
2546 call_status: u32,
2547 warning_flags: u8,
2548 rowid: Option<(u32, u16, u32, u16)>,
2549 }
2550
2551 fn write_error_info(writer: &mut TtcWriter, info: ErrorInfo<'_>) {
2552 writer.write_ub4(info.call_status);
2553 writer.write_ub2(0); writer.write_ub4(0); writer.write_ub2(0); writer.write_ub2(0); writer.write_ub2(0); writer.write_ub2(info.cursor_id);
2559 writer.write_sb4(0); writer.write_raw(&[0u8; 5]);
2561 writer.write_u8(info.warning_flags);
2562 let (rba, partition_id, block_num, slot_num) = info.rowid.unwrap_or((0, 0, 0, 0));
2563 writer.write_ub4(rba);
2564 writer.write_ub2(partition_id);
2565 writer.write_u8(0);
2566 writer.write_ub4(block_num);
2567 writer.write_ub2(slot_num);
2568 writer.write_ub4(0); writer.write_raw(&[0u8; 2]);
2570 writer.write_ub2(0); writer.write_ub4(0); writer
2573 .write_bytes_with_two_lengths(None)
2574 .expect("diagnostic field");
2575 writer.write_ub2(0); writer.write_ub4(0); writer.write_ub2(0); writer.write_ub4(info.number);
2579 writer.write_ub8(info.row_count);
2580 writer.write_ub4(0); writer.write_ub4(0); if info.number != 0 {
2583 writer
2584 .write_bytes_with_length(info.message.as_bytes())
2585 .expect("server error message");
2586 }
2587 }
2588
2589 fn write_return_parameters(
2590 writer: &mut TtcWriter,
2591 registration_block: &[u8],
2592 row_counts: Option<&[u64]>,
2593 ) {
2594 writer.write_ub2(0); writer.write_ub2(0); writer.write_ub2(0); writer.write_ub2(u16::try_from(registration_block.len()).expect("registration len"));
2598 writer.write_raw(registration_block);
2599 if let Some(row_counts) = row_counts {
2600 writer.write_ub4(u32::try_from(row_counts.len()).expect("row count len"));
2601 for &count in row_counts {
2602 writer.write_ub8(count);
2603 }
2604 }
2605 }
2606
2607 fn write_prefetched_lob_value(writer: &mut TtcWriter, data: &[u8]) {
2608 writer.write_ub4(u32::try_from(data.len()).expect("data len"));
2609 writer.write_ub8(u64::try_from(data.len()).expect("data size"));
2610 writer.write_ub4(8192);
2611 writer
2612 .write_bytes_with_length(data)
2613 .expect("prefetched data");
2614 writer.write_bytes_with_length(b"locator").expect("locator");
2615 }
2616
2617 fn write_physical_urowid_cell(writer: &mut TtcWriter) -> String {
2618 let rba: u32 = 0x0102_0304;
2619 let partition_id: u16 = 0x0506;
2620 let block_num: u32 = 0x0708_090a;
2621 let slot_num: u16 = 0x0b0c;
2622 let mut encoded = Vec::new();
2623 encoded.push(1);
2624 encoded.extend_from_slice(&rba.to_be_bytes());
2625 encoded.extend_from_slice(&partition_id.to_be_bytes());
2626 encoded.extend_from_slice(&block_num.to_be_bytes());
2627 encoded.extend_from_slice(&slot_num.to_be_bytes());
2628 writer.write_bytes_with_length(&[1]).expect("urowid probe");
2629 writer
2630 .write_bytes_with_length(&encoded)
2631 .expect("physical urowid");
2632 encode_physical_rowid(rba, partition_id, block_num, slot_num)
2633 }
2634
2635 #[test]
2636 fn response_wrappers_parse_out_binds_returning_status_token_and_error_state() {
2637 let binds = [
2638 BindValue::InOut {
2639 value: Box::new(BindValue::Text("in".to_string())),
2640 out_buffer_size: 20,
2641 },
2642 BindValue::ReturnOutput {
2643 ora_type_num: ORA_TYPE_NUM_VARCHAR,
2644 csfrm: CS_FORM_IMPLICIT,
2645 buffer_size: 20,
2646 },
2647 ];
2648
2649 let mut out_payload = TtcWriter::new();
2650 out_payload.write_u8(TNS_MSG_TYPE_IO_VECTOR);
2651 write_io_vector_body(&mut out_payload, &[16, TNS_BIND_DIR_INPUT], b"F", b"R");
2652 out_payload.write_u8(TNS_MSG_TYPE_ROW_DATA);
2653 out_payload
2654 .write_bytes_with_length(b"OUT")
2655 .expect("out bind value");
2656 out_payload.write_sb4(0);
2657 out_payload.write_u8(TNS_MSG_TYPE_STATUS);
2658 out_payload.write_ub4(TNS_EOCS_FLAGS_TXN_IN_PROGRESS);
2659 out_payload.write_ub2(0);
2660 out_payload.write_u8(TNS_MSG_TYPE_SERVER_SIDE_PIGGYBACK);
2661 out_payload.write_u8(TNS_SERVER_PIGGYBACK_QUERY_CACHE_INVALIDATION);
2662 out_payload.write_u8(TNS_MSG_TYPE_TOKEN);
2663 out_payload.write_ub8(0x1122_3344_5566_7788);
2664 out_payload.write_u8(TNS_MSG_TYPE_ERROR);
2665 write_error_info(
2666 &mut out_payload,
2667 ErrorInfo {
2668 number: 0,
2669 message: "",
2670 cursor_id: 77,
2671 row_count: 3,
2672 call_status: 0,
2673 warning_flags: 0x20,
2674 rowid: Some((1, 2, 3, 4)),
2675 },
2676 );
2677
2678 let result = parse_query_response_with_binds_options_and_columns(
2679 &out_payload.into_bytes(),
2680 caps(TNS_CCAP_FIELD_VERSION_23_4),
2681 &binds,
2682 ExecuteOptions::default(),
2683 &[],
2684 )
2685 .expect("owned response with OUT bind/status/token/error");
2686 assert_eq!(
2687 result.out_values,
2688 vec![(0, Some(QueryValue::Text("OUT".to_string())))]
2689 );
2690 assert_eq!(result.txn_in_progress, Some(false));
2691 assert_eq!(result.cursor_id, 77);
2692 assert_eq!(result.row_count, 3);
2693 assert!(result.compilation_error_warning);
2694 assert_eq!(result.last_rowid, Some(encode_physical_rowid(1, 2, 3, 4)));
2695 assert_eq!(result.token_num, Some(0x1122_3344_5566_7788));
2696
2697 let mut returning_payload = TtcWriter::new();
2698 returning_payload.write_u8(TNS_MSG_TYPE_ROW_DATA);
2699 returning_payload.write_ub4(2);
2700 returning_payload
2701 .write_bytes_with_length(b"A")
2702 .expect("returning value 1");
2703 returning_payload.write_sb4(0);
2704 returning_payload
2705 .write_bytes_with_length(b"B")
2706 .expect("returning value 2");
2707 returning_payload.write_sb4(0);
2708 returning_payload.write_u8(TNS_MSG_TYPE_END_OF_RESPONSE);
2709
2710 let returning = parse_query_response_with_binds_options_columns_and_limits(
2711 &returning_payload.into_bytes(),
2712 caps(TNS_CCAP_FIELD_VERSION_23_4),
2713 &binds,
2714 ExecuteOptions::default(),
2715 &[],
2716 ProtocolLimits::DEFAULT,
2717 )
2718 .expect("owned response with RETURNING values");
2719 assert_eq!(
2720 returning.return_values,
2721 vec![(
2722 1,
2723 vec![
2724 Some(QueryValue::Text("A".to_string())),
2725 Some(QueryValue::Text("B".to_string()))
2726 ]
2727 )]
2728 );
2729 }
2730
2731 #[test]
2732 fn response_flush_breaks_before_trailing_unknown_message() {
2733 let payload = [TNS_MSG_TYPE_FLUSH_OUT_BINDS, 0xff];
2734 parse_query_response(&payload, caps(TNS_CCAP_FIELD_VERSION_23_4))
2735 .expect("flush should stop response parsing before trailing bytes");
2736 }
2737
2738 #[test]
2739 fn no_data_error_marks_more_rows_false_when_columns_are_known() {
2740 let mut payload = TtcWriter::new();
2741 payload.write_u8(TNS_MSG_TYPE_ERROR);
2742 write_error_info(
2743 &mut payload,
2744 ErrorInfo {
2745 number: TNS_ERR_NO_DATA_FOUND,
2746 message: "ORA-01403: no data found",
2747 cursor_id: 91,
2748 row_count: 11,
2749 call_status: TNS_EOCS_FLAGS_TXN_IN_PROGRESS,
2750 warning_flags: 0,
2751 rowid: None,
2752 },
2753 );
2754 let columns = [column("C", ORA_TYPE_NUM_VARCHAR, CS_FORM_IMPLICIT, 30)];
2755
2756 let result = parse_query_response_with_context(
2757 &payload.into_bytes(),
2758 caps(TNS_CCAP_FIELD_VERSION_23_4),
2759 &columns,
2760 None,
2761 )
2762 .expect("no-data end-of-call should finalize fetch");
2763
2764 assert!(!result.more_rows);
2765 assert_eq!(result.cursor_id, 91);
2766 assert_eq!(result.row_count, 11);
2767 assert_eq!(result.txn_in_progress, Some(true));
2768 }
2769
2770 #[test]
2771 fn io_vector_boundaries_skip_zero_and_one_byte_payloads_and_filter_returning() {
2772 let mut zero = TtcWriter::new();
2773 write_io_vector_body(&mut zero, &[16, 48, TNS_BIND_DIR_INPUT], &[], &[]);
2774 let zero_bytes = zero.into_bytes();
2775 let mut reader = TtcReader::new(&zero_bytes);
2776 assert_eq!(
2777 parse_io_vector(&mut reader, 2).expect("zero skips"),
2778 vec![0, 1]
2779 );
2780 assert_eq!(reader.remaining(), 0);
2781
2782 let mut one = TtcWriter::new();
2783 write_io_vector_body(&mut one, &[TNS_BIND_DIR_INPUT, 16, 48], b"X", b"Y");
2784 let one_bytes = one.into_bytes();
2785 let mut reader = TtcReader::new(&one_bytes);
2786 assert_eq!(
2787 parse_io_vector(&mut reader, 2).expect("one-byte skips"),
2788 vec![1]
2789 );
2790 assert_eq!(reader.remaining(), 0);
2791 }
2792
2793 #[test]
2794 fn describe_and_column_metadata_decode_zero_columns_annotations_flags_and_vector() {
2795 let field_version = TNS_CCAP_FIELD_VERSION_23_4;
2796
2797 let mut zero_body = TtcWriter::new();
2798 write_describe_body(&mut zero_body, field_version, &[]);
2799 let zero_bytes = zero_body.into_bytes();
2800 let mut zero_reader = TtcReader::new(&zero_bytes);
2801 let mut zero_result = QueryResult::default();
2802 parse_describe_info(&mut zero_reader, caps(field_version), &mut zero_result)
2803 .expect("zero-column describe");
2804 assert!(zero_result.columns.is_empty());
2805 assert_eq!(zero_reader.remaining(), 0);
2806
2807 let annotated_vector = ColumnRecord {
2808 name: "VEC",
2809 ora_type_num: ORA_TYPE_NUM_VECTOR,
2810 csfrm: CS_FORM_IMPLICIT,
2811 buffer_size: 16,
2812 max_size: 32,
2813 uds_flags: TNS_UDS_FLAGS_IS_JSON | TNS_UDS_FLAGS_IS_OSON,
2814 vector_dimensions: Some(1536),
2815 vector_format: 2,
2816 vector_flags: 0xa5,
2817 object_schema: None,
2818 object_type_name: None,
2819 annotations: &[("purpose", "mutation")],
2820 };
2821 let mut one_body = TtcWriter::new();
2822 write_describe_body(&mut one_body, field_version, &[annotated_vector]);
2823 let one_bytes = one_body.into_bytes();
2824 let mut one_reader = TtcReader::new(&one_bytes);
2825 let mut one_result = QueryResult::default();
2826 parse_describe_info(&mut one_reader, caps(field_version), &mut one_result)
2827 .expect("one-column describe");
2828 let meta = one_result.columns.first().expect("described column");
2829 assert_eq!(meta.name(), "VEC");
2830 assert_eq!(meta.buffer_size(), 16);
2831 assert_eq!(meta.max_size(), 32);
2832 assert!(meta.is_json());
2833 assert!(meta.is_oson());
2834 assert_eq!(meta.domain_schema(), Some("DOMSCHEMA"));
2835 assert_eq!(meta.domain_name(), Some("DOMNAME"));
2836 assert_eq!(
2837 meta.annotations(),
2838 Some(&[("purpose".to_string(), "mutation".to_string())][..])
2839 );
2840 assert_eq!(meta.vector_dimensions(), Some(1536));
2841 assert_eq!(meta.vector_format(), 2);
2842 assert_eq!(meta.vector_flags(), 0xa5);
2843 assert_eq!(one_reader.remaining(), 0);
2844 }
2845
2846 #[test]
2847 fn row_header_returns_embedded_bit_vector() {
2848 let mut writer = TtcWriter::new();
2849 writer.write_u8(0); writer.write_ub2(1); writer.write_ub4(2); writer.write_ub4(3); writer.write_ub2(4); writer.write_ub4(1); writer.write_u8(0); writer.write_raw(&[0b1111_1101]);
2857 writer
2858 .write_bytes_with_two_lengths(Some(b"rid"))
2859 .expect("rxhrid");
2860 let bytes = writer.into_bytes();
2861 let mut reader = TtcReader::new(&bytes);
2862
2863 assert_eq!(
2864 parse_row_header(&mut reader).expect("row header"),
2865 Some(vec![0b1111_1101])
2866 );
2867 assert_eq!(reader.remaining(), 0);
2868 }
2869
2870 #[test]
2871 fn owned_column_value_decodes_scalar_and_cold_variants() {
2872 let scalar_cases: Vec<(ColumnMetadata, Vec<u8>, Option<QueryValue>)> = vec![
2873 (
2874 column("D", ORA_TYPE_NUM_BINARY_DOUBLE, CS_FORM_IMPLICIT, 8),
2875 encode_binary_double(-2.5).to_vec(),
2876 Some(QueryValue::BinaryDouble("-2.5".to_string())),
2877 ),
2878 (
2879 column("F", ORA_TYPE_NUM_BINARY_FLOAT, CS_FORM_IMPLICIT, 4),
2880 encode_binary_float(3.25).to_vec(),
2881 Some(QueryValue::BinaryDouble("3.25".to_string())),
2882 ),
2883 (
2884 column("B", ORA_TYPE_NUM_BOOLEAN, CS_FORM_IMPLICIT, 1),
2885 vec![0, 1],
2886 Some(QueryValue::Boolean(true)),
2887 ),
2888 (
2889 column("DS", ORA_TYPE_NUM_INTERVAL_DS, CS_FORM_IMPLICIT, 11),
2890 encode_interval_ds(4, 5 * 3600 + 6 * 60 + 7, 890)
2891 .expect("interval ds")
2892 .to_vec(),
2893 Some(QueryValue::IntervalDS {
2894 days: 4,
2895 hours: 5,
2896 minutes: 6,
2897 seconds: 7,
2898 fseconds: 890,
2899 }),
2900 ),
2901 (
2902 column("YM", ORA_TYPE_NUM_INTERVAL_YM, CS_FORM_IMPLICIT, 5),
2903 encode_interval_ym(-3, 2).expect("interval ym").to_vec(),
2904 Some(QueryValue::IntervalYM {
2905 years: -3,
2906 months: 2,
2907 }),
2908 ),
2909 (
2910 column("TS", ORA_TYPE_NUM_TIMESTAMP, CS_FORM_IMPLICIT, 11),
2911 encode_oracle_timestamp(2026, 7, 8, 9, 10, 11, 123_456_789).expect("timestamp"),
2912 Some(QueryValue::DateTime {
2913 year: 2026,
2914 month: 7,
2915 day: 8,
2916 hour: 9,
2917 minute: 10,
2918 second: 11,
2919 nanosecond: 123_456_789,
2920 }),
2921 ),
2922 ];
2923
2924 for (metadata, bytes, expected) in scalar_cases {
2925 let mut writer = TtcWriter::new();
2926 writer
2927 .write_bytes_with_length(&bytes)
2928 .expect("framed scalar");
2929 let payload = writer.into_bytes();
2930 let mut reader = TtcReader::new(&payload);
2931 assert_eq!(
2932 parse_column_value(&mut reader, &metadata).expect("scalar decode"),
2933 expected,
2934 "owned scalar decode for {}",
2935 metadata.name()
2936 );
2937 assert_eq!(reader.remaining(), 0);
2938 }
2939
2940 let mut lob_writer = TtcWriter::new();
2941 lob_writer.write_ub4(3);
2942 lob_writer.write_ub8(3);
2943 lob_writer.write_ub4(8192);
2944 lob_writer
2945 .write_bytes_with_length(b"lob")
2946 .expect("lob locator");
2947 let lob_bytes = lob_writer.into_bytes();
2948 let mut reader = TtcReader::new(&lob_bytes);
2949 assert!(matches!(
2950 parse_column_value(
2951 &mut reader,
2952 &column("CLOB", ORA_TYPE_NUM_CLOB, CS_FORM_IMPLICIT, 4000)
2953 )
2954 .expect("lob decode"),
2955 Some(QueryValue::Lob(_))
2956 ));
2957
2958 let vector = Vector::Dense(VectorValues::Int8(vec![-1, 0, 7]));
2959 let vector_image = encode_vector(&vector);
2960 let mut writer = TtcWriter::new();
2961 write_prefetched_lob_value(&mut writer, &vector_image);
2962 let bytes = writer.into_bytes();
2963 let mut reader = TtcReader::new(&bytes);
2964 assert_eq!(
2965 parse_column_value(
2966 &mut reader,
2967 &column("VEC", ORA_TYPE_NUM_VECTOR, CS_FORM_IMPLICIT, 4000)
2968 )
2969 .expect("vector decode"),
2970 Some(QueryValue::Vector(Box::new(vector)))
2971 );
2972
2973 let json = OsonValue::Object(vec![("k".to_string(), OsonValue::String("v".to_string()))]);
2974 let json_image = encode_oson(&json, false).expect("encode oson");
2975 let mut writer = TtcWriter::new();
2976 write_prefetched_lob_value(&mut writer, &json_image);
2977 let bytes = writer.into_bytes();
2978 let mut reader = TtcReader::new(&bytes);
2979 assert_eq!(
2980 parse_column_value(
2981 &mut reader,
2982 &column("JSON", ORA_TYPE_NUM_JSON, CS_FORM_IMPLICIT, 4000)
2983 )
2984 .expect("json decode"),
2985 Some(QueryValue::Json(Box::new(json)))
2986 );
2987
2988 let object_metadata = ColumnMetadata {
2989 name: "OBJ".to_string(),
2990 ora_type_num: ORA_TYPE_NUM_OBJECT,
2991 buffer_size: 4000,
2992 object_schema: Some("S".to_string()),
2993 object_type_name: Some("T".to_string()),
2994 ..ColumnMetadata::default()
2995 };
2996 let mut writer = TtcWriter::new();
2997 writer
2998 .write_bytes_with_two_lengths(Some(b"toid"))
2999 .expect("toid");
3000 writer
3001 .write_bytes_with_two_lengths(Some(b"oid"))
3002 .expect("oid");
3003 writer
3004 .write_bytes_with_two_lengths(Some(b"snap"))
3005 .expect("snapshot");
3006 writer.write_ub2(1);
3007 writer.write_ub4(4);
3008 writer.write_raw(&[0, 0]);
3009 writer
3010 .write_bytes_with_length(b"PACK")
3011 .expect("object payload");
3012 let bytes = writer.into_bytes();
3013 let mut reader = TtcReader::new(&bytes);
3014 assert!(matches!(
3015 parse_column_value(&mut reader, &object_metadata).expect("object decode"),
3016 Some(QueryValue::Object(object))
3017 if object.schema.as_deref() == Some("S")
3018 && object.type_name.as_deref() == Some("T")
3019 && object.packed_data == b"PACK"
3020 ));
3021
3022 let mut writer = TtcWriter::new();
3023 writer.write_u8(0); write_describe_body(&mut writer, TNS_CCAP_FIELD_VERSION_23_4, &[]);
3025 writer.write_ub2(44);
3026 let bytes = writer.into_bytes();
3027 let mut reader = TtcReader::new(&bytes);
3028 assert!(matches!(
3029 parse_column_value(
3030 &mut reader,
3031 &column("CUR", ORA_TYPE_NUM_CURSOR, CS_FORM_IMPLICIT, 1)
3032 )
3033 .expect("cursor decode"),
3034 Some(QueryValue::Cursor(cursor)) if cursor.cursor_id == 44
3035 ));
3036 }
3037
3038 #[test]
3039 fn urowid_requires_full_physical_payload() {
3040 let mut truncated = TtcWriter::new();
3041 truncated
3042 .write_bytes_with_length(&[1])
3043 .expect("urowid probe");
3044 truncated
3045 .write_bytes_with_length(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
3046 .expect("truncated physical urowid");
3047 let truncated_bytes = truncated.into_bytes();
3048 let mut reader = TtcReader::new(&truncated_bytes);
3049 assert!(parse_urowid_value(&mut reader).is_err());
3050
3051 let mut ok = TtcWriter::new();
3052 let expected = write_physical_urowid_cell(&mut ok);
3053 let ok_bytes = ok.into_bytes();
3054 let mut reader = TtcReader::new(&ok_bytes);
3055 assert_eq!(
3056 parse_urowid_value(&mut reader).expect("physical urowid"),
3057 Some(expected)
3058 );
3059 }
3060
3061 #[test]
3062 fn rowid_parser_distinguishes_null_and_physical_rowid() {
3063 let null_payload = [0u8];
3064 let mut reader = TtcReader::new(&null_payload);
3065 assert_eq!(parse_rowid_value(&mut reader).expect("null ROWID"), None);
3066
3067 let rba: u32 = 0x0102_0304;
3068 let partition_id: u16 = 0x0506;
3069 let block_num: u32 = 0x0708_090a;
3070 let slot_num: u16 = 0x0b0c;
3071 let mut physical = TtcWriter::new();
3072 physical.write_u8(1);
3073 physical.write_ub4(rba);
3074 physical.write_ub2(partition_id);
3075 physical.write_u8(0);
3076 physical.write_ub4(block_num);
3077 physical.write_ub2(slot_num);
3078 let physical_bytes = physical.into_bytes();
3079 let mut reader = TtcReader::new(&physical_bytes);
3080
3081 assert_eq!(
3082 parse_rowid_value(&mut reader).expect("physical ROWID"),
3083 Some(encode_physical_rowid(
3084 rba,
3085 partition_id,
3086 block_num,
3087 slot_num
3088 ))
3089 );
3090 assert_eq!(reader.remaining(), 0);
3091 }
3092
3093 #[test]
3094 fn borrowed_slots_cover_scalar_variants_and_nchar_fallback() {
3095 let columns = vec![
3096 column("TEXT", ORA_TYPE_NUM_VARCHAR, CS_FORM_IMPLICIT, 20),
3097 column("NCHAR", ORA_TYPE_NUM_VARCHAR, CS_FORM_NCHAR, 20),
3098 column("RAW", ORA_TYPE_NUM_RAW, CS_FORM_IMPLICIT, 20),
3099 column("NUM", ORA_TYPE_NUM_NUMBER, CS_FORM_IMPLICIT, 22),
3100 column("BOOL", ORA_TYPE_NUM_BOOLEAN, CS_FORM_IMPLICIT, 1),
3101 column("DS", ORA_TYPE_NUM_INTERVAL_DS, CS_FORM_IMPLICIT, 11),
3102 column("YM", ORA_TYPE_NUM_INTERVAL_YM, CS_FORM_IMPLICIT, 5),
3103 column("TS", ORA_TYPE_NUM_TIMESTAMP, CS_FORM_IMPLICIT, 11),
3104 ];
3105 let mut writer = TtcWriter::new();
3106 writer.write_bytes_with_length(b"hello").expect("text");
3107 writer
3108 .write_bytes_with_length(&[0, b'H', 0, b'i'])
3109 .expect("nchar utf16");
3110 writer.write_bytes_with_length(&[0xde, 0xad]).expect("raw");
3111 let number = encode_number_text("123.45").expect("number");
3112 writer
3113 .write_bytes_with_length(&number)
3114 .expect("number cell");
3115 writer.write_bytes_with_length(&[1]).expect("boolean");
3116 writer
3117 .write_bytes_with_length(
3118 &encode_interval_ds(1, 2 * 3600 + 3 * 60 + 4, 5).expect("interval ds"),
3119 )
3120 .expect("interval ds cell");
3121 writer
3122 .write_bytes_with_length(&encode_interval_ym(6, 7).expect("interval ym"))
3123 .expect("interval ym cell");
3124 writer
3125 .write_bytes_with_length(
3126 &encode_oracle_timestamp(2026, 7, 8, 9, 10, 11, 0).expect("timestamp"),
3127 )
3128 .expect("timestamp cell");
3129 let buffer = writer.into_bytes();
3130 let ptr_range = {
3131 let start = buffer.as_ptr() as usize;
3132 start..start + buffer.len()
3133 };
3134 let batch = BorrowedRowBatch::new(buffer, columns, vec![0]);
3135
3136 batch
3137 .for_each_row_ref(|row| {
3138 assert!(matches!(row[0], Some(QueryValueRef::Text("hello"))));
3139 if let Some(QueryValueRef::Text(text)) = row[0] {
3140 assert!(ptr_range.contains(&(text.as_ptr() as usize)));
3141 }
3142 assert_eq!(
3143 row[1].as_ref().map(QueryValueRef::to_owned_value),
3144 Some(QueryValue::Text("Hi".to_string()))
3145 );
3146 assert_eq!(
3147 row[2].as_ref().and_then(|v| v.as_raw()),
3148 Some(&[0xde, 0xad][..])
3149 );
3150 assert_eq!(
3151 row[3].as_ref().and_then(|v| v.as_number_text()),
3152 Some("123.45")
3153 );
3154 assert_eq!(
3155 row[4].as_ref().map(QueryValueRef::to_owned_value),
3156 Some(QueryValue::Boolean(true))
3157 );
3158 assert!(matches!(
3159 row[5].as_ref().map(QueryValueRef::to_owned_value),
3160 Some(QueryValue::IntervalDS {
3161 days: 1,
3162 hours: 2,
3163 minutes: 3,
3164 seconds: 4,
3165 fseconds: 5
3166 })
3167 ));
3168 assert_eq!(
3169 row[6].as_ref().map(QueryValueRef::to_owned_value),
3170 Some(QueryValue::IntervalYM {
3171 years: 6,
3172 months: 7
3173 })
3174 );
3175 assert!(matches!(
3176 row[7].as_ref().map(QueryValueRef::to_owned_value),
3177 Some(QueryValue::DateTime {
3178 year: 2026,
3179 month: 7,
3180 day: 8,
3181 hour: 9,
3182 minute: 10,
3183 second: 11,
3184 nanosecond: 0
3185 })
3186 ));
3187 Ok::<(), ProtocolError>(())
3188 })
3189 .expect("borrowed scalar slots");
3190 }
3191
3192 #[test]
3193 fn borrowed_response_dispatches_messages_and_surfaces_non_default_state() {
3194 let field_version = TNS_CCAP_FIELD_VERSION_23_4;
3195 let mut payload = TtcWriter::new();
3196 payload.write_u8(0);
3197 payload.write_u8(TNS_MSG_TYPE_DESCRIBE_INFO);
3198 payload
3199 .write_bytes_with_length(b"describe")
3200 .expect("describe name");
3201 write_describe_body(
3202 &mut payload,
3203 field_version,
3204 &[ColumnRecord::scalar(
3205 "N",
3206 ORA_TYPE_NUM_NUMBER,
3207 CS_FORM_IMPLICIT,
3208 22,
3209 )],
3210 );
3211 payload.write_u8(TNS_MSG_TYPE_ROW_HEADER);
3212 payload.write_u8(0);
3213 payload.write_ub2(1);
3214 payload.write_ub4(1);
3215 payload.write_ub4(1);
3216 payload.write_ub2(0);
3217 payload.write_ub4(0);
3218 payload
3219 .write_bytes_with_two_lengths(None)
3220 .expect("row header rxhrid");
3221 payload.write_u8(TNS_MSG_TYPE_ROW_DATA);
3222 let number = encode_number_text("7").expect("number");
3223 payload
3224 .write_bytes_with_length(&number)
3225 .expect("row number");
3226 payload.write_u8(TNS_MSG_TYPE_PARAMETER);
3227 write_return_parameters(&mut payload, &[], None);
3228 payload.write_u8(TNS_MSG_TYPE_STATUS);
3229 payload.write_ub4(TNS_EOCS_FLAGS_TXN_IN_PROGRESS);
3230 payload.write_ub2(0);
3231 payload.write_u8(TNS_MSG_TYPE_SERVER_SIDE_PIGGYBACK);
3232 payload.write_u8(TNS_SERVER_PIGGYBACK_QUERY_CACHE_INVALIDATION);
3233 payload.write_u8(TNS_MSG_TYPE_IMPLICIT_RESULTSET);
3234 payload.write_ub4(0);
3235 payload.write_u8(TNS_MSG_TYPE_TOKEN);
3236 payload.write_ub8(99);
3237 payload.write_u8(TNS_MSG_TYPE_ERROR);
3238 write_error_info(
3239 &mut payload,
3240 ErrorInfo {
3241 number: TNS_ERR_NO_DATA_FOUND,
3242 message: "ORA-01403: no data found",
3243 cursor_id: 123,
3244 row_count: 1,
3245 call_status: 0,
3246 warning_flags: 0,
3247 rowid: None,
3248 },
3249 );
3250
3251 let borrowed =
3252 parse_query_response_borrowed(&payload.into_bytes(), caps(field_version), &[], None)
3253 .expect("borrowed response");
3254 assert!(!borrowed.more_rows);
3255 assert_eq!(borrowed.cursor_id, 123);
3256 assert_eq!(borrowed.row_count, 1);
3257 assert_eq!(borrowed.batch.columns()[0].name(), "N");
3258 assert_eq!(borrowed.batch.row_count(), 1);
3259 let mut rows = Vec::new();
3260 borrowed
3261 .batch
3262 .for_each_row_ref(|row| {
3263 rows.push(
3264 row[0]
3265 .as_ref()
3266 .and_then(|value| value.as_number_text())
3267 .map(str::to_string)
3268 .unwrap_or_else(|| "<missing>".to_string()),
3269 );
3270 Ok::<(), ProtocolError>(())
3271 })
3272 .expect("iterate borrowed response");
3273 assert_eq!(rows, vec!["7".to_string()]);
3274 }
3275
3276 #[test]
3277 fn borrowed_response_uses_bit_vector_duplicates_and_breaks_on_flush() {
3278 let columns = vec![
3279 column("A", ORA_TYPE_NUM_NUMBER, CS_FORM_IMPLICIT, 22),
3280 column("B", ORA_TYPE_NUM_NUMBER, CS_FORM_IMPLICIT, 22),
3281 ];
3282 let previous_row = vec![
3283 Some(QueryValue::number_from_text("10", true)),
3284 Some(QueryValue::number_from_text("20", true)),
3285 ];
3286 let mut payload = TtcWriter::new();
3287 payload.write_u8(TNS_MSG_TYPE_BIT_VECTOR);
3288 payload.write_ub2(2);
3289 payload.write_raw(&[0b1111_1101]); payload.write_u8(TNS_MSG_TYPE_ROW_DATA);
3291 let value = encode_number_text("30").expect("number");
3292 payload
3293 .write_bytes_with_length(&value)
3294 .expect("first column");
3295 payload.write_u8(TNS_MSG_TYPE_FLUSH_OUT_BINDS);
3296 payload.write_u8(0xff);
3297
3298 let borrowed = parse_query_response_borrowed(
3299 &payload.into_bytes(),
3300 caps(TNS_CCAP_FIELD_VERSION_23_4),
3301 &columns,
3302 Some(&previous_row),
3303 )
3304 .expect("borrowed bit-vector response");
3305 assert_eq!(borrowed.batch.row_count(), 1);
3306 borrowed
3307 .batch
3308 .for_each_row_ref(|row| {
3309 assert_eq!(row[0].as_ref().and_then(|v| v.as_number_text()), Some("30"));
3310 assert_eq!(
3311 row[1]
3312 .as_ref()
3313 .map(QueryValueRef::to_owned_value)
3314 .and_then(|value| value.as_i64()),
3315 Some(20)
3316 );
3317 Ok::<(), ProtocolError>(())
3318 })
3319 .expect("duplicate column resolution");
3320 }
3321
3322 #[test]
3323 fn return_parameters_decode_registration_block_and_row_counts() {
3324 let mut writer = TtcWriter::new();
3325 write_return_parameters(
3326 &mut writer,
3327 &[
3328 0xaa, 0xbb, 0xcc, 0xdd, 0x11, 0x22, 0x33, 0x44, ],
3331 Some(&[2, 0, 5]),
3332 );
3333 let payload = writer.into_bytes();
3334 let mut reader = TtcReader::new(&payload);
3335
3336 let params = parse_query_return_parameters(&mut reader, true).expect("return parameters");
3337
3338 assert_eq!(params.query_id, Some(0x1122_3344_aabb_ccdd));
3339 assert_eq!(params.row_counts, Some(vec![2, 0, 5]));
3340 assert_eq!(reader.remaining(), 0);
3341 }
3342}
3343
3344#[cfg(test)]
3345mod batch_error_continuation_tests {
3346 use super::*;
3347
3348 fn write_return_parameters_with_row_counts(w: &mut TtcWriter, row_counts: &[u64]) {
3363 w.write_ub2(0); w.write_ub2(0); w.write_ub2(0); w.write_ub2(0); w.write_ub4(u32::try_from(row_counts.len()).expect("row count fits u32"));
3368 for &count in row_counts {
3369 w.write_ub8(count);
3370 }
3371 }
3372
3373 fn write_batch_error_info(w: &mut TtcWriter, errors: &[(u32, u32, &str)]) {
3378 w.write_ub4(0); w.write_ub2(0); w.write_ub4(0); w.write_ub2(0); w.write_ub2(0); w.write_ub2(0); w.write_ub2(0); w.write_sb4(0); w.write_raw(&[0u8; 5]); w.write_u8(0); w.write_ub4(0);
3391 w.write_ub2(0);
3392 w.write_u8(0);
3393 w.write_ub4(0);
3394 w.write_ub2(0);
3395 w.write_ub4(0); w.write_raw(&[0u8; 2]); w.write_ub2(0); w.write_ub4(0); w.write_bytes_with_two_lengths(None)
3400 .expect("empty rowid-diagnostic field"); let count = errors.len();
3403 w.write_ub2(u16::try_from(count).expect("error count fits u16"));
3405 if count > 0 {
3406 w.write_u8(u8::try_from(count).expect("packed length fits u8")); for &(code, _, _) in errors {
3408 w.write_ub2(u16::try_from(code).expect("ORA code fits u16"));
3409 }
3410 }
3411 w.write_ub4(u32::try_from(count).expect("offset count fits u32"));
3413 if count > 0 {
3414 w.write_u8(u8::try_from(count).expect("packed length fits u8"));
3415 for &(_, offset, _) in errors {
3416 w.write_ub4(offset);
3417 }
3418 }
3419 w.write_ub2(u16::try_from(count).expect("message count fits u16"));
3421 if count > 0 {
3422 w.write_u8(0); for &(_, _, message) in errors {
3424 w.write_ub2(u16::try_from(message.len()).expect("message len fits u16")); w.write_bytes_with_length(message.as_bytes())
3426 .expect("batch error message");
3427 w.write_raw(&[0u8, 0u8]); }
3429 }
3430 w.write_ub4(TNS_ERR_ARRAY_DML_ERRORS);
3432 w.write_ub8(0); w.write_ub4(0); w.write_ub4(0); w.write_bytes_with_length(b"ORA-24381: error(s) in array DML operation")
3439 .expect("summary message");
3440 }
3441
3442 #[test]
3443 fn execute_response_decodes_batch_error_continuation_and_row_counts() {
3444 let row_counts = [1u64, 0, 1, 0, 1];
3448 let errors: [(u32, u32, &str); 2] = [
3449 (1, 1, "ORA-00001: unique constraint (X.PK) violated"),
3450 (
3451 1400,
3452 3,
3453 "ORA-01400: cannot insert NULL into (\"X\".\"T\".\"C\")",
3454 ),
3455 ];
3456
3457 let mut writer = TtcWriter::new();
3458 writer.write_u8(TNS_MSG_TYPE_PARAMETER);
3459 write_return_parameters_with_row_counts(&mut writer, &row_counts);
3460 writer.write_u8(TNS_MSG_TYPE_ERROR);
3461 write_batch_error_info(&mut writer, &errors);
3462 let payload = writer.into_bytes();
3463
3464 let options = ExecuteOptions::default().with_arraydmlrowcounts(true);
3465 let result = parse_query_response_with_binds_and_options(
3466 &payload,
3467 ClientCapabilities::default(),
3468 &[],
3469 options,
3470 )
3471 .expect("batch-error execute response decodes");
3472
3473 assert_eq!(
3476 result.array_dml_row_counts.as_deref(),
3477 Some(row_counts.as_slice()),
3478 "per-iteration counts survive: committed rows report 1, failed rows 0"
3479 );
3480
3481 assert_eq!(
3484 result.batch_errors.len(),
3485 2,
3486 "both failing rows are reported, proving continuation past row 1"
3487 );
3488 assert_eq!(result.batch_errors[0].code(), 1);
3489 assert_eq!(result.batch_errors[0].offset(), 1);
3490 assert_eq!(
3491 result.batch_errors[0].message(),
3492 "ORA-00001: unique constraint (X.PK) violated"
3493 );
3494 assert_eq!(result.batch_errors[1].code(), 1400);
3495 assert_eq!(result.batch_errors[1].offset(), 3);
3496 assert_eq!(
3497 result.batch_errors[1].message(),
3498 "ORA-01400: cannot insert NULL into (\"X\".\"T\".\"C\")"
3499 );
3500
3501 let committed: u64 = result
3503 .array_dml_row_counts
3504 .as_deref()
3505 .expect("row counts present")
3506 .iter()
3507 .sum();
3508 assert_eq!(committed, 3, "the 3 non-failing rows committed");
3509 }
3510}
3511
3512#[cfg(test)]
3513mod borrowed_fetch_tests {
3514 use super::*;
3515 use crate::thin::codecs::encode_number_text;
3516
3517 #[test]
3523 fn validate_utf8_matches_core_accept_reject() {
3524 let cases: &[&[u8]] = &[
3525 b"",
3526 b"a",
3527 b"hello world",
3528 "VARCHAR2 cell".as_bytes(),
3529 "中文 mixed \u{1f600}".as_bytes(), "naïve café".as_bytes(),
3531 &[0x80], &[0xC0, 0x80], &[0xED, 0xA0, 0x80], &[0xF4, 0x90, 0x80, 0x80], &[0xFF], &[0xE2, 0x82], ];
3538 for &bytes in cases {
3539 let core = core::str::from_utf8(bytes);
3540 let ours = validate_utf8(bytes);
3541 assert_eq!(
3542 core.is_ok(),
3543 ours.is_ok(),
3544 "accept/reject diverged for {bytes:02x?}"
3545 );
3546 if let (Ok(a), Ok(b)) = (core, ours) {
3547 assert_eq!(a, b, "accepted text diverged for {bytes:02x?}");
3548 }
3549 }
3550 }
3551
3552 fn col(name: &str, ora_type_num: u8, csfrm: u8, buffer_size: u32) -> ColumnMetadata {
3554 ColumnMetadata {
3555 name: name.to_string(),
3556 ora_type_num,
3557 csfrm,
3558 buffer_size,
3559 ..ColumnMetadata::default()
3560 }
3561 }
3562
3563 fn encode_mixed_row(writer: &mut TtcWriter, text: &str, number: &str, raw: &[u8]) {
3568 writer.write_bytes_with_length(text.as_bytes()).unwrap();
3569 let num = encode_number_text(number).unwrap();
3570 writer.write_bytes_with_length(&num).unwrap();
3571 writer.write_bytes_with_length(raw).unwrap();
3572 writer.write_u8(0); }
3574
3575 #[test]
3580 fn borrowed_batch_matches_owned_path_for_mixed_row() {
3581 let columns = vec![
3582 col("T", ORA_TYPE_NUM_VARCHAR, CS_FORM_IMPLICIT, 4000),
3583 col("N", ORA_TYPE_NUM_NUMBER, CS_FORM_IMPLICIT, 22),
3584 col("R", ORA_TYPE_NUM_RAW, CS_FORM_IMPLICIT, 2000),
3585 col("Z", ORA_TYPE_NUM_VARCHAR, CS_FORM_IMPLICIT, 4000),
3586 ];
3587
3588 let mut writer = TtcWriter::new();
3589 encode_mixed_row(
3590 &mut writer,
3591 "héllo world",
3592 "-12.5",
3593 &[0xDE, 0xAD, 0xBE, 0xEF],
3594 );
3595 encode_mixed_row(&mut writer, "second", "42", &[0x01]);
3596 let buffer = writer.into_bytes();
3597 let row_starts = vec![0, {
3598 let mut reader = TtcReader::new(&buffer);
3600 for c in &columns {
3601 let _ = parse_column_value(&mut reader, c).unwrap();
3602 }
3603 reader.position()
3604 }];
3605
3606 let owned_rows: Vec<Vec<Option<QueryValue>>> = row_starts
3608 .iter()
3609 .map(|&start| {
3610 let mut reader = TtcReader::new(&buffer[start..]);
3611 columns
3612 .iter()
3613 .map(|c| parse_column_value(&mut reader, c).unwrap())
3614 .collect()
3615 })
3616 .collect();
3617
3618 let batch = BorrowedRowBatch::new(buffer.clone(), columns.clone(), row_starts);
3621 let buf_ptr_range = batch.buffer_ptr_range();
3622
3623 let mut seen_rows = 0usize;
3624 let mut borrowed_owned: Vec<Vec<Option<QueryValue>>> = Vec::new();
3625 batch
3626 .for_each_row_ref(|row| {
3627 seen_rows += 1;
3628 if let Some(QueryValueRef::Text(t)) = row[0] {
3630 let p = t.as_ptr() as usize;
3631 assert!(
3632 buf_ptr_range.contains(&p),
3633 "Text cell must borrow the batch buffer (zero-copy)"
3634 );
3635 }
3636 if let Some(QueryValueRef::Raw(r)) = row[2] {
3638 let p = r.as_ptr() as usize;
3639 assert!(
3640 buf_ptr_range.contains(&p),
3641 "Raw cell must borrow the batch buffer (zero-copy)"
3642 );
3643 }
3644 borrowed_owned.push(
3645 row.iter()
3646 .map(|cell| cell.map(|v| v.to_owned_value()))
3647 .collect(),
3648 );
3649 Ok::<(), ProtocolError>(())
3650 })
3651 .unwrap();
3652
3653 assert_eq!(seen_rows, 2, "batch yields both rows");
3654 assert_eq!(
3655 borrowed_owned, owned_rows,
3656 "borrowed cells to_owned() must equal the owned-path values"
3657 );
3658 }
3659
3660 #[test]
3661 fn borrowed_number_to_owned_matches_owned_for_trailing_zero_number() {
3662 let column = col("N", ORA_TYPE_NUM_NUMBER, CS_FORM_IMPLICIT, 22);
3663 let number = encode_number_text("1000").expect("encode trailing-zero number");
3664 let mut writer = TtcWriter::new();
3665 writer
3666 .write_bytes_with_length(&number)
3667 .expect("write framed number");
3668 let buffer = writer.into_bytes();
3669
3670 let mut owned_reader = TtcReader::new(&buffer);
3671 let owned = parse_column_value(&mut owned_reader, &column)
3672 .expect("owned decode")
3673 .expect("owned number should be non-null");
3674
3675 let batch = BorrowedRowBatch::new(buffer, vec![column], vec![0]);
3676 let mut borrowed_owned = Vec::new();
3677 batch
3678 .for_each_row_ref(|row| {
3679 borrowed_owned.push(
3680 row[0]
3681 .expect("borrowed number should be non-null")
3682 .to_owned_value(),
3683 );
3684 Ok::<(), ProtocolError>(())
3685 })
3686 .expect("borrowed decode");
3687 let borrowed = borrowed_owned
3688 .pop()
3689 .expect("borrowed decode should yield one row");
3690
3691 assert_eq!(
3692 borrowed.as_number_text().as_deref(),
3693 owned.as_number_text().as_deref(),
3694 "borrowed and owned paths must expose identical canonical NUMBER text"
3695 );
3696 assert_eq!(
3697 borrowed, owned,
3698 "borrowed to_owned_value should materialize the same trailing-zero NUMBER"
3699 );
3700 }
3701
3702 #[test]
3703 fn describe_size_zero_urowid_decodes_and_preserves_following_column_alignment() {
3704 let columns = vec![
3705 col("RID", ORA_TYPE_NUM_UROWID, CS_FORM_IMPLICIT, 0),
3706 col("NEXT", ORA_TYPE_NUM_VARCHAR, CS_FORM_IMPLICIT, 4000),
3707 ];
3708 let rba: u32 = 0x0102_0304;
3709 let partition_id: u16 = 0x0506;
3710 let block_num: u32 = 0x0708_090a;
3711 let slot_num: u16 = 0x0b0c;
3712 let mut encoded_urowid = Vec::new();
3713 encoded_urowid.push(1);
3714 encoded_urowid.extend_from_slice(&rba.to_be_bytes());
3715 encoded_urowid.extend_from_slice(&partition_id.to_be_bytes());
3716 encoded_urowid.extend_from_slice(&block_num.to_be_bytes());
3717 encoded_urowid.extend_from_slice(&slot_num.to_be_bytes());
3718 let expected_rowid = encode_physical_rowid(rba, partition_id, block_num, slot_num);
3719
3720 let mut writer = TtcWriter::new();
3721 writer
3722 .write_bytes_with_length(&[1])
3723 .expect("write UROWID null probe field");
3724 writer
3725 .write_bytes_with_length(&encoded_urowid)
3726 .expect("write encoded UROWID");
3727 writer
3728 .write_bytes_with_length(b"after")
3729 .expect("write following text column");
3730 let buffer = writer.into_bytes();
3731
3732 let mut owned_reader = TtcReader::new(&buffer);
3733 let owned = columns
3734 .iter()
3735 .map(|column| parse_column_value(&mut owned_reader, column).expect("owned decode"))
3736 .collect::<Vec<_>>();
3737 assert_eq!(
3738 owned[0].as_ref().and_then(QueryValue::as_rowid),
3739 Some(expected_rowid.as_str()),
3740 "owned decode must not NULL a describe-size-0 UROWID"
3741 );
3742 assert_eq!(
3743 owned[1].as_ref().and_then(QueryValue::as_text),
3744 Some("after"),
3745 "owned decode must consume UROWID bytes before the following column"
3746 );
3747
3748 let batch = BorrowedRowBatch::new(buffer, columns, vec![0]);
3749 let mut borrowed_rows = Vec::new();
3750 batch
3751 .for_each_row_ref(|row| {
3752 borrowed_rows.push(
3753 row.iter()
3754 .map(|cell| cell.map(|value| value.to_owned_value()))
3755 .collect::<Vec<_>>(),
3756 );
3757 Ok::<(), ProtocolError>(())
3758 })
3759 .expect("borrowed decode");
3760 assert_eq!(borrowed_rows, vec![owned]);
3761 }
3762
3763 #[test]
3771 fn borrowed_response_parse_matches_owned_fetch_path() {
3772 use hex::FromHex;
3773 let payload = Vec::from_hex("06020101000205dc0001010101000702c1041d")
3774 .expect("fixture response should be valid hex");
3775 let columns = vec![
3776 col("INTCOL", ORA_TYPE_NUM_NUMBER, CS_FORM_IMPLICIT, 22),
3777 col("NUMBERCOL", ORA_TYPE_NUM_NUMBER, CS_FORM_IMPLICIT, 22),
3778 ];
3779 let previous_row = vec![
3780 Some(QueryValue::number_from_text("2", true)),
3781 Some(QueryValue::number_from_text("0.5", false)),
3782 ];
3783
3784 let owned = parse_query_response_with_context(
3786 &payload,
3787 ClientCapabilities::default(),
3788 &columns,
3789 Some(&previous_row),
3790 )
3791 .expect("owned fetch decode");
3792
3793 let borrowed = parse_query_response_borrowed(
3795 &payload,
3796 ClientCapabilities::default(),
3797 &columns,
3798 Some(&previous_row),
3799 )
3800 .expect("borrowed fetch decode");
3801
3802 assert_eq!(borrowed.more_rows, owned.more_rows);
3803 assert_eq!(borrowed.cursor_id, owned.cursor_id);
3804 assert_eq!(borrowed.batch.row_count(), owned.rows.len());
3805
3806 let mut borrowed_owned: Vec<Vec<Option<QueryValue>>> = Vec::new();
3807 borrowed
3808 .batch
3809 .for_each_row_ref(|row| {
3810 borrowed_owned.push(
3811 row.iter()
3812 .map(|cell| cell.map(|v| v.to_owned_value()))
3813 .collect(),
3814 );
3815 Ok::<(), ProtocolError>(())
3816 })
3817 .expect("iterate borrowed rows");
3818
3819 assert_eq!(
3820 borrowed_owned, owned.rows,
3821 "borrowed batch must reproduce the owned fetch rows (incl. duplicate columns)"
3822 );
3823 }
3824}
3825
3826#[cfg(test)]
3827mod out_bind_boolean_regression_tests {
3828 use super::*;
3829
3830 fn boolean_column(is_array: bool) -> ColumnMetadata {
3831 ColumnMetadata {
3832 name: "B".to_string(),
3833 ora_type_num: ORA_TYPE_NUM_BOOLEAN,
3834 is_array,
3835 ..ColumnMetadata::default()
3836 }
3837 }
3838
3839 fn boolean_value_with_negative_actual_bytes() -> Vec<u8> {
3840 let mut writer = TtcWriter::new();
3841 writer
3842 .write_bytes_with_length(&[1])
3843 .expect("write present boolean value");
3844 writer.write_sb4(-1);
3845 writer.into_bytes()
3846 }
3847
3848 #[test]
3849 fn scalar_boolean_out_bind_negative_actual_bytes_decodes_null() {
3850 let bind_columns = [boolean_column(false)];
3851 let out_bind_indexes = [0usize];
3852 let payload = boolean_value_with_negative_actual_bytes();
3853 let mut reader = TtcReader::new(&payload);
3854 let mut result = QueryResult::default();
3855
3856 parse_out_bind_row_data(&mut reader, &mut result, &bind_columns, &out_bind_indexes)
3857 .expect("parse scalar BOOLEAN OUT bind");
3858
3859 assert_eq!(result.out_values, vec![(0, None)]);
3860 }
3861
3862 #[test]
3863 fn array_boolean_out_bind_negative_actual_bytes_decodes_null_element() {
3864 let bind_columns = [boolean_column(true)];
3865 let out_bind_indexes = [0usize];
3866 let mut writer = TtcWriter::new();
3867 writer.write_ub4(1);
3868 writer
3869 .write_bytes_with_length(&[1])
3870 .expect("write present boolean array element");
3871 writer.write_sb4(-1);
3872 let payload = writer.into_bytes();
3873 let mut reader = TtcReader::new(&payload);
3874 let mut result = QueryResult::default();
3875
3876 parse_out_bind_row_data(&mut reader, &mut result, &bind_columns, &out_bind_indexes)
3877 .expect("parse array BOOLEAN OUT bind");
3878
3879 assert_eq!(
3880 result.out_values,
3881 vec![(0, Some(QueryValue::Array(vec![None])))]
3882 );
3883 }
3884
3885 #[test]
3886 fn returning_boolean_negative_actual_bytes_decodes_null() {
3887 let bind_columns = [boolean_column(false)];
3888 let output_bind_indexes = [0usize];
3889 let mut writer = TtcWriter::new();
3890 writer.write_ub4(1);
3891 writer
3892 .write_bytes_with_length(&[1])
3893 .expect("write present returning boolean value");
3894 writer.write_sb4(-1);
3895 let payload = writer.into_bytes();
3896 let mut reader = TtcReader::new(&payload);
3897 let mut result = QueryResult::default();
3898
3899 parse_returning_row_data(
3900 &mut reader,
3901 &mut result,
3902 &bind_columns,
3903 &output_bind_indexes,
3904 )
3905 .expect("parse BOOLEAN RETURNING value");
3906
3907 assert_eq!(result.return_values, vec![(0, vec![None])]);
3908 }
3909}
3910
3911#[cfg(test)]
3912mod in_out_io_vector_tests {
3913 use super::*;
3914
3915 const TNS_BIND_DIR_OUTPUT: u8 = 16;
3921 const TNS_BIND_DIR_INPUT_OUTPUT: u8 = 48;
3922
3923 fn io_vector_payload(directions: &[u8]) -> Vec<u8> {
3927 io_vector_payload_with_skips(directions, &[], &[])
3928 }
3929
3930 fn io_vector_payload_with_skips(
3931 directions: &[u8],
3932 fast_fetch_bytes: &[u8],
3933 rowid_bytes: &[u8],
3934 ) -> Vec<u8> {
3935 let mut writer = TtcWriter::new();
3936 writer.write_u8(0);
3937 writer.write_ub2(u16::try_from(directions.len()).unwrap());
3938 writer.write_ub4(0);
3939 writer.write_ub4(1);
3940 writer.write_ub2(0);
3941 writer.write_ub2(u16::try_from(fast_fetch_bytes.len()).unwrap());
3942 writer.write_raw(fast_fetch_bytes);
3943 writer.write_ub2(u16::try_from(rowid_bytes.len()).unwrap());
3944 writer.write_raw(rowid_bytes);
3945 for &direction in directions {
3946 writer.write_u8(direction);
3947 }
3948 writer.into_bytes()
3949 }
3950
3951 #[test]
3952 fn in_out_and_out_directions_read_back_pure_input_does_not() {
3953 let payload = io_vector_payload(&[
3955 TNS_BIND_DIR_INPUT,
3956 TNS_BIND_DIR_INPUT_OUTPUT,
3957 TNS_BIND_DIR_OUTPUT,
3958 ]);
3959 let mut reader = TtcReader::new(&payload);
3960 let out_indexes = parse_io_vector(&mut reader, 3).expect("parse io vector");
3961 assert_eq!(
3962 out_indexes,
3963 vec![1, 2],
3964 "IN OUT (48) and OUT (16) are read back; pure IN (32) is not"
3965 );
3966 }
3967
3968 #[test]
3969 fn io_vector_skips_optional_payloads_and_ignores_unbound_slots() {
3970 let payload = io_vector_payload_with_skips(
3971 &[
3972 TNS_BIND_DIR_OUTPUT,
3973 TNS_BIND_DIR_INPUT,
3974 TNS_BIND_DIR_INPUT_OUTPUT,
3975 ],
3976 b"ff",
3977 b"rid",
3978 );
3979 let mut reader = TtcReader::new(&payload);
3980 let out_indexes = parse_io_vector(&mut reader, 2).expect("parse io vector with skips");
3981
3982 assert_eq!(
3983 out_indexes,
3984 vec![0],
3985 "optional fast-fetch/rowid payloads are skipped, and directions beyond bind_count are ignored"
3986 );
3987 assert_eq!(reader.remaining(), 0);
3988 }
3989
3990 #[test]
3995 fn in_out_varchar_reads_back_value_larger_than_input() {
3996 let inout = BindValue::InOut {
3997 value: Box::new(BindValue::Text("ab".to_string())),
3998 out_buffer_size: 200,
3999 };
4000 let bind_columns = [bind_column_metadata(&inout)];
4001 let out_bind_indexes = [0usize];
4002
4003 let mut writer = TtcWriter::new();
4006 writer
4007 .write_bytes_with_length(b"ABCD")
4008 .expect("write returned varchar");
4009 writer.write_sb4(0);
4010 let payload = writer.into_bytes();
4011
4012 let mut reader = TtcReader::new(&payload);
4013 let mut result = QueryResult::default();
4014 parse_out_bind_row_data(&mut reader, &mut result, &bind_columns, &out_bind_indexes)
4015 .expect("parse IN OUT VARCHAR read-back");
4016
4017 assert_eq!(
4018 result.out_values,
4019 vec![(0, Some(QueryValue::Text("ABCD".to_string())))],
4020 "the routine-modified IN OUT value is read back against its bind metadata"
4021 );
4022 }
4023}
4024
4025#[cfg(test)]
4026mod fuzz_regression_tests {
4027 use super::*;
4028
4029 #[test]
4035 fn fuzz_regression_implicit_resultset_oom() {
4036 let payload = [27u8, 4, 37, 0, 0, 0];
4038 let err = parse_query_response(&payload, ClientCapabilities::default())
4039 .expect_err("oversized implicit-resultset count must fail closed");
4040 assert!(
4041 matches!(
4042 err,
4043 ProtocolError::TtcDecode(_) | ProtocolError::ResourceLimit { .. }
4044 ),
4045 "expected fail-closed protocol error, got {err:?}"
4046 );
4047 }
4048
4049 #[test]
4056 fn describe_info_oversized_column_count_fails_closed_not_oom() {
4057 let payload = [16u8, 0, 0, 4, 0x25, 0x00, 0x00, 0x00];
4061 let err = parse_query_response(&payload, ClientCapabilities::default())
4062 .expect_err("oversized column count must fail closed");
4063 assert!(
4064 matches!(
4065 err,
4066 ProtocolError::TtcDecode(_) | ProtocolError::ResourceLimit { .. }
4067 ),
4068 "expected fail-closed protocol error, got {err:?}"
4069 );
4070 }
4071
4072 #[test]
4073 fn describe_info_respects_protocol_column_limit() {
4074 let payload = [TNS_MSG_TYPE_DESCRIBE_INFO, 0, 0, 1, 2];
4078 let limits = ProtocolLimits {
4079 max_columns: 1,
4080 ..ProtocolLimits::DEFAULT
4081 };
4082 let err = parse_query_response_with_limits(&payload, ClientCapabilities::default(), limits)
4083 .expect_err("column count above policy must fail");
4084 assert!(
4085 matches!(
4086 err,
4087 ProtocolError::ResourceLimit {
4088 limit: "columns",
4089 observed: 2,
4090 maximum: 1,
4091 }
4092 ),
4093 "expected column ResourceLimit, got {err:?}"
4094 );
4095 }
4096
4097 #[test]
4102 fn out_bind_array_oversized_element_count_fails_closed_not_oom() {
4103 let metadata = ColumnMetadata {
4104 name: "ARR".to_string(),
4105 ora_type_num: ORA_TYPE_NUM_NUMBER,
4106 is_array: true,
4107 ..ColumnMetadata::default()
4108 };
4109 let bind_columns = [metadata];
4110 let out_bind_indexes = [0usize];
4111 let payload = [4u8, 0x25, 0x00, 0x00, 0x00];
4113 let mut reader = TtcReader::new(&payload);
4114 let mut result = QueryResult::default();
4115 let err =
4116 parse_out_bind_row_data(&mut reader, &mut result, &bind_columns, &out_bind_indexes)
4117 .expect_err("oversized array OUT bind count must fail closed");
4118 assert!(
4119 matches!(
4120 err,
4121 ProtocolError::TtcDecode(_) | ProtocolError::ResourceLimit { .. }
4122 ),
4123 "expected fail-closed protocol error, got {err:?}"
4124 );
4125 }
4126
4127 fn describe_caps(fv: u8) -> ClientCapabilities {
4142 ClientCapabilities {
4143 ttc_field_version: fv,
4144 max_string_size: 32_767,
4145 charset_id: 873,
4146 }
4147 }
4148
4149 fn describe_column_bytes(fv: u8) -> Vec<u8> {
4152 let mut w = TtcWriter::new();
4153 w.write_u8(ORA_TYPE_NUM_VARCHAR);
4154 w.write_u8(0); w.write_u8(0); w.write_u8(0); w.write_ub4(4000); w.write_ub4(0); w.write_ub8(0); w.write_bytes_with_two_lengths(None).expect("oid");
4161 w.write_ub2(0); w.write_ub2(0); w.write_u8(CS_FORM_IMPLICIT);
4164 w.write_ub4(4000); if fv >= TNS_CCAP_FIELD_VERSION_12_2 {
4166 w.write_ub4(0x1122_3344); }
4168 w.write_u8(1); w.write_u8(0); w.write_bytes_with_two_lengths(Some(b"TXT")).expect("name");
4171 w.write_bytes_with_two_lengths(None).expect("object schema");
4172 w.write_bytes_with_two_lengths(None).expect("object type");
4173 w.write_ub2(1); w.write_ub4(0); if fv >= TNS_CCAP_FIELD_VERSION_23_1 {
4176 w.write_bytes_with_two_lengths(None).expect("domain schema");
4177 w.write_bytes_with_two_lengths(None).expect("domain name");
4178 }
4179 if fv >= TNS_CCAP_FIELD_VERSION_23_1_EXT_3 {
4180 w.write_ub4(0); }
4182 if fv >= TNS_CCAP_FIELD_VERSION_23_4 {
4183 w.write_ub4(2); w.write_u8(0); w.write_u8(0); }
4187 w.into_bytes()
4188 }
4189
4190 fn parse_outcome(bytes: &[u8], fv: u8) -> Option<(String, usize)> {
4195 let mut reader = TtcReader::new(bytes);
4196 parse_column_metadata(&mut reader, describe_caps(fv))
4197 .ok()
4198 .map(|meta| (meta.name().to_string(), reader.remaining()))
4199 }
4200
4201 #[test]
4202 fn describe_column_metadata_gates_fields_on_field_version() {
4203 let boundaries = [
4206 (TNS_CCAP_FIELD_VERSION_12_2 - 1, TNS_CCAP_FIELD_VERSION_12_2), (TNS_CCAP_FIELD_VERSION_23_1 - 1, TNS_CCAP_FIELD_VERSION_23_1), (
4209 TNS_CCAP_FIELD_VERSION_23_1_EXT_3 - 1,
4210 TNS_CCAP_FIELD_VERSION_23_1_EXT_3,
4211 ), (TNS_CCAP_FIELD_VERSION_23_4 - 1, TNS_CCAP_FIELD_VERSION_23_4), ];
4214 for (lo, hi) in boundaries {
4215 let lo_bytes = describe_column_bytes(lo);
4216 let hi_bytes = describe_column_bytes(hi);
4217 assert!(
4218 hi_bytes.len() > lo_bytes.len(),
4219 "fv {hi}: the gated field must add bytes vs fv {lo}"
4220 );
4221
4222 let matched = Some(("TXT".to_string(), 0));
4224 assert_eq!(parse_outcome(&lo_bytes, lo), matched, "fv {lo} matched");
4225 assert_eq!(parse_outcome(&hi_bytes, hi), matched, "fv {hi} matched");
4226
4227 assert_ne!(
4231 parse_outcome(&hi_bytes, lo),
4232 matched,
4233 "fv {hi} bytes read as fv {lo} must not consume cleanly"
4234 );
4235 assert_ne!(
4236 parse_outcome(&lo_bytes, hi),
4237 matched,
4238 "fv {lo} bytes read as fv {hi} must not consume cleanly"
4239 );
4240 }
4241 }
4242}