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