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