1#![forbid(unsafe_code)]
2
3pub(crate) use std::collections::BTreeMap;
9
10pub(crate) use crate::sql::statement_is_plsql;
11pub(crate) use crate::wire::{BorrowedBytes, BoundedReader, TtcReader, TtcWriter};
12pub(crate) use crate::{ProtocolError, Result, TNS_VERSION_DESIRED, TNS_VERSION_MIN};
13pub(crate) use hex::FromHex;
14
15pub mod aq;
16mod auth;
17mod bind;
18mod codecs;
19mod connect;
20mod constants;
21mod dbobject;
22mod errors;
23mod execute;
24mod fetch;
25mod lob;
26mod number;
27mod sessionless;
28mod subscr;
29mod types;
30
31#[cfg(test)]
36mod proptests;
37
38pub use auth::*;
39pub use bind::*;
40pub use codecs::*;
41pub use connect::*;
42pub use constants::*;
43pub use dbobject::*;
44pub use execute::*;
45pub use fetch::*;
46pub use lob::*;
47pub use number::*;
48pub use sessionless::*;
49pub use subscr::*;
50pub use types::*;
51pub(crate) use errors::*;
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58
59 #[test]
60 fn connect_payload_matches_reference_shape() {
61 let payload = build_connect_packet_payload(
62 "(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=1522))(CONNECT_DATA=(SERVICE_NAME=FREEPDB1)))",
63 8192,
64 )
65 .expect("connect payload should encode");
66 assert_eq!(&payload[..8], &[0x01, 0x3f, 0x01, 0x2c, 0, 1, 0x20, 0]);
67 }
68
69 #[test]
70 fn auth_phase_one_contains_identity_keys() {
71 let payload = build_fast_auth_phase_one_payload("u", "p", "m", "o", "t", 42)
72 .expect("auth packet should encode");
73 let text = String::from_utf8_lossy(&payload);
74 assert!(text.contains("AUTH_PROGRAM_NM"));
75 assert!(text.contains("AUTH_MACHINE"));
76 assert!(text.contains("AUTH_SID"));
77 }
78
79 #[test]
80 fn nchar_bind_text_uses_utf16be() {
81 assert_eq!(encode_text_value("Aあ", CS_FORM_IMPLICIT), b"A\xE3\x81\x82");
82 assert_eq!(
83 encode_text_value("Aあ", CS_FORM_NCHAR),
84 vec![0x00, 0x41, 0x30, 0x42]
85 );
86 }
87
88 #[test]
89 fn query_response_decodes_prefetched_text_row_with_no_data_eof() {
90 let payload = Vec::from_hex(concat!(
91 "101710740fb986350b6010fbcb6e06a74ed0787e060a110328014001018201800000",
92 "014000000000020369010140023ffe010501050556414c554500000000000000000000",
93 "010707787e060a110b1000021fe8010a010a00062201010001020000000708414c33",
94 "32555446380801060323a4d500010100000000000004010102013b010102057b0000",
95 "01010003000000000000000000000000030001010000000002057b0101010300194f",
96 "52412d30313430333a206e6f206461746120666f756e640a1d",
97 ))
98 .expect("fixture response should be valid hex");
99
100 let parsed = parse_query_response(&payload, ClientCapabilities::default())
101 .expect("accepted execute response should decode");
102
103 assert_eq!(parsed.columns.len(), 1);
104 assert_eq!(parsed.columns[0].name, "VALUE");
105 assert_eq!(
106 parsed.rows,
107 vec![vec![Some(QueryValue::Text("AL32UTF8".into()))]]
108 );
109 assert!(!parsed.more_rows);
110 }
111
112 #[test]
113 fn fetch_response_decodes_rows_with_previous_cursor_metadata() {
114 let payload = Vec::from_hex("06020101000205dc0001010101000702c1041d")
115 .expect("fixture response should be valid hex");
116 let columns = vec![number_column("INTCOL"), number_column("NUMBERCOL")];
117 let previous_row = vec![
118 Some(QueryValue::number_from_text("2", true)),
119 Some(QueryValue::number_from_text("0.5", false)),
120 ];
121
122 let parsed = parse_query_response_with_context(
123 &payload,
124 ClientCapabilities::default(),
125 &columns,
126 Some(&previous_row),
127 )
128 .expect("fetch response should decode using cached cursor metadata");
129
130 assert_eq!(parsed.columns, columns);
131 assert_eq!(
132 parsed.rows,
133 vec![vec![
134 Some(QueryValue::number_from_text("3", true)),
135 previous_row[1].clone(),
136 ]]
137 );
138 }
139
140 #[test]
141 fn fetch_response_skips_long_status_fields() {
142 let payload =
143 Vec::from_hex("07036162638101001d").expect("fixture response should be valid hex");
144 let columns = vec![long_column("LONGCOL")];
145
146 let parsed = parse_fetch_response_with_context(
147 &payload,
148 ClientCapabilities::default(),
149 &columns,
150 None,
151 )
152 .expect("fetch response should consume LONG status fields");
153
154 assert_eq!(
155 parsed.rows,
156 vec![vec![Some(QueryValue::Text("abc".into()))]]
157 );
158 }
159
160 #[test]
161 fn fetch_response_decodes_mid_row_oracle_error() {
162 let payload = Vec::from_hex(concat!(
163 "150101010703c20401010205100205db0205c400000106018f030000000000",
164 "0301214d0118000293b60201c60000080000000000000205db0205c40103",
165 "00244f52412d30313437363a2064697669736f7220697320657175616c20",
166 "746f207a65726f0a1d",
167 ))
168 .expect("fixture response should be valid hex");
169 let columns = vec![number_column("INTCOL"), number_column("NUMBERCOL")];
170 let previous_row = vec![
171 Some(QueryValue::number_from_text("1499", true)),
172 Some(QueryValue::number_from_text("0.5", false)),
173 ];
174
175 let err = parse_query_response_with_context(
176 &payload,
177 ClientCapabilities::default(),
178 &columns,
179 Some(&previous_row),
180 )
181 .expect_err("mid-row error info should surface as a server error");
182
183 assert_eq!(
184 err.to_string(),
185 "server returned Oracle error: ORA-01476: divisor is equal to zero"
186 );
187 }
188
189 #[test]
190 fn lob_read_payload_writes_modern_token_field() {
191 let locator = [0x00, 0x70, 0xaa];
192 let modern =
193 build_lob_read_payload_with_seq(&locator, 1, 5, 8, TNS_CCAP_FIELD_VERSION_23_1_EXT_1)
194 .expect("LOB read payload should encode");
195 assert_eq!(
196 &modern[..7],
197 &[TNS_MSG_TYPE_FUNCTION, TNS_FUNC_LOB_OP, 8, 0, 1, 1, 3]
198 );
199
200 let legacy =
201 build_lob_read_payload_with_seq(&locator, 1, 5, 8, TNS_CCAP_FIELD_VERSION_23_1)
202 .expect("LOB read payload should encode");
203 assert_eq!(
204 &legacy[..6],
205 &[TNS_MSG_TYPE_FUNCTION, TNS_FUNC_LOB_OP, 8, 1, 1, 3]
206 );
207 }
208
209 #[test]
210 fn lob_locator_temporary_flags_match_reference_offsets() {
211 let mut locator = vec![0; 40];
212 assert!(!lob_locator_is_temporary(&locator));
213
214 locator[TNS_LOB_LOC_OFFSET_FLAG_1] = TNS_LOB_LOC_FLAGS_ABSTRACT;
215 assert!(lob_locator_is_temporary(&locator));
216
217 locator[TNS_LOB_LOC_OFFSET_FLAG_1] = 0;
218 locator[TNS_LOB_LOC_OFFSET_FLAG_4] = TNS_LOB_LOC_FLAGS_TEMP;
219 assert!(lob_locator_is_temporary(&locator));
220 }
221
222 #[test]
223 fn lob_free_temp_payload_writes_array_free_operation() {
224 let locator = vec![0xaa; 40];
225 let payload = build_lob_free_temp_payload_with_seq(
226 std::slice::from_ref(&locator),
227 9,
228 TNS_CCAP_FIELD_VERSION_23_1_EXT_1,
229 )
230 .expect("LOB free-temp payload should encode");
231
232 assert_eq!(
233 &payload[..19],
234 &[
235 TNS_MSG_TYPE_FUNCTION,
236 TNS_FUNC_LOB_OP,
237 9,
238 0,
239 1,
240 1,
241 40,
242 0,
243 0,
244 0,
245 0,
246 0,
247 0,
248 0,
249 4,
250 0,
251 8,
252 1,
253 0x11,
254 ]
255 );
256 assert!(payload.ends_with(&locator));
257 }
258
259 #[test]
260 fn lob_free_temp_response_skips_returned_locator_parameter() {
261 let payload = Vec::from_hex(concat!(
262 "0800260000020080000002ee5500000044000000030369000a000000000002",
263 "5295f656000000010000040101021a390000000000000000000000000000",
264 "00000000000a000000000000000000001d",
265 ))
266 .expect("fixture response should be valid hex");
267
268 parse_lob_free_temp_response(&payload, ClientCapabilities::default(), 40)
269 .expect("free-temp response should consume returned locator");
270 }
271
272 #[test]
273 fn rowid_value_decodes_physical_rowid() {
274 let mut reader = TtcReader::new(&[
275 13, 1, 1, 1, 2, 0, 1, 3, 1, 4, ]);
282
283 let value = parse_rowid_value(&mut reader).expect("physical rowid should decode");
284
285 assert_eq!(value.as_deref(), Some("AAAAABAACAAAAADAAE"));
286 assert_eq!(reader.remaining(), 0);
287 }
288
289 #[test]
290 fn urowid_value_decodes_physical_rowid() {
291 let mut reader = TtcReader::new(&[
292 1, 13, 13, 1, 0, 0, 0, 1, 0, 2, 0, 0, 0, 3, 0, 4, ]);
300
301 let value = parse_urowid_value(&mut reader).expect("physical urowid should decode");
302
303 assert_eq!(value.as_deref(), Some("AAAAABAACAAAAADAAE"));
304 assert_eq!(reader.remaining(), 0);
305 }
306
307 #[test]
308 fn urowid_value_decodes_logical_rowid() {
309 let mut reader = TtcReader::new(&[
310 1, 13, 13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
313 ]);
314
315 let value = parse_urowid_value(&mut reader).expect("logical urowid should decode");
316
317 assert_eq!(value.as_deref(), Some("*AQIDBAUGBwgJCgsM"));
318 assert_eq!(reader.remaining(), 0);
319 }
320
321 #[test]
322 fn binary_double_round_trips_oracle_canonical_bytes() {
323 for value in [0.0, -0.0, 1.5, -2.25, f64::INFINITY, f64::NEG_INFINITY] {
324 let decoded = decode_binary_double(&encode_binary_double(value))
325 .expect("BINARY_DOUBLE should round trip");
326 assert_eq!(decoded.to_bits(), value.to_bits());
327 }
328
329 let decoded =
330 decode_binary_double(&encode_binary_double(f64::NAN)).expect("NaN should decode");
331 assert!(decoded.is_nan());
332 }
333
334 #[test]
335 fn bind_value_type_info_reports_protocol_metadata() {
336 assert_eq!(bind_value_type_info(&BindValue::Null), None);
337 assert_eq!(
338 bind_value_type_info(&BindValue::Text("abc".into())),
339 Some(BindTypeInfo {
340 ora_type_num: ORA_TYPE_NUM_VARCHAR,
341 csfrm: CS_FORM_IMPLICIT,
342 buffer_size: 12,
343 })
344 );
345 assert_eq!(
346 bind_value_type_info(&BindValue::BinaryDouble(1.25)),
347 Some(BindTypeInfo {
348 ora_type_num: ORA_TYPE_NUM_BINARY_DOUBLE,
349 csfrm: 0,
350 buffer_size: ORA_TYPE_SIZE_BINARY_DOUBLE,
351 })
352 );
353 }
354
355 #[test]
356 fn adjust_refetch_metadata_follows_reference_rules() {
357 let column = |ora_type_num: u8, csfrm: u8| ColumnMetadata {
358 name: "VALUE".to_string(),
359 ora_type_num,
360 csfrm,
361 precision: 0,
362 scale: 0,
363 buffer_size: 4000,
364 max_size: 1000,
365 nulls_allowed: true,
366 is_json: false,
367 is_oson: false,
368 object_schema: None,
369 object_type_name: None,
370 is_array: false,
371 vector_dimensions: None,
372 vector_format: 0,
373 vector_flags: 0,
374 ..Default::default()
375 };
376
377 let mut current = column(ORA_TYPE_NUM_CLOB, CS_FORM_IMPLICIT);
379 assert!(adjust_refetch_metadata(
380 &column(ORA_TYPE_NUM_VARCHAR, CS_FORM_IMPLICIT),
381 &mut current
382 ));
383 assert_eq!(current.ora_type_num, ORA_TYPE_NUM_LONG);
384 assert_eq!(current.csfrm, CS_FORM_IMPLICIT);
385 assert_eq!(current.buffer_size, TNS_MAX_LONG_LENGTH);
386 assert_eq!(current.max_size, 0);
387
388 let mut current = column(ORA_TYPE_NUM_CLOB, CS_FORM_NCHAR);
390 assert!(adjust_refetch_metadata(
391 &column(ORA_TYPE_NUM_VARCHAR, CS_FORM_NCHAR),
392 &mut current
393 ));
394 assert_eq!(current.ora_type_num, ORA_TYPE_NUM_LONG);
395 assert_eq!(current.csfrm, CS_FORM_NCHAR);
396
397 let mut current = column(ORA_TYPE_NUM_BLOB, 0);
399 assert!(adjust_refetch_metadata(
400 &column(ORA_TYPE_NUM_RAW, 0),
401 &mut current
402 ));
403 assert_eq!(current.ora_type_num, ORA_TYPE_NUM_LONG_RAW);
404 assert_eq!(current.csfrm, 0);
405
406 let mut current = column(ORA_TYPE_NUM_CLOB, CS_FORM_IMPLICIT);
408 assert!(!adjust_refetch_metadata(
409 &column(ORA_TYPE_NUM_NUMBER, 0),
410 &mut current
411 ));
412 assert_eq!(current.ora_type_num, ORA_TYPE_NUM_CLOB);
413 let mut current = column(ORA_TYPE_NUM_VARCHAR, CS_FORM_IMPLICIT);
414 assert!(!adjust_refetch_metadata(
415 &column(ORA_TYPE_NUM_CLOB, CS_FORM_IMPLICIT),
416 &mut current
417 ));
418 assert_eq!(current.ora_type_num, ORA_TYPE_NUM_VARCHAR);
419 }
420
421 #[test]
422 fn row_bind_metadata_keeps_raw_type_with_promoted_buffer_size() {
423 let rows = vec![
427 vec![BindValue::Raw(vec![0; 25_000])],
428 vec![BindValue::Raw(vec![0; 40_000])],
429 ];
430 let mut writer = TtcWriter::new();
431
432 let (ora_type_num, csfrm, buffer_size) =
433 write_bind_metadata_for_rows(&mut writer, &rows, 0).expect("metadata writes");
434
435 assert_eq!(ora_type_num, ORA_TYPE_NUM_RAW);
436 assert_eq!(csfrm, 0);
437 assert_eq!(buffer_size, 40_000);
438 }
439
440 #[test]
441 fn non_plsql_bind_rows_emit_long_values_last() {
442 let row = vec![
443 BindValue::Number("1".into()),
444 BindValue::Raw(vec![0; 40_000]),
445 BindValue::Number("8".into()),
446 BindValue::Text("A".repeat(40_000)),
447 ];
448 let metadata = row.iter().map(bind_metadata).collect::<Vec<_>>();
449
450 assert_eq!(
451 bind_row_value_order(&row, &metadata, false),
452 vec![0, 2, 1, 3]
453 );
454 assert_eq!(
455 bind_row_value_order(&row, &metadata, true),
456 vec![0, 1, 2, 3]
457 );
458 }
459
460 #[test]
461 fn lob_bind_metadata_sets_prefetch_continuation_flag() {
462 let mut writer = TtcWriter::new();
463 write_bind_metadata_with_type(
464 &mut writer,
465 &BindValue::Lob {
466 ora_type_num: ORA_TYPE_NUM_CLOB,
467 csfrm: CS_FORM_IMPLICIT,
468 locator: vec![0; 40],
469 },
470 ORA_TYPE_NUM_CLOB,
471 CS_FORM_IMPLICIT,
472 1,
473 )
474 .expect("CLOB bind metadata should encode");
475 let encoded = writer.into_bytes();
476 let mut reader = TtcReader::new(&encoded);
477
478 assert_eq!(reader.read_u8().expect("type"), ORA_TYPE_NUM_CLOB);
479 reader.skip(3).expect("flags, precision, scale");
480 assert_eq!(reader.read_ub4().expect("buffer size"), 1);
481 assert_eq!(reader.read_ub4().expect("max elements"), 0);
482 assert_eq!(
483 reader.read_ub8().expect("cont flags"),
484 TNS_LOB_PREFETCH_FLAG
485 );
486 }
487
488 #[test]
489 fn define_metadata_from_bind_preserves_clob_long_define_semantics() {
490 let mut source = number_column("VALUE");
491 source.ora_type_num = ORA_TYPE_NUM_CLOB;
492 source.csfrm = CS_FORM_NCHAR;
493 let metadata = define_metadata_from_bind(
494 &source,
495 &BindValue::TypedNull {
496 ora_type_num: ORA_TYPE_NUM_VARCHAR,
497 csfrm: CS_FORM_IMPLICIT,
498 buffer_size: 128,
499 },
500 );
501
502 assert_eq!(metadata.ora_type_num, ORA_TYPE_NUM_LONG);
503 assert_eq!(metadata.csfrm, CS_FORM_NCHAR);
504 assert_eq!(metadata.buffer_size, TNS_MAX_LONG_LENGTH);
505 assert_eq!(metadata.max_size, 0);
506 }
507
508 #[test]
509 fn output_bind_normalizes_type_metadata() {
510 assert_eq!(
511 output_bind(BindValue::Text("abc".into())),
512 BindValue::Output {
513 ora_type_num: ORA_TYPE_NUM_VARCHAR,
514 csfrm: CS_FORM_IMPLICIT,
515 buffer_size: 12,
516 }
517 );
518 assert_eq!(
519 returning_output_bind(BindValue::Null),
520 BindValue::ReturnOutput {
521 ora_type_num: ORA_TYPE_NUM_VARCHAR,
522 csfrm: CS_FORM_IMPLICIT,
523 buffer_size: 1,
524 }
525 );
526 assert!(is_cursor_bind_template(&cursor_bind_template()));
527 }
528
529 #[test]
530 fn public_dbtype_names_come_from_protocol_metadata() {
531 assert_eq!(
532 public_dbtype_name_from_type_name("NATIVE_FLOAT"),
533 "DB_TYPE_BINARY_DOUBLE"
534 );
535 assert_eq!(
536 public_dbtype_name_from_bind(&BindValue::BinaryDouble(1.25)),
537 "DB_TYPE_BINARY_DOUBLE"
538 );
539 assert_eq!(
540 public_dbtype_name_from_bind(&BindValue::TypedNull {
541 ora_type_num: ORA_TYPE_NUM_VARCHAR,
542 csfrm: CS_FORM_NCHAR,
543 buffer_size: 16,
544 }),
545 "DB_TYPE_NVARCHAR"
546 );
547 }
548
549 #[test]
550 fn public_dbtype_names_from_column_metadata_preserve_fetch_semantics() {
551 let mut metadata = number_column("VALUE");
552 assert_eq!(
553 public_dbtype_name_from_column_metadata(&metadata),
554 "DB_TYPE_NUMBER"
555 );
556
557 metadata.ora_type_num = ORA_TYPE_NUM_CHAR;
558 metadata.csfrm = CS_FORM_NCHAR;
559 assert_eq!(
560 public_dbtype_name_from_column_metadata(&metadata),
561 "DB_TYPE_NCHAR"
562 );
563
564 metadata.ora_type_num = ORA_TYPE_NUM_VARCHAR;
565 assert_eq!(
566 public_dbtype_name_from_column_metadata(&metadata),
567 "DB_TYPE_NVARCHAR"
568 );
569
570 metadata.ora_type_num = ORA_TYPE_NUM_CLOB;
571 assert_eq!(
572 public_dbtype_name_from_column_metadata(&metadata),
573 "DB_TYPE_NCLOB"
574 );
575
576 metadata.csfrm = CS_FORM_IMPLICIT;
577 for (ora_type_num, expected) in [
578 (ORA_TYPE_NUM_LONG, "DB_TYPE_LONG"),
579 (ORA_TYPE_NUM_LONG_RAW, "DB_TYPE_LONG_RAW"),
580 (ORA_TYPE_NUM_ROWID, "DB_TYPE_ROWID"),
581 (ORA_TYPE_NUM_UROWID, "DB_TYPE_UROWID"),
582 (ORA_TYPE_NUM_TIMESTAMP, "DB_TYPE_TIMESTAMP"),
583 (ORA_TYPE_NUM_TIMESTAMP_LTZ, "DB_TYPE_TIMESTAMP_LTZ"),
584 (ORA_TYPE_NUM_TIMESTAMP_TZ, "DB_TYPE_TIMESTAMP_TZ"),
585 (ORA_TYPE_NUM_BFILE, "DB_TYPE_BFILE"),
586 ] {
587 metadata.ora_type_num = ora_type_num;
588 assert_eq!(public_dbtype_name_from_column_metadata(&metadata), expected);
589 }
590
591 metadata.ora_type_num = ORA_TYPE_NUM_OBJECT;
592 metadata.object_schema = Some("SYS".into());
593 metadata.object_type_name = Some("XMLTYPE".into());
594 assert!(column_metadata_is_xmltype(&metadata));
595 assert_eq!(
596 public_dbtype_name_from_column_metadata(&metadata),
597 "DB_TYPE_XMLTYPE"
598 );
599 }
600
601 #[test]
602 fn oracle_dictionary_type_metadata_is_protocol_owned() {
603 assert_eq!(
604 public_dbtype_name_from_oracle_type_name("timestamp with local time zone"),
605 "DB_TYPE_TIMESTAMP_LTZ"
606 );
607 assert_eq!(
608 public_dbtype_name_from_oracle_type_name("TIMESTAMP WITH TZ"),
609 "DB_TYPE_TIMESTAMP_TZ"
610 );
611 assert_eq!(
612 public_dbtype_name_from_oracle_type_name("BINARY_FLOAT"),
613 "DB_TYPE_BINARY_FLOAT"
614 );
615 assert_eq!(
616 public_dbtype_name_from_oracle_type_name("UDT_OBJECT"),
617 "DB_TYPE_OBJECT"
618 );
619 for (name, expected) in [
622 ("BOOLEAN", "DB_TYPE_BOOLEAN"),
623 ("PL/SQL BOOLEAN", "DB_TYPE_BOOLEAN"),
624 ("PL/SQL PLS INTEGER", "DB_TYPE_BINARY_INTEGER"),
625 ("PL/SQL BINARY INTEGER", "DB_TYPE_BINARY_INTEGER"),
626 ("BINARY_INTEGER", "DB_TYPE_BINARY_INTEGER"),
627 ("PLS_INTEGER", "DB_TYPE_BINARY_INTEGER"),
628 ("INTERVAL DAY TO SECOND", "DB_TYPE_INTERVAL_DS"),
629 ("INTERVAL YEAR TO MONTH", "DB_TYPE_INTERVAL_YM"),
630 ] {
631 assert_eq!(public_dbtype_name_from_oracle_type_name(name), expected);
632 }
633
634 assert_eq!(
635 dbobject_attr_precision_scale("NUMBER", None, Some(0)),
636 (38, 0)
637 );
638 assert_eq!(
639 dbobject_attr_precision_scale("NUMBER", None, None),
640 (0, -127)
641 );
642 assert_eq!(
643 dbobject_attr_precision_scale("DOUBLE PRECISION", None, None),
644 (126, -127)
645 );
646 assert_eq!(dbobject_attr_max_size("NVARCHAR2", Some(10)), 20);
647 assert_eq!(
648 dbobject_rowtype_attr_max_size("NVARCHAR2", Some(40), Some(7)),
649 14
650 );
651 assert_eq!(
652 dbobject_rowtype_attr_max_size("NVARCHAR2", Some(40), Some(0)),
653 80
654 );
655 assert_eq!(dbobject_rowtype_attr_max_size("NUMBER", Some(22), None), 0);
656 }
657
658 #[test]
659 fn bind_templates_are_protocol_owned() {
660 assert_eq!(
661 bind_template_from_type_name("DB_TYPE_NCLOB", 0),
662 BindValue::TypedNull {
663 ora_type_num: ORA_TYPE_NUM_LONG,
664 csfrm: CS_FORM_NCHAR,
665 buffer_size: TNS_MAX_LONG_LENGTH,
666 }
667 );
668 assert_eq!(
669 bind_template_from_type_name("DB_TYPE_BLOB", 0),
670 BindValue::TypedNull {
671 ora_type_num: ORA_TYPE_NUM_LONG_RAW,
672 csfrm: 0,
673 buffer_size: TNS_MAX_LONG_LENGTH,
674 }
675 );
676 assert_eq!(
677 dbobject_element_bind_type_info("DB_TYPE_NCHAR", 12),
678 BindTypeInfo {
679 ora_type_num: ORA_TYPE_NUM_VARCHAR,
680 csfrm: CS_FORM_NCHAR,
681 buffer_size: 4000,
682 }
683 );
684 }
685
686 #[test]
687 fn dbobject_packed_reader_decodes_header_lengths_and_nulls() {
688 let bytes = [
689 TNS_OBJ_NO_PREFIX_SEG,
690 1,
691 0,
692 4,
693 b't',
694 b'e',
695 b's',
696 b't',
697 TNS_OBJ_ATOMIC_NULL,
698 ];
699 let mut reader = DbObjectPackedReader::new(&bytes);
700 reader.read_header().expect("header should decode");
701 assert_eq!(
702 reader
703 .read_value_bytes()
704 .expect("value bytes should decode"),
705 Some(b"test".to_vec())
706 );
707 assert!(reader
708 .read_atomic_null(false)
709 .expect("atomic null should decode"));
710 }
711
712 #[test]
713 fn dbobject_scalar_decoders_match_oracle_canonical_data() {
714 assert_eq!(
715 decode_dbobject_text(&[0, b'A'], "DB_TYPE_NCHAR").expect("nchar text"),
716 "A"
717 );
718 assert_eq!(
719 decode_dbobject_xmltype_text(&[
720 TNS_OBJ_NO_PREFIX_SEG,
721 1,
722 0,
723 0,
724 0,
725 0,
726 0,
727 TNS_XML_TYPE_STRING as u8,
728 b'<',
729 b'x',
730 b'/',
731 b'>',
732 ])
733 .expect("XMLTYPE text should decode"),
734 Some("<x/>".to_string())
735 );
736 assert_eq!(
737 decode_dbobject_binary_float(&[0xbf, 0x80, 0, 0]).expect("binary float"),
738 1.0
739 );
740 assert_eq!(
741 decode_dbobject_binary_double(&[0xbf, 0xf0, 0, 0, 0, 0, 0, 0]).expect("binary double"),
742 1.0
743 );
744 }
745
746 #[test]
747 fn lob_text_encoding_uses_csfrm_and_locator_flags() {
748 assert_eq!(
749 decode_lob_text(b"Plain", CS_FORM_IMPLICIT, None).expect("utf8 lob"),
750 "Plain"
751 );
752 assert_eq!(
753 encode_lob_text("Text", CS_FORM_IMPLICIT, None),
754 b"Text".to_vec()
755 );
756 assert_eq!(
757 encode_lob_text("AB", CS_FORM_NCHAR, None),
758 vec![0, b'A', 0, b'B']
759 );
760 assert_eq!(
761 decode_lob_text(&[0, b'A', 0, b'B'], CS_FORM_NCHAR, None).expect("nchar lob"),
762 "AB"
763 );
764
765 let mut locator = vec![0; 8];
766 locator[TNS_LOB_LOC_OFFSET_FLAG_3] = TNS_LOB_LOC_FLAGS_VAR_LENGTH_CHARSET;
767 locator[TNS_LOB_LOC_OFFSET_FLAG_4] = TNS_LOB_LOC_FLAGS_LITTLE_ENDIAN;
768 assert_eq!(
769 encode_lob_text("AB", CS_FORM_IMPLICIT, Some(&locator)),
770 vec![b'A', 0, b'B', 0]
771 );
772 assert_eq!(
773 decode_lob_text(&[b'A', 0, b'B', 0], CS_FORM_IMPLICIT, Some(&locator))
774 .expect("locator utf16 lob"),
775 "AB"
776 );
777 }
778
779 #[test]
780 fn bfile_locator_name_decodes_directory_and_file_tail() {
781 let locator = Vec::from_hex(
782 "0808000000010000000000000015544553545f313933365f4d495353494e475f444952\
783 001a746573745f313933365f6d697373696e675f66696c652e747874",
784 )
785 .expect("BFILE locator fixture should be valid hex");
786
787 assert_eq!(
788 decode_bfile_locator_name(&locator),
789 Some((
790 "TEST_1936_MISSING_DIR".to_string(),
791 "test_1936_missing_file.txt".to_string()
792 ))
793 );
794 }
795
796 fn number_column(name: &str) -> ColumnMetadata {
797 ColumnMetadata {
798 name: name.into(),
799 ora_type_num: ORA_TYPE_NUM_NUMBER,
800 csfrm: CS_FORM_IMPLICIT,
801 precision: 0,
802 scale: 0,
803 buffer_size: ORA_TYPE_SIZE_NUMBER,
804 max_size: ORA_TYPE_SIZE_NUMBER,
805 nulls_allowed: true,
806 is_json: false,
807 is_oson: false,
808 object_schema: None,
809 object_type_name: None,
810 is_array: false,
811 vector_dimensions: None,
812 vector_format: 0,
813 vector_flags: 0,
814 ..Default::default()
815 }
816 }
817
818 fn long_column(name: &str) -> ColumnMetadata {
819 ColumnMetadata {
820 name: name.into(),
821 ora_type_num: ORA_TYPE_NUM_LONG,
822 csfrm: CS_FORM_IMPLICIT,
823 precision: 0,
824 scale: 0,
825 buffer_size: TNS_MAX_LONG_LENGTH,
826 max_size: 0,
827 nulls_allowed: true,
828 is_json: false,
829 is_oson: false,
830 object_schema: None,
831 object_type_name: None,
832 is_array: false,
833 vector_dimensions: None,
834 vector_format: 0,
835 vector_flags: 0,
836 ..Default::default()
837 }
838 }
839}