1#![forbid(unsafe_code)]
2
3use super::*;
4
5impl BindValue {
6 pub(crate) fn is_output_only(&self) -> bool {
7 matches!(self, BindValue::Output { .. })
8 || matches!(self, BindValue::ReturnOutput { .. })
9 || matches!(self, BindValue::ObjectOutput { .. })
10 || matches!(self, BindValue::Array { values, .. } if values.is_empty())
11 }
12
13 pub(crate) fn is_return_output(&self) -> bool {
14 matches!(self, BindValue::ReturnOutput { .. })
15 || matches!(
16 self,
17 BindValue::ObjectOutput {
18 is_return: true,
19 ..
20 }
21 )
22 }
23}
24
25pub(crate) fn write_bind_metadata_with_type(
26 writer: &mut TtcWriter,
27 value: &BindValue,
28 ora_type_num: u8,
29 csfrm: u8,
30 buffer_size: u32,
31 ttc_field_version: u8,
32) -> Result<()> {
33 let (flags, max_elements) = match value {
34 BindValue::Array { max_elements, .. } => {
35 (TNS_BIND_USE_INDICATORS | TNS_BIND_ARRAY, *max_elements)
36 }
37 _ => (TNS_BIND_USE_INDICATORS, 0),
38 };
39 let buffer_size = if ora_type_num == ORA_TYPE_NUM_JSON {
42 TNS_JSON_MAX_LENGTH
43 } else {
44 buffer_size
45 };
46 writer.write_u8(ora_type_num);
47 writer.write_u8(flags);
48 writer.write_u8(0);
49 writer.write_u8(0);
50 writer.write_ub4(buffer_size);
51 writer.write_ub4(max_elements);
52 let cont_flags = if matches!(
53 ora_type_num,
54 ORA_TYPE_NUM_CLOB | ORA_TYPE_NUM_BLOB | ORA_TYPE_NUM_VECTOR | ORA_TYPE_NUM_JSON
55 ) {
56 TNS_LOB_PREFETCH_FLAG
57 } else {
58 0
59 };
60 writer.write_ub8(cont_flags);
61 if let BindValue::ObjectOutput { oid, version, .. }
62 | BindValue::ObjectInput { oid, version, .. } = value
63 {
64 writer.write_bytes_with_two_lengths(Some(oid))?;
65 writer.write_ub4(*version);
66 } else {
67 writer.write_ub4(0);
68 writer.write_ub2(0);
69 }
70 if csfrm != 0 {
71 writer.write_ub2(TNS_CHARSET_UTF8);
72 } else {
73 writer.write_ub2(0);
74 }
75 writer.write_u8(csfrm);
76 let lob_prefetch_length = match ora_type_num {
79 ORA_TYPE_NUM_VECTOR => TNS_VECTOR_MAX_LENGTH,
80 ORA_TYPE_NUM_JSON => TNS_JSON_MAX_LENGTH,
81 _ => 0,
82 };
83 writer.write_ub4(lob_prefetch_length);
84 if version_gates::carries_oaccolid(ttc_field_version) {
93 writer.write_ub4(0); }
95 Ok(())
96}
97
98pub fn bind_value_type_info(value: &BindValue) -> Option<BindTypeInfo> {
99 let (ora_type_num, csfrm, buffer_size) = match value {
100 BindValue::Null => return None,
101 BindValue::InOut {
108 value,
109 out_buffer_size,
110 } => {
111 let (ora_type_num, csfrm, input_size) = match bind_value_type_info(value) {
112 Some(info) => (info.ora_type_num, info.csfrm, info.buffer_size),
113 None => (ORA_TYPE_NUM_VARCHAR, CS_FORM_IMPLICIT, 0),
114 };
115 return Some(BindTypeInfo {
116 ora_type_num,
117 csfrm,
118 buffer_size: input_size.max(*out_buffer_size).max(1),
119 });
120 }
121 BindValue::TypedNull {
122 ora_type_num,
123 csfrm,
124 buffer_size,
125 }
126 | BindValue::Output {
127 ora_type_num,
128 csfrm,
129 buffer_size,
130 }
131 | BindValue::ReturnOutput {
132 ora_type_num,
133 csfrm,
134 buffer_size,
135 } => (*ora_type_num, *csfrm, (*buffer_size).max(1)),
136 BindValue::ObjectOutput { buffer_size, .. }
137 | BindValue::ObjectInput { buffer_size, .. } => {
138 (ORA_TYPE_NUM_OBJECT, 0, (*buffer_size).max(1))
139 }
140 BindValue::Text(value) => {
146 let buffer_size = u32::try_from(value.chars().count())
147 .unwrap_or(u32::MAX)
148 .saturating_mul(4)
149 .max(1);
150 (ORA_TYPE_NUM_VARCHAR, CS_FORM_IMPLICIT, buffer_size)
151 }
152 BindValue::Raw(value) => {
153 let buffer_size = u32::try_from(value.len()).unwrap_or(u32::MAX).max(1);
154 (ORA_TYPE_NUM_RAW, 0, buffer_size)
155 }
156 BindValue::Lob {
157 ora_type_num,
158 csfrm,
159 ..
160 } => (*ora_type_num, *csfrm, 1),
161 BindValue::Number(_) => (ORA_TYPE_NUM_NUMBER, 0, ORA_TYPE_SIZE_NUMBER),
162 BindValue::BinaryInteger(_) => (ORA_TYPE_NUM_BINARY_INTEGER, 0, ORA_TYPE_SIZE_NUMBER),
163 BindValue::Boolean(_) => (ORA_TYPE_NUM_BOOLEAN, 0, ORA_TYPE_SIZE_BOOLEAN),
164 BindValue::BinaryDouble(_) => (ORA_TYPE_NUM_BINARY_DOUBLE, 0, ORA_TYPE_SIZE_BINARY_DOUBLE),
165 BindValue::BinaryFloat(_) => (ORA_TYPE_NUM_BINARY_FLOAT, 0, ORA_TYPE_SIZE_BINARY_FLOAT),
166 BindValue::IntervalDS { .. } => (ORA_TYPE_NUM_INTERVAL_DS, 0, ORA_TYPE_SIZE_INTERVAL_DS),
167 BindValue::IntervalYM { .. } => (ORA_TYPE_NUM_INTERVAL_YM, 0, ORA_TYPE_SIZE_INTERVAL_YM),
168 BindValue::DateTime { .. } => (ORA_TYPE_NUM_DATE, 0, ORA_TYPE_SIZE_DATE),
169 BindValue::Timestamp { ora_type_num, .. } => (
170 *ora_type_num,
171 0,
172 if *ora_type_num == ORA_TYPE_NUM_TIMESTAMP_TZ {
173 ORA_TYPE_SIZE_TIMESTAMP_TZ
174 } else {
175 ORA_TYPE_SIZE_TIMESTAMP
176 },
177 ),
178 BindValue::TimestampTz { .. } => (ORA_TYPE_NUM_TIMESTAMP_TZ, 0, ORA_TYPE_SIZE_TIMESTAMP_TZ),
179 BindValue::Array {
180 ora_type_num,
181 csfrm,
182 buffer_size,
183 ..
184 } => (*ora_type_num, *csfrm, (*buffer_size).max(1)),
185 BindValue::Vector(_) => (ORA_TYPE_NUM_VECTOR, 0, TNS_VECTOR_MAX_LENGTH),
188 BindValue::Json(_) => (ORA_TYPE_NUM_JSON, 0, 1),
194 BindValue::Cursor { .. } => (ORA_TYPE_NUM_CURSOR, 0, 4),
195 };
196 Some(BindTypeInfo {
197 ora_type_num,
198 csfrm,
199 buffer_size,
200 })
201}
202
203pub fn define_metadata_from_bind(source: &ColumnMetadata, value: &BindValue) -> ColumnMetadata {
204 let Some(mut info) = bind_value_type_info(value) else {
205 return source.clone();
206 };
207 if source.ora_type_num == ORA_TYPE_NUM_CLOB
208 && matches!(
209 info.ora_type_num,
210 ORA_TYPE_NUM_CHAR | ORA_TYPE_NUM_LONG | ORA_TYPE_NUM_VARCHAR
211 )
212 {
213 info.ora_type_num = ORA_TYPE_NUM_LONG;
214 if source.csfrm != 0 {
215 info.csfrm = source.csfrm;
216 }
217 }
218 let mut metadata = source.clone();
219 metadata.ora_type_num = info.ora_type_num;
220 metadata.csfrm = info.csfrm;
221 if info.ora_type_num == ORA_TYPE_NUM_LONG {
222 metadata.buffer_size = TNS_MAX_LONG_LENGTH;
223 metadata.max_size = 0;
224 } else {
225 metadata.buffer_size = info.buffer_size.max(1);
226 metadata.max_size = info.buffer_size.max(1);
227 }
228 metadata
229}
230
231pub fn adjust_refetch_metadata(previous: &ColumnMetadata, current: &mut ColumnMetadata) -> bool {
238 if current.ora_type_num == ORA_TYPE_NUM_CLOB
239 && matches!(
240 previous.ora_type_num,
241 ORA_TYPE_NUM_CHAR | ORA_TYPE_NUM_LONG | ORA_TYPE_NUM_VARCHAR
242 )
243 {
244 current.ora_type_num = ORA_TYPE_NUM_LONG;
245 current.csfrm = previous.csfrm;
246 current.buffer_size = TNS_MAX_LONG_LENGTH;
247 current.max_size = 0;
248 return true;
249 }
250 if current.ora_type_num == ORA_TYPE_NUM_BLOB
251 && matches!(
252 previous.ora_type_num,
253 ORA_TYPE_NUM_RAW | ORA_TYPE_NUM_LONG_RAW
254 )
255 {
256 current.ora_type_num = ORA_TYPE_NUM_LONG_RAW;
257 current.csfrm = 0;
258 current.buffer_size = TNS_MAX_LONG_LENGTH;
259 current.max_size = 0;
260 return true;
261 }
262 false
263}
264
265pub fn output_bind(value: BindValue) -> BindValue {
266 match value {
267 BindValue::ObjectOutput {
268 schema,
269 type_name,
270 oid,
271 version,
272 buffer_size,
273 ..
274 } => BindValue::ObjectOutput {
275 schema,
276 type_name,
277 oid,
278 version,
279 buffer_size: buffer_size.max(1),
280 is_return: false,
281 },
282 value => {
283 let info = bind_value_type_info(&value).unwrap_or(BindTypeInfo {
284 ora_type_num: ORA_TYPE_NUM_VARCHAR,
285 csfrm: CS_FORM_IMPLICIT,
286 buffer_size: 1,
287 });
288 BindValue::Output {
289 ora_type_num: info.ora_type_num,
290 csfrm: info.csfrm,
291 buffer_size: info.buffer_size,
292 }
293 }
294 }
295}
296
297pub fn returning_output_bind(value: BindValue) -> BindValue {
298 match value {
299 BindValue::ObjectOutput {
300 schema,
301 type_name,
302 oid,
303 version,
304 buffer_size,
305 ..
306 } => BindValue::ObjectOutput {
307 schema,
308 type_name,
309 oid,
310 version,
311 buffer_size: buffer_size.max(1),
312 is_return: true,
313 },
314 value => {
315 let info = bind_value_type_info(&value).unwrap_or(BindTypeInfo {
316 ora_type_num: ORA_TYPE_NUM_VARCHAR,
317 csfrm: CS_FORM_IMPLICIT,
318 buffer_size: 1,
319 });
320 BindValue::ReturnOutput {
321 ora_type_num: info.ora_type_num,
322 csfrm: info.csfrm,
323 buffer_size: info.buffer_size,
324 }
325 }
326 }
327}
328
329pub fn cursor_bind_template() -> BindValue {
330 BindValue::TypedNull {
331 ora_type_num: ORA_TYPE_NUM_CURSOR,
332 csfrm: 0,
333 buffer_size: 4,
334 }
335}
336
337pub fn is_cursor_bind_template(value: &BindValue) -> bool {
338 matches!(
339 value,
340 BindValue::TypedNull {
341 ora_type_num: ORA_TYPE_NUM_CURSOR,
342 ..
343 }
344 )
345}
346
347pub fn public_dbtype_name_from_type_name(type_name: &str) -> &'static str {
348 match type_name {
349 "NUMBER" | "DB_TYPE_NUMBER" | "int" | "float" | "Decimal" => "DB_TYPE_NUMBER",
350 "NATIVE_INT" | "DB_TYPE_BINARY_INTEGER" => "DB_TYPE_BINARY_INTEGER",
351 "NATIVE_FLOAT" | "DB_TYPE_BINARY_DOUBLE" => "DB_TYPE_BINARY_DOUBLE",
352 "DB_TYPE_BINARY_FLOAT" | "BINARY_FLOAT" => "DB_TYPE_BINARY_FLOAT",
353 "DB_TYPE_BOOLEAN" | "BOOLEAN" | "bool" => "DB_TYPE_BOOLEAN",
354 "DB_TYPE_INTERVAL_DS" | "INTERVAL DAY TO SECOND" | "timedelta" => "DB_TYPE_INTERVAL_DS",
355 "DB_TYPE_INTERVAL_YM" | "INTERVAL YEAR TO MONTH" | "IntervalYM" => "DB_TYPE_INTERVAL_YM",
356 "DB_TYPE_BFILE" | "BFILE" => "DB_TYPE_BFILE",
357 "DB_TYPE_JSON" | "JSON" => "DB_TYPE_JSON",
358 "STRING" | "DB_TYPE_VARCHAR" | "str" => "DB_TYPE_VARCHAR",
359 "DB_TYPE_CHAR" => "DB_TYPE_CHAR",
360 "DB_TYPE_NCHAR" => "DB_TYPE_NCHAR",
361 "DB_TYPE_NVARCHAR" => "DB_TYPE_NVARCHAR",
362 "DB_TYPE_CLOB" | "CLOB" => "DB_TYPE_CLOB",
363 "DB_TYPE_NCLOB" | "NCLOB" => "DB_TYPE_NCLOB",
364 "DB_TYPE_BLOB" | "BLOB" => "DB_TYPE_BLOB",
365 "DB_TYPE_LONG" | "LONG" | "LONG_STRING" => "DB_TYPE_LONG",
366 "DB_TYPE_LONG_NVARCHAR" | "LONG NVARCHAR" => "DB_TYPE_LONG_NVARCHAR",
367 "DB_TYPE_LONG_RAW" | "LONG RAW" | "LONG_BINARY" => "DB_TYPE_LONG_RAW",
368 "DB_TYPE_RAW" | "BINARY" | "bytes" => "DB_TYPE_RAW",
369 "ROWID" | "DB_TYPE_ROWID" => "DB_TYPE_ROWID",
370 "DB_TYPE_UROWID" => "DB_TYPE_UROWID",
371 "DATETIME" | "DB_TYPE_DATE" | "date" | "datetime" => "DB_TYPE_DATE",
372 "DB_TYPE_TIMESTAMP" | "TIMESTAMP" => "DB_TYPE_TIMESTAMP",
373 "DB_TYPE_TIMESTAMP_LTZ" | "TIMESTAMP WITH LOCAL TIME ZONE" => "DB_TYPE_TIMESTAMP_LTZ",
374 "DB_TYPE_TIMESTAMP_TZ" | "TIMESTAMP WITH TIME ZONE" => "DB_TYPE_TIMESTAMP_TZ",
375 "DB_TYPE_CURSOR" | "CURSOR" => "DB_TYPE_CURSOR",
376 "DB_TYPE_VECTOR" | "VECTOR" => "DB_TYPE_VECTOR",
377 _ => "DB_TYPE_VARCHAR",
378 }
379}
380
381pub fn column_metadata_is_xmltype(metadata: &ColumnMetadata) -> bool {
382 metadata
383 .object_schema
384 .as_deref()
385 .is_some_and(|schema| schema.eq_ignore_ascii_case("SYS"))
386 && metadata
387 .object_type_name
388 .as_deref()
389 .is_some_and(|name| name.eq_ignore_ascii_case("XMLTYPE"))
390}
391
392pub fn public_dbtype_name_from_column_metadata(metadata: &ColumnMetadata) -> &'static str {
393 if column_metadata_is_xmltype(metadata) {
394 return "DB_TYPE_XMLTYPE";
395 }
396 match (metadata.ora_type_num, metadata.csfrm) {
397 (ORA_TYPE_NUM_LONG, CS_FORM_NCHAR) => "DB_TYPE_LONG_NVARCHAR",
398 (ORA_TYPE_NUM_LONG, _) => "DB_TYPE_LONG",
399 (ORA_TYPE_NUM_LONG_RAW, _) => "DB_TYPE_LONG_RAW",
400 (ORA_TYPE_NUM_VARCHAR, CS_FORM_NCHAR) => "DB_TYPE_NVARCHAR",
401 (ORA_TYPE_NUM_CHAR, CS_FORM_NCHAR) => "DB_TYPE_NCHAR",
402 (ORA_TYPE_NUM_CHAR, _) => "DB_TYPE_CHAR",
403 (ORA_TYPE_NUM_VARCHAR, _) => "DB_TYPE_VARCHAR",
404 (ORA_TYPE_NUM_RAW, _) => "DB_TYPE_RAW",
405 (ORA_TYPE_NUM_ROWID, _) => "DB_TYPE_ROWID",
406 (ORA_TYPE_NUM_UROWID, _) => "DB_TYPE_UROWID",
407 (ORA_TYPE_NUM_BINARY_DOUBLE, _) => "DB_TYPE_BINARY_DOUBLE",
408 (ORA_TYPE_NUM_BINARY_FLOAT, _) => "DB_TYPE_BINARY_FLOAT",
409 (ORA_TYPE_NUM_BINARY_INTEGER, _) => "DB_TYPE_BINARY_INTEGER",
410 (ORA_TYPE_NUM_NUMBER, _) => "DB_TYPE_NUMBER",
411 (ORA_TYPE_NUM_CURSOR, _) => "DB_TYPE_CURSOR",
412 (ORA_TYPE_NUM_OBJECT, _) => "DB_TYPE_OBJECT",
413 (ORA_TYPE_NUM_CLOB, CS_FORM_NCHAR) => "DB_TYPE_NCLOB",
414 (ORA_TYPE_NUM_CLOB, _) => "DB_TYPE_CLOB",
415 (ORA_TYPE_NUM_BLOB, _) => "DB_TYPE_BLOB",
416 (ORA_TYPE_NUM_BFILE, _) => "DB_TYPE_BFILE",
417 (ORA_TYPE_NUM_DATE, _) => "DB_TYPE_DATE",
418 (ORA_TYPE_NUM_TIMESTAMP, _) => "DB_TYPE_TIMESTAMP",
419 (ORA_TYPE_NUM_TIMESTAMP_LTZ, _) => "DB_TYPE_TIMESTAMP_LTZ",
420 (ORA_TYPE_NUM_TIMESTAMP_TZ, _) => "DB_TYPE_TIMESTAMP_TZ",
421 (ORA_TYPE_NUM_INTERVAL_DS, _) => "DB_TYPE_INTERVAL_DS",
422 (ORA_TYPE_NUM_INTERVAL_YM, _) => "DB_TYPE_INTERVAL_YM",
423 (ORA_TYPE_NUM_BOOLEAN, _) => "DB_TYPE_BOOLEAN",
424 (ORA_TYPE_NUM_VECTOR, _) => "DB_TYPE_VECTOR",
425 (ORA_TYPE_NUM_JSON, _) => "DB_TYPE_JSON",
426 _ => "DB_TYPE_VARCHAR",
427 }
428}
429
430pub fn public_dbtype_size_info(dbtype_name: &str) -> (u32, u32) {
434 match dbtype_name {
435 "DB_TYPE_BFILE" => (0, 4000),
436 "DB_TYPE_BINARY_DOUBLE" => (0, ORA_TYPE_SIZE_BINARY_DOUBLE),
437 "DB_TYPE_BINARY_FLOAT" => (0, ORA_TYPE_SIZE_BINARY_FLOAT),
438 "DB_TYPE_BINARY_INTEGER" | "DB_TYPE_NUMBER" => (0, ORA_TYPE_SIZE_NUMBER),
439 "DB_TYPE_BLOB" | "DB_TYPE_CLOB" | "DB_TYPE_NCLOB" => (0, 112),
440 "DB_TYPE_BOOLEAN" => (0, ORA_TYPE_SIZE_BOOLEAN),
441 "DB_TYPE_CHAR" | "DB_TYPE_NCHAR" => (2000, 4),
442 "DB_TYPE_CURSOR" => (0, 4),
443 "DB_TYPE_DATE" => (0, ORA_TYPE_SIZE_DATE),
444 "DB_TYPE_INTERVAL_DS" => (0, ORA_TYPE_SIZE_INTERVAL_DS),
445 "DB_TYPE_INTERVAL_YM" => (0, 5),
446 "DB_TYPE_LONG" | "DB_TYPE_LONG_NVARCHAR" | "DB_TYPE_LONG_RAW" => (0, TNS_MAX_LONG_LENGTH),
447 "DB_TYPE_NVARCHAR" | "DB_TYPE_VARCHAR" => (4000, 4),
448 "DB_TYPE_RAW" => (4000, 1),
449 "DB_TYPE_ROWID" => (0, ORA_TYPE_SIZE_ROWID),
450 "DB_TYPE_TIMESTAMP" | "DB_TYPE_TIMESTAMP_LTZ" => (0, ORA_TYPE_SIZE_TIMESTAMP),
451 "DB_TYPE_TIMESTAMP_TZ" => (0, ORA_TYPE_SIZE_TIMESTAMP_TZ),
452 "DB_TYPE_JSON" | "DB_TYPE_VECTOR" => (0, 1),
453 _ => (0, 0),
454 }
455}
456
457pub fn check_fetch_conversion(
466 source: &ColumnMetadata,
467 to_ora_type_num: u8,
468 to_csfrm: u8,
469) -> Option<ColumnMetadata> {
470 const CHAR_TYPES: [u8; 3] = [ORA_TYPE_NUM_CHAR, ORA_TYPE_NUM_LONG, ORA_TYPE_NUM_VARCHAR];
471 let from = source.ora_type_num;
472 let to = to_ora_type_num;
473 if from == to {
474 return Some(source.clone());
475 }
476 let supported = match from {
477 ORA_TYPE_NUM_BINARY_DOUBLE | ORA_TYPE_NUM_BINARY_FLOAT => {
478 matches!(
479 to,
480 ORA_TYPE_NUM_BINARY_INTEGER
481 | ORA_TYPE_NUM_BINARY_DOUBLE
482 | ORA_TYPE_NUM_BINARY_FLOAT
483 | ORA_TYPE_NUM_NUMBER
484 ) || CHAR_TYPES.contains(&to)
485 }
486 ORA_TYPE_NUM_BINARY_INTEGER => to == ORA_TYPE_NUM_NUMBER || CHAR_TYPES.contains(&to),
487 ORA_TYPE_NUM_BLOB => {
488 if matches!(to, ORA_TYPE_NUM_RAW | ORA_TYPE_NUM_LONG_RAW) {
489 let mut metadata = source.clone();
490 metadata.ora_type_num = ORA_TYPE_NUM_LONG_RAW;
491 metadata.csfrm = 0;
492 metadata.buffer_size = TNS_MAX_LONG_LENGTH;
493 metadata.max_size = 0;
494 return Some(metadata);
495 }
496 false
497 }
498 ORA_TYPE_NUM_CHAR | ORA_TYPE_NUM_LONG | ORA_TYPE_NUM_VARCHAR => {
499 matches!(
500 to,
501 ORA_TYPE_NUM_BINARY_DOUBLE
502 | ORA_TYPE_NUM_BINARY_FLOAT
503 | ORA_TYPE_NUM_NUMBER
504 | ORA_TYPE_NUM_BINARY_INTEGER
505 ) || CHAR_TYPES.contains(&to)
506 }
507 ORA_TYPE_NUM_CLOB => {
508 if CHAR_TYPES.contains(&to) {
509 let mut metadata = source.clone();
510 metadata.ora_type_num = ORA_TYPE_NUM_LONG;
511 metadata.buffer_size = TNS_MAX_LONG_LENGTH;
512 metadata.max_size = 0;
513 return Some(metadata);
514 }
515 false
516 }
517 ORA_TYPE_NUM_DATE
518 | ORA_TYPE_NUM_TIMESTAMP
519 | ORA_TYPE_NUM_TIMESTAMP_LTZ
520 | ORA_TYPE_NUM_TIMESTAMP_TZ => {
521 matches!(
522 to,
523 ORA_TYPE_NUM_DATE
524 | ORA_TYPE_NUM_TIMESTAMP
525 | ORA_TYPE_NUM_TIMESTAMP_LTZ
526 | ORA_TYPE_NUM_TIMESTAMP_TZ
527 ) || CHAR_TYPES.contains(&to)
528 }
529 ORA_TYPE_NUM_INTERVAL_DS | ORA_TYPE_NUM_INTERVAL_YM | ORA_TYPE_NUM_ROWID => {
530 CHAR_TYPES.contains(&to)
531 }
532 ORA_TYPE_NUM_NUMBER => {
533 matches!(
534 to,
535 ORA_TYPE_NUM_BINARY_INTEGER
536 | ORA_TYPE_NUM_BINARY_DOUBLE
537 | ORA_TYPE_NUM_BINARY_FLOAT
538 ) || CHAR_TYPES.contains(&to)
539 }
540 ORA_TYPE_NUM_JSON => {
541 if matches!(to, ORA_TYPE_NUM_CHAR | ORA_TYPE_NUM_VARCHAR) {
559 let mut metadata = source.clone();
560 metadata.ora_type_num = ORA_TYPE_NUM_VARCHAR;
561 metadata.csfrm = CS_FORM_IMPLICIT;
562 metadata.buffer_size = TNS_MAX_LONG_LENGTH;
563 metadata.max_size = 0;
564 return Some(metadata);
565 }
566 if to == ORA_TYPE_NUM_RAW {
570 let mut metadata = source.clone();
571 metadata.ora_type_num = ORA_TYPE_NUM_RAW;
572 metadata.csfrm = 0;
573 metadata.buffer_size = TNS_MAX_LONG_LENGTH;
574 metadata.max_size = 0;
575 return Some(metadata);
576 }
577 false
578 }
579 ORA_TYPE_NUM_VECTOR => {
580 if CHAR_TYPES.contains(&to) {
584 let mut metadata = source.clone();
585 metadata.ora_type_num = ORA_TYPE_NUM_LONG;
586 metadata.csfrm = CS_FORM_IMPLICIT;
587 metadata.buffer_size = TNS_MAX_LONG_LENGTH;
588 metadata.max_size = 0;
589 return Some(metadata);
590 }
591 if to == ORA_TYPE_NUM_CLOB {
592 let mut metadata = source.clone();
593 metadata.ora_type_num = ORA_TYPE_NUM_CLOB;
594 return Some(metadata);
595 }
596 false
597 }
598 _ => false,
599 };
600 let _ = to_csfrm;
601 if supported {
602 Some(source.clone())
603 } else {
604 None
605 }
606}
607
608#[derive(Clone, Copy, Debug, Eq, PartialEq)]
609enum BuiltinDescriptorFamily {
610 Timestamp,
611 TimestampTz,
612 TimestampLtz,
613 IntervalDs,
614 IntervalYm,
615}
616
617fn builtin_descriptor_family(type_name: &str) -> Option<BuiltinDescriptorFamily> {
624 if let Some(timestamp_suffix) = timestamp_descriptor_suffix(type_name) {
625 let mut words = timestamp_suffix.split_ascii_whitespace();
626 let second = words.next();
627 return match second {
628 None => Some(BuiltinDescriptorFamily::Timestamp),
629 Some(with) if with.eq_ignore_ascii_case("WITH") => match words.next() {
630 Some(tz) if tz.eq_ignore_ascii_case("TZ") && words.next().is_none() => {
631 Some(BuiltinDescriptorFamily::TimestampTz)
632 }
633 Some(time) if time.eq_ignore_ascii_case("TIME") => (words
634 .next()
635 .is_some_and(|zone| zone.eq_ignore_ascii_case("ZONE"))
636 && words.next().is_none())
637 .then_some(BuiltinDescriptorFamily::TimestampTz),
638 Some(local) if local.eq_ignore_ascii_case("LOCAL") => match words.next() {
639 Some(tz) if tz.eq_ignore_ascii_case("TZ") && words.next().is_none() => {
640 Some(BuiltinDescriptorFamily::TimestampLtz)
641 }
642 Some(time) if time.eq_ignore_ascii_case("TIME") => (words
643 .next()
644 .is_some_and(|zone| zone.eq_ignore_ascii_case("ZONE"))
645 && words.next().is_none())
646 .then_some(BuiltinDescriptorFamily::TimestampLtz),
647 _ => None,
648 },
649 _ => None,
650 },
651 _ => None,
652 };
653 }
654
655 let mut words = type_name.split_ascii_whitespace();
656 let first = words.next()?;
657 if first.eq_ignore_ascii_case("INTERVAL") {
658 return match (words.next(), words.next(), words.next(), words.next()) {
659 (Some(day), Some(to), Some(second), None)
660 if day.eq_ignore_ascii_case("DAY")
661 && to.eq_ignore_ascii_case("TO")
662 && second.eq_ignore_ascii_case("SECOND") =>
663 {
664 Some(BuiltinDescriptorFamily::IntervalDs)
665 }
666 (Some(year), Some(to), Some(month), None)
667 if year.eq_ignore_ascii_case("YEAR")
668 && to.eq_ignore_ascii_case("TO")
669 && month.eq_ignore_ascii_case("MONTH") =>
670 {
671 Some(BuiltinDescriptorFamily::IntervalYm)
672 }
673 _ => None,
674 };
675 }
676
677 None
678}
679
680fn timestamp_descriptor_suffix(type_name: &str) -> Option<&str> {
683 let type_name = type_name.trim_matches(|ch: char| ch.is_ascii_whitespace());
684 let (timestamp, mut suffix) = type_name
685 .get(.."TIMESTAMP".len())
686 .zip(type_name.get("TIMESTAMP".len()..))?;
687 if !timestamp.eq_ignore_ascii_case("TIMESTAMP") {
688 return None;
689 }
690
691 let has_word_separator = suffix
692 .as_bytes()
693 .first()
694 .is_some_and(|byte| byte.is_ascii_whitespace());
695 suffix = suffix.trim_start_matches(|ch: char| ch.is_ascii_whitespace());
696 if suffix.is_empty() {
697 return Some("");
698 }
699 if !suffix.starts_with('(') {
700 return has_word_separator.then_some(suffix);
701 }
702 suffix = &suffix[1..];
703 suffix = suffix.trim_start_matches(|ch: char| ch.is_ascii_whitespace());
704 let precision = *suffix.as_bytes().first()?;
705 if !precision.is_ascii_digit() {
706 return None;
707 }
708 suffix = suffix[1..].trim_start_matches(|ch: char| ch.is_ascii_whitespace());
709 suffix = suffix.strip_prefix(')')?;
710 if suffix.is_empty() {
711 return Some("");
712 }
713 suffix
714 .as_bytes()
715 .first()
716 .is_some_and(|byte| byte.is_ascii_whitespace())
717 .then(|| suffix.trim_matches(|ch: char| ch.is_ascii_whitespace()))
718}
719
720pub fn public_dbtype_name_from_oracle_type_name(type_name: &str) -> &'static str {
721 if let Some(family) = builtin_descriptor_family(type_name) {
722 return match family {
723 BuiltinDescriptorFamily::Timestamp => "DB_TYPE_TIMESTAMP",
724 BuiltinDescriptorFamily::TimestampTz => "DB_TYPE_TIMESTAMP_TZ",
725 BuiltinDescriptorFamily::TimestampLtz => "DB_TYPE_TIMESTAMP_LTZ",
726 BuiltinDescriptorFamily::IntervalDs => "DB_TYPE_INTERVAL_DS",
727 BuiltinDescriptorFamily::IntervalYm => "DB_TYPE_INTERVAL_YM",
728 };
729 }
730
731 let upper = type_name.to_ascii_uppercase();
732 match upper.as_str() {
733 "CHAR" => "DB_TYPE_CHAR",
734 "NCHAR" => "DB_TYPE_NCHAR",
735 "VARCHAR2" | "VARCHAR" => "DB_TYPE_VARCHAR",
736 "NVARCHAR2" | "NVARCHAR" => "DB_TYPE_NVARCHAR",
737 "RAW" => "DB_TYPE_RAW",
738 "DATE" => "DB_TYPE_DATE",
739 "CLOB" => "DB_TYPE_CLOB",
740 "NCLOB" => "DB_TYPE_NCLOB",
741 "BLOB" => "DB_TYPE_BLOB",
742 "XMLTYPE" => "DB_TYPE_XMLTYPE",
743 "BINARY_FLOAT" => "DB_TYPE_BINARY_FLOAT",
744 "BINARY_DOUBLE" => "DB_TYPE_BINARY_DOUBLE",
745 "NUMBER" | "INTEGER" | "SMALLINT" | "REAL" | "DOUBLE PRECISION" | "FLOAT" => {
746 "DB_TYPE_NUMBER"
747 }
748 "BOOLEAN" | "PL/SQL BOOLEAN" => "DB_TYPE_BOOLEAN",
753 "BINARY_INTEGER" | "PLS_INTEGER" | "PL/SQL BINARY INTEGER" | "PL/SQL PLS INTEGER" => {
754 "DB_TYPE_BINARY_INTEGER"
755 }
756 "LONG" => "DB_TYPE_LONG",
757 "LONG RAW" => "DB_TYPE_LONG_RAW",
758 "ROWID" => "DB_TYPE_ROWID",
759 "UROWID" => "DB_TYPE_UROWID",
760 "BFILE" => "DB_TYPE_BFILE",
761 "JSON" => "DB_TYPE_JSON",
762 "VECTOR" => "DB_TYPE_VECTOR",
763 _ => "DB_TYPE_OBJECT",
766 }
767}
768
769pub fn dbobject_attr_precision_scale(
770 type_name: &str,
771 precision: Option<i8>,
772 scale: Option<i8>,
773) -> (i8, i8) {
774 if let Some(family) = builtin_descriptor_family(type_name) {
775 return match family {
776 BuiltinDescriptorFamily::Timestamp
777 | BuiltinDescriptorFamily::TimestampTz
778 | BuiltinDescriptorFamily::TimestampLtz => (precision.unwrap_or(0), scale.unwrap_or(6)),
779 BuiltinDescriptorFamily::IntervalDs => (precision.unwrap_or(2), scale.unwrap_or(6)),
780 BuiltinDescriptorFamily::IntervalYm => (precision.unwrap_or(2), scale.unwrap_or(0)),
781 };
782 }
783
784 match type_name.to_ascii_uppercase().as_str() {
785 "NUMBER" => (
786 precision.unwrap_or(if scale == Some(0) { 38 } else { 0 }),
787 scale.unwrap_or(-127),
788 ),
789 "INTEGER" | "SMALLINT" => (precision.unwrap_or(38), scale.unwrap_or(0)),
790 "REAL" => (precision.unwrap_or(63), scale.unwrap_or(-127)),
791 "DOUBLE PRECISION" | "FLOAT" => (precision.unwrap_or(126), scale.unwrap_or(-127)),
792 _ => (0, 0),
798 }
799}
800
801pub fn dbobject_attr_public_precision_scale(
810 type_name: &str,
811 precision: Option<i8>,
812 scale: Option<i8>,
813) -> (i8, i8) {
814 let precision_scale = dbobject_attr_precision_scale(type_name, precision, scale);
815 if builtin_descriptor_family(type_name).is_some() {
816 (0, 0)
817 } else {
818 precision_scale
819 }
820}
821
822pub fn dbobject_attr_max_size(type_name: &str, length: Option<u32>) -> u32 {
823 let length = length.unwrap_or(0);
824 match type_name.to_ascii_uppercase().as_str() {
825 "NCHAR" | "NVARCHAR2" | "NVARCHAR" => length.saturating_mul(2),
826 _ => length,
827 }
828}
829
830pub fn dbobject_rowtype_attr_max_size(
831 type_name: &str,
832 data_length: Option<u32>,
833 char_length: Option<u32>,
834) -> u32 {
835 match type_name.to_ascii_uppercase().as_str() {
836 "CHAR" | "VARCHAR" | "VARCHAR2" | "RAW" => data_length.unwrap_or(0),
837 "NCHAR" | "NVARCHAR" | "NVARCHAR2" => dbobject_attr_max_size(
838 type_name,
839 char_length.filter(|length| *length > 0).or(data_length),
840 ),
841 _ => 0,
842 }
843}
844
845pub fn public_dbtype_name_from_bind(value: &BindValue) -> &'static str {
846 match value {
847 BindValue::TypedNull {
848 ora_type_num,
849 csfrm,
850 ..
851 }
852 | BindValue::Output {
853 ora_type_num,
854 csfrm,
855 ..
856 }
857 | BindValue::ReturnOutput {
858 ora_type_num,
859 csfrm,
860 ..
861 }
862 | BindValue::Array {
863 ora_type_num,
864 csfrm,
865 ..
866 } => public_dbtype_name_from_type_info(*ora_type_num, *csfrm),
867 BindValue::ObjectOutput { .. } | BindValue::ObjectInput { .. } => "DB_TYPE_OBJECT",
868 BindValue::InOut { value, .. } => public_dbtype_name_from_bind(value),
870 BindValue::Text(_) => "DB_TYPE_VARCHAR",
871 BindValue::Raw(_) => "DB_TYPE_RAW",
872 BindValue::Lob {
873 ora_type_num,
874 csfrm,
875 ..
876 } => match (*ora_type_num, *csfrm) {
877 (ORA_TYPE_NUM_BLOB, _) => "DB_TYPE_BLOB",
878 (ORA_TYPE_NUM_CLOB, CS_FORM_NCHAR) => "DB_TYPE_NCLOB",
879 (ORA_TYPE_NUM_CLOB, _) => "DB_TYPE_CLOB",
880 _ => "DB_TYPE_CLOB",
881 },
882 BindValue::Number(_) => "DB_TYPE_NUMBER",
883 BindValue::BinaryInteger(_) => "DB_TYPE_BINARY_INTEGER",
884 BindValue::BinaryDouble(_) => "DB_TYPE_BINARY_DOUBLE",
885 BindValue::BinaryFloat(_) => "DB_TYPE_BINARY_FLOAT",
886 BindValue::Boolean(_) => "DB_TYPE_BOOLEAN",
887 BindValue::IntervalDS { .. } => "DB_TYPE_INTERVAL_DS",
888 BindValue::IntervalYM { .. } => "DB_TYPE_INTERVAL_YM",
889 BindValue::DateTime { .. } => "DB_TYPE_DATE",
890 BindValue::Timestamp { ora_type_num, .. } => match *ora_type_num {
891 ORA_TYPE_NUM_TIMESTAMP_LTZ => "DB_TYPE_TIMESTAMP_LTZ",
892 ORA_TYPE_NUM_TIMESTAMP_TZ => "DB_TYPE_TIMESTAMP_TZ",
893 _ => "DB_TYPE_TIMESTAMP",
894 },
895 BindValue::TimestampTz { .. } => "DB_TYPE_TIMESTAMP_TZ",
896 BindValue::Vector(_) => "DB_TYPE_VECTOR",
897 BindValue::Json(_) => "DB_TYPE_JSON",
898 BindValue::Cursor { .. } => "DB_TYPE_CURSOR",
899 BindValue::Null => "DB_TYPE_VARCHAR",
900 }
901}
902
903pub fn bind_template_from_type_name(type_name: &str, size: u32) -> BindValue {
904 let text_buffer_size = if size == 0 { 4000 } else { size.max(1) };
905 let nchar_buffer_size = text_buffer_size.saturating_mul(4);
906 match type_name {
907 "NUMBER" | "DB_TYPE_NUMBER" | "int" | "float" | "Decimal" => BindValue::TypedNull {
908 ora_type_num: ORA_TYPE_NUM_NUMBER,
909 csfrm: 0,
910 buffer_size: ORA_TYPE_SIZE_NUMBER,
911 },
912 "NATIVE_INT" | "DB_TYPE_BINARY_INTEGER" => BindValue::TypedNull {
913 ora_type_num: ORA_TYPE_NUM_BINARY_INTEGER,
914 csfrm: 0,
915 buffer_size: ORA_TYPE_SIZE_NUMBER,
916 },
917 "NATIVE_FLOAT" | "DB_TYPE_BINARY_DOUBLE" => BindValue::TypedNull {
918 ora_type_num: ORA_TYPE_NUM_BINARY_DOUBLE,
919 csfrm: 0,
920 buffer_size: ORA_TYPE_SIZE_BINARY_DOUBLE,
921 },
922 "DB_TYPE_BINARY_FLOAT" | "BINARY_FLOAT" => BindValue::TypedNull {
923 ora_type_num: ORA_TYPE_NUM_BINARY_FLOAT,
924 csfrm: 0,
925 buffer_size: ORA_TYPE_SIZE_BINARY_FLOAT,
926 },
927 "DB_TYPE_BOOLEAN" | "BOOLEAN" | "bool" => BindValue::TypedNull {
928 ora_type_num: ORA_TYPE_NUM_BOOLEAN,
929 csfrm: 0,
930 buffer_size: ORA_TYPE_SIZE_BOOLEAN,
931 },
932 "DB_TYPE_INTERVAL_DS" | "INTERVAL DAY TO SECOND" | "timedelta" => BindValue::TypedNull {
933 ora_type_num: ORA_TYPE_NUM_INTERVAL_DS,
934 csfrm: 0,
935 buffer_size: ORA_TYPE_SIZE_INTERVAL_DS,
936 },
937 "DB_TYPE_INTERVAL_YM" | "INTERVAL YEAR TO MONTH" | "IntervalYM" => BindValue::TypedNull {
938 ora_type_num: ORA_TYPE_NUM_INTERVAL_YM,
939 csfrm: 0,
940 buffer_size: ORA_TYPE_SIZE_INTERVAL_YM,
941 },
942 "STRING" | "DB_TYPE_VARCHAR" | "DB_TYPE_CHAR" | "str" => BindValue::TypedNull {
943 ora_type_num: ORA_TYPE_NUM_VARCHAR,
944 csfrm: CS_FORM_IMPLICIT,
945 buffer_size: text_buffer_size,
946 },
947 "DB_TYPE_NCHAR" | "DB_TYPE_NVARCHAR" => BindValue::TypedNull {
948 ora_type_num: ORA_TYPE_NUM_VARCHAR,
949 csfrm: CS_FORM_NCHAR,
950 buffer_size: nchar_buffer_size,
951 },
952 "DB_TYPE_CLOB" | "CLOB" => BindValue::TypedNull {
953 ora_type_num: ORA_TYPE_NUM_LONG,
954 csfrm: CS_FORM_IMPLICIT,
955 buffer_size: TNS_MAX_LONG_LENGTH,
956 },
957 "DB_TYPE_NCLOB" | "NCLOB" => BindValue::TypedNull {
958 ora_type_num: ORA_TYPE_NUM_LONG,
959 csfrm: CS_FORM_NCHAR,
960 buffer_size: TNS_MAX_LONG_LENGTH,
961 },
962 "DB_TYPE_BLOB" | "BLOB" => BindValue::TypedNull {
963 ora_type_num: ORA_TYPE_NUM_LONG_RAW,
964 csfrm: 0,
965 buffer_size: TNS_MAX_LONG_LENGTH,
966 },
967 "DB_TYPE_LONG" | "LONG" | "LONG_STRING" => BindValue::TypedNull {
968 ora_type_num: ORA_TYPE_NUM_LONG,
969 csfrm: CS_FORM_IMPLICIT,
970 buffer_size: TNS_MAX_LONG_LENGTH,
971 },
972 "DB_TYPE_LONG_NVARCHAR" | "LONG NVARCHAR" => BindValue::TypedNull {
973 ora_type_num: ORA_TYPE_NUM_LONG,
974 csfrm: CS_FORM_NCHAR,
975 buffer_size: TNS_MAX_LONG_LENGTH,
976 },
977 "DB_TYPE_LONG_RAW" | "LONG RAW" | "LONG_BINARY" => BindValue::TypedNull {
978 ora_type_num: ORA_TYPE_NUM_LONG_RAW,
979 csfrm: 0,
980 buffer_size: TNS_MAX_LONG_LENGTH,
981 },
982 "DB_TYPE_RAW" | "BINARY" | "bytes" => BindValue::TypedNull {
983 ora_type_num: ORA_TYPE_NUM_RAW,
984 csfrm: 0,
985 buffer_size: size.max(1).max(4000),
986 },
987 "ROWID" | "DB_TYPE_ROWID" | "DB_TYPE_UROWID" => BindValue::TypedNull {
988 ora_type_num: ORA_TYPE_NUM_VARCHAR,
989 csfrm: CS_FORM_IMPLICIT,
990 buffer_size: 5267,
991 },
992 "DATETIME" | "DB_TYPE_DATE" | "date" | "datetime" => BindValue::TypedNull {
993 ora_type_num: ORA_TYPE_NUM_DATE,
994 csfrm: 0,
995 buffer_size: ORA_TYPE_SIZE_DATE,
996 },
997 "DB_TYPE_TIMESTAMP" | "TIMESTAMP" => BindValue::TypedNull {
998 ora_type_num: ORA_TYPE_NUM_TIMESTAMP,
999 csfrm: 0,
1000 buffer_size: ORA_TYPE_SIZE_TIMESTAMP,
1001 },
1002 "DB_TYPE_TIMESTAMP_LTZ" | "TIMESTAMP WITH LOCAL TIME ZONE" => BindValue::TypedNull {
1003 ora_type_num: ORA_TYPE_NUM_TIMESTAMP_LTZ,
1004 csfrm: 0,
1005 buffer_size: ORA_TYPE_SIZE_TIMESTAMP,
1006 },
1007 "DB_TYPE_TIMESTAMP_TZ" | "TIMESTAMP WITH TIME ZONE" => BindValue::TypedNull {
1008 ora_type_num: ORA_TYPE_NUM_TIMESTAMP_TZ,
1009 csfrm: 0,
1010 buffer_size: ORA_TYPE_SIZE_TIMESTAMP_TZ,
1011 },
1012 "DB_TYPE_CURSOR" | "CURSOR" => cursor_bind_template(),
1013 "DB_TYPE_VECTOR" | "VECTOR" => BindValue::TypedNull {
1014 ora_type_num: ORA_TYPE_NUM_VECTOR,
1015 csfrm: 0,
1016 buffer_size: TNS_VECTOR_MAX_LENGTH,
1017 },
1018 "DB_TYPE_JSON" | "JSON" => BindValue::TypedNull {
1019 ora_type_num: ORA_TYPE_NUM_JSON,
1020 csfrm: 0,
1021 buffer_size: TNS_VECTOR_MAX_LENGTH,
1022 },
1023 _ => BindValue::Null,
1024 }
1025}
1026
1027pub fn dbobject_element_bind_type_info(dbtype_name: &str, max_size: u32) -> BindTypeInfo {
1028 let buffer_size = max_size.max(1);
1029 let (ora_type_num, csfrm, buffer_size) = match dbtype_name {
1030 "DB_TYPE_NUMBER" => (ORA_TYPE_NUM_NUMBER, 0, ORA_TYPE_SIZE_NUMBER),
1031 "DB_TYPE_RAW" | "DB_TYPE_BLOB" => (ORA_TYPE_NUM_RAW, 0, buffer_size.max(4000)),
1032 "DB_TYPE_NCHAR" | "DB_TYPE_NVARCHAR" | "DB_TYPE_NCLOB" => {
1033 (ORA_TYPE_NUM_VARCHAR, CS_FORM_NCHAR, buffer_size.max(4000))
1034 }
1035 "DB_TYPE_DATE" => (ORA_TYPE_NUM_DATE, 0, ORA_TYPE_SIZE_DATE),
1036 "DB_TYPE_TIMESTAMP" => (ORA_TYPE_NUM_TIMESTAMP, 0, ORA_TYPE_SIZE_TIMESTAMP),
1037 "DB_TYPE_TIMESTAMP_LTZ" => (ORA_TYPE_NUM_TIMESTAMP_LTZ, 0, ORA_TYPE_SIZE_TIMESTAMP),
1038 "DB_TYPE_TIMESTAMP_TZ" => (ORA_TYPE_NUM_TIMESTAMP_TZ, 0, ORA_TYPE_SIZE_TIMESTAMP_TZ),
1039 _ => (
1040 ORA_TYPE_NUM_VARCHAR,
1041 CS_FORM_IMPLICIT,
1042 buffer_size.max(4000),
1043 ),
1044 };
1045 BindTypeInfo {
1046 ora_type_num,
1047 csfrm,
1048 buffer_size,
1049 }
1050}
1051
1052pub(crate) fn public_dbtype_name_from_type_info(ora_type_num: u8, csfrm: u8) -> &'static str {
1053 match (ora_type_num, csfrm) {
1054 (ORA_TYPE_NUM_BINARY_DOUBLE, _) => "DB_TYPE_BINARY_DOUBLE",
1055 (ORA_TYPE_NUM_BINARY_FLOAT, _) => "DB_TYPE_BINARY_FLOAT",
1056 (ORA_TYPE_NUM_INTERVAL_DS, _) => "DB_TYPE_INTERVAL_DS",
1057 (ORA_TYPE_NUM_INTERVAL_YM, _) => "DB_TYPE_INTERVAL_YM",
1058 (ORA_TYPE_NUM_BOOLEAN, _) => "DB_TYPE_BOOLEAN",
1059 (ORA_TYPE_NUM_BINARY_INTEGER, _) => "DB_TYPE_BINARY_INTEGER",
1060 (ORA_TYPE_NUM_NUMBER, _) => "DB_TYPE_NUMBER",
1061 (ORA_TYPE_NUM_CHAR, CS_FORM_NCHAR) | (ORA_TYPE_NUM_VARCHAR, CS_FORM_NCHAR) => {
1062 "DB_TYPE_NVARCHAR"
1063 }
1064 (ORA_TYPE_NUM_CHAR, _) => "DB_TYPE_CHAR",
1065 (ORA_TYPE_NUM_VARCHAR, _) => "DB_TYPE_VARCHAR",
1066 (ORA_TYPE_NUM_LONG, CS_FORM_NCHAR) => "DB_TYPE_LONG_NVARCHAR",
1067 (ORA_TYPE_NUM_LONG, _) => "DB_TYPE_LONG",
1068 (ORA_TYPE_NUM_LONG_RAW, _) => "DB_TYPE_LONG_RAW",
1069 (ORA_TYPE_NUM_RAW, _) => "DB_TYPE_RAW",
1070 (ORA_TYPE_NUM_DATE, _) => "DB_TYPE_DATE",
1071 (ORA_TYPE_NUM_TIMESTAMP, _) => "DB_TYPE_TIMESTAMP",
1072 (ORA_TYPE_NUM_TIMESTAMP_LTZ, _) => "DB_TYPE_TIMESTAMP_LTZ",
1073 (ORA_TYPE_NUM_TIMESTAMP_TZ, _) => "DB_TYPE_TIMESTAMP_TZ",
1074 (ORA_TYPE_NUM_CURSOR, _) => "DB_TYPE_CURSOR",
1075 (ORA_TYPE_NUM_OBJECT, _) => "DB_TYPE_OBJECT",
1076 (ORA_TYPE_NUM_VECTOR, _) => "DB_TYPE_VECTOR",
1077 (ORA_TYPE_NUM_JSON, _) => "DB_TYPE_JSON",
1078 _ => "DB_TYPE_VARCHAR",
1079 }
1080}
1081
1082pub(crate) fn bind_metadata(value: &BindValue) -> (u8, u8, u32) {
1083 bind_value_type_info(value)
1084 .map(|info| (info.ora_type_num, info.csfrm, info.buffer_size))
1085 .unwrap_or((ORA_TYPE_NUM_VARCHAR, CS_FORM_IMPLICIT, 1))
1086}
1087
1088pub(crate) fn write_bind_value(writer: &mut TtcWriter, value: &BindValue, csfrm: u8) -> Result<()> {
1089 match value {
1090 BindValue::TypedNull {
1091 ora_type_num: ORA_TYPE_NUM_CURSOR,
1092 ..
1093 } => {
1094 writer.write_u8(1);
1095 writer.write_u8(0);
1096 Ok(())
1097 }
1098 BindValue::TypedNull {
1103 ora_type_num: ORA_TYPE_NUM_BOOLEAN,
1104 ..
1105 } => {
1106 writer.write_u8(TNS_ESCAPE_CHAR);
1107 writer.write_u8(1);
1108 Ok(())
1109 }
1110 BindValue::Null | BindValue::TypedNull { .. } => {
1111 writer.write_u8(0);
1112 Ok(())
1113 }
1114 BindValue::Output { .. } | BindValue::ReturnOutput { .. } => {
1115 writer.write_u8(0);
1116 Ok(())
1117 }
1118 BindValue::InOut { value, .. } => write_bind_value(writer, value, csfrm),
1123 BindValue::ObjectOutput { .. } => {
1124 writer.write_ub4(0);
1127 writer.write_ub4(0);
1128 writer.write_ub4(0);
1129 writer.write_ub2(0);
1130 writer.write_ub4(0);
1131 writer.write_ub4(TNS_OBJ_TOP_LEVEL);
1132 Ok(())
1133 }
1134 BindValue::ObjectInput { oid, image, .. } => write_dbobject_bind(writer, oid, image),
1135 BindValue::Text(value) => {
1136 if csfrm == CS_FORM_NCHAR {
1141 let bytes = encode_text_value(value, csfrm);
1142 writer.write_bytes_with_length(&bytes)
1143 } else {
1144 writer.write_bytes_with_length(value.as_bytes())
1145 }
1146 }
1147 BindValue::Raw(value) => writer.write_bytes_with_length(value),
1148 BindValue::Lob { locator, .. } => writer.write_bytes_with_two_lengths(Some(locator)),
1149 BindValue::Number(value) | BindValue::BinaryInteger(value) => {
1150 let bytes = encode_number_text(value)?;
1151 writer.write_bytes_with_length(&bytes)
1152 }
1153 BindValue::Boolean(value) => {
1156 let bytes: &[u8] = if *value { &[1, 1] } else { &[0] };
1157 writer.write_bytes_with_length(bytes)
1158 }
1159 BindValue::BinaryDouble(value) => {
1160 let bytes = encode_binary_double(*value);
1161 writer.write_bytes_with_length(&bytes)
1162 }
1163 BindValue::BinaryFloat(value) => {
1164 let bytes = encode_binary_float(*value as f32);
1165 writer.write_bytes_with_length(&bytes)
1166 }
1167 BindValue::IntervalDS {
1168 days,
1169 seconds,
1170 microseconds,
1171 } => {
1172 let nanoseconds = microseconds
1173 .checked_mul(1000)
1174 .ok_or(ProtocolError::TtcDecode(
1175 "INTERVAL DS fractional seconds out of range",
1176 ))?;
1177 let bytes = encode_interval_ds(*days, *seconds, nanoseconds)?;
1178 writer.write_bytes_with_length(&bytes)
1179 }
1180 BindValue::IntervalYM { years, months } => {
1181 let bytes = encode_interval_ym(*years, *months)?;
1182 writer.write_bytes_with_length(&bytes)
1183 }
1184 BindValue::DateTime {
1185 year,
1186 month,
1187 day,
1188 hour,
1189 minute,
1190 second,
1191 } => {
1192 let bytes = encode_oracle_date(*year, *month, *day, *hour, *minute, *second)?;
1193 writer.write_bytes_with_length(&bytes)
1194 }
1195 BindValue::Timestamp {
1196 year,
1197 month,
1198 day,
1199 hour,
1200 minute,
1201 second,
1202 nanosecond,
1203 ora_type_num,
1204 } => {
1205 let bytes = if matches!(*ora_type_num, ORA_TYPE_NUM_TIMESTAMP_TZ) {
1206 encode_oracle_timestamp_tz(
1207 *year,
1208 *month,
1209 *day,
1210 *hour,
1211 *minute,
1212 *second,
1213 *nanosecond,
1214 )?
1215 } else {
1216 encode_oracle_timestamp(*year, *month, *day, *hour, *minute, *second, *nanosecond)?
1217 };
1218 writer.write_bytes_with_length(&bytes)
1219 }
1220 BindValue::TimestampTz {
1221 year,
1222 month,
1223 day,
1224 hour,
1225 minute,
1226 second,
1227 nanosecond,
1228 offset_minutes,
1229 } => {
1230 let bytes = encode_oracle_timestamp_tz_with_offset(
1231 *year,
1232 *month,
1233 *day,
1234 *hour,
1235 *minute,
1236 *second,
1237 *nanosecond,
1238 *offset_minutes,
1239 )?;
1240 writer.write_bytes_with_length(&bytes)
1241 }
1242 BindValue::Array {
1243 values,
1244 csfrm: array_csfrm,
1245 ..
1246 } => {
1247 writer.write_ub4(u32::try_from(values.len()).map_err(|_| {
1248 ProtocolError::InvalidPacketLength {
1249 length: values.len(),
1250 minimum: 0,
1251 }
1252 })?);
1253 for value in values {
1254 match value {
1255 Some(value) => write_bind_value(writer, value, *array_csfrm)?,
1256 None => writer.write_u8(0),
1257 }
1258 }
1259 Ok(())
1260 }
1261 BindValue::Vector(vector) => {
1264 let image = crate::vector::encode_vector_checked(vector)?;
1265 crate::vector::write_vector_image(writer, &image)
1266 }
1267 BindValue::Json(image) => crate::vector::write_vector_image(writer, image),
1270 BindValue::Cursor { cursor_id } => {
1271 if *cursor_id == 0 {
1272 writer.write_u8(1);
1273 writer.write_u8(0);
1274 } else {
1275 writer.write_ub4(1);
1276 writer.write_ub4(*cursor_id);
1277 }
1278 Ok(())
1279 }
1280 }
1281}
1282
1283pub(crate) fn encode_text_value(value: &str, csfrm: u8) -> Vec<u8> {
1284 if csfrm == CS_FORM_NCHAR {
1285 let mut bytes = Vec::with_capacity(value.len().saturating_mul(2));
1286 for unit in value.encode_utf16() {
1287 bytes.extend_from_slice(&unit.to_be_bytes());
1288 }
1289 bytes
1290 } else {
1291 value.as_bytes().to_vec()
1292 }
1293}
1294
1295#[cfg(test)]
1296mod bind_tests {
1297 use super::*;
1298
1299 const ORACLE_23AI_DESCRIPTOR_FIXTURE: &str =
1300 include_str!("../../tests/golden/oracle_23ai_all_type_attrs_tstz.txt");
1301
1302 fn written_bytes(value: &BindValue, csfrm: u8) -> Vec<u8> {
1303 let mut writer = TtcWriter::new();
1304 write_bind_value(&mut writer, value, csfrm).expect("write bind value");
1305 writer.into_bytes()
1306 }
1307
1308 #[test]
1309 fn captured_23ai_descriptor_fixture_normalizes_precision_scale() {
1310 let mut captured_rows = 0;
1311 for line in ORACLE_23AI_DESCRIPTOR_FIXTURE
1312 .lines()
1313 .filter(|line| line.starts_with("ATTR|"))
1314 {
1315 captured_rows += 1;
1316 let fields: Vec<_> = line.split('|').collect();
1317 assert_eq!(fields.len(), 8, "fixture row shape: {line}");
1318 assert_eq!(fields[1], "SYS", "fixture owner: {line}");
1319 assert_eq!(fields[2], "C23G_TSTZ_TYPE", "fixture type: {line}");
1320
1321 let precision = (fields[6] != "NULL")
1322 .then(|| fields[6].parse::<i8>().expect("fixture precision is an i8"));
1323 let scale = (fields[7] != "NULL")
1324 .then(|| fields[7].parse::<i8>().expect("fixture scale is an i8"));
1325 let (expected_dbtype, expected_precision_scale) = match fields[4] {
1326 "TS_DEFAULT" => ("DB_TYPE_TIMESTAMP", (0, 6)),
1327 "TS_3" => ("DB_TYPE_TIMESTAMP", (0, 3)),
1328 "TSTZ_DEFAULT" => ("DB_TYPE_TIMESTAMP_TZ", (0, 6)),
1329 "TSTZ_3" => ("DB_TYPE_TIMESTAMP_TZ", (0, 3)),
1330 "TSLTZ_DEFAULT" => ("DB_TYPE_TIMESTAMP_LTZ", (0, 6)),
1331 "TSLTZ_3" => ("DB_TYPE_TIMESTAMP_LTZ", (0, 3)),
1332 "IDS_DEFAULT" => ("DB_TYPE_INTERVAL_DS", (2, 6)),
1333 "IDS_9_3" => ("DB_TYPE_INTERVAL_DS", (9, 3)),
1334 "IYM_DEFAULT" => ("DB_TYPE_INTERVAL_YM", (2, 0)),
1335 "IYM_9" => ("DB_TYPE_INTERVAL_YM", (9, 0)),
1336 name => panic!("unexpected captured attribute {name}"),
1337 };
1338
1339 assert_eq!(
1340 public_dbtype_name_from_oracle_type_name(fields[5]),
1341 expected_dbtype,
1342 "captured raw type name: {line}"
1343 );
1344 assert_eq!(
1345 dbobject_attr_precision_scale(fields[5], precision, scale),
1346 expected_precision_scale,
1347 "captured raw precision/scale: {line}"
1348 );
1349 }
1350 assert_eq!(captured_rows, 10, "captured descriptor row count");
1351 }
1352
1353 #[test]
1354 fn descriptor_normalizer_folds_only_builtin_grammar() {
1355 assert_eq!(
1356 builtin_descriptor_family(" \ttImEsTaMp WITH\nLOCAL\tTZ "),
1357 Some(BuiltinDescriptorFamily::TimestampLtz)
1358 );
1359 assert_eq!(
1360 builtin_descriptor_family(" timestamp ( 6 ) with time zone "),
1361 Some(BuiltinDescriptorFamily::TimestampTz)
1362 );
1363 assert_eq!(
1364 public_dbtype_name_from_oracle_type_name("TIMESTAMP(6)"),
1365 "DB_TYPE_TIMESTAMP"
1366 );
1367 assert_eq!(
1368 builtin_descriptor_family("INTERVAL YEAR\tTO MONTH"),
1369 Some(BuiltinDescriptorFamily::IntervalYm)
1370 );
1371 for name in [
1372 "TIMESTAMP_AUDIT",
1373 "TIMESTAMP(10)",
1374 "TIMESTAMP(6)WITH TZ",
1375 "TIMESTAMP WITH TZ EXTRA",
1376 "INTERVAL DAY TO SECOND EXTRA",
1377 ] {
1378 assert_eq!(builtin_descriptor_family(name), None, "{name}");
1379 assert_eq!(
1380 public_dbtype_name_from_oracle_type_name(name),
1381 "DB_TYPE_OBJECT",
1382 "{name} remains an ADT"
1383 );
1384 assert_eq!(dbobject_attr_precision_scale(name, None, None), (0, 0));
1385 }
1386 }
1387
1388 #[test]
1389 fn public_metadata_hides_temporal_descriptor_defaults() {
1390 for name in [
1391 "TIMESTAMP",
1392 "TIMESTAMP(6)",
1393 "TIMESTAMP WITH TZ",
1394 "TIMESTAMP WITH LOCAL TZ",
1395 "INTERVAL DAY TO SECOND",
1396 "INTERVAL YEAR TO MONTH",
1397 ] {
1398 assert_ne!(
1399 dbobject_attr_precision_scale(name, None, None),
1400 (0, 0),
1401 "raw descriptor metadata for {name}"
1402 );
1403 assert_eq!(
1404 dbobject_attr_public_precision_scale(name, None, None),
1405 (0, 0),
1406 "DbObjectAttr exposes None precision/scale for {name}"
1407 );
1408 }
1409 }
1410
1411 #[test]
1412 fn number_descriptor_precision_scale_is_unchanged() {
1413 assert_eq!(
1414 dbobject_attr_precision_scale("NUMBER", None, None),
1415 (0, -127),
1416 "unconstrained NUMBER keeps its existing defaults"
1417 );
1418 assert_eq!(
1419 dbobject_attr_precision_scale("NUMBER", None, Some(0)),
1420 (38, 0),
1421 "scale-zero NUMBER keeps its existing precision default"
1422 );
1423 assert_eq!(
1424 dbobject_attr_precision_scale("NUMBER", Some(9), Some(3)),
1425 (9, 3),
1426 "explicit NUMBER metadata is preserved"
1427 );
1428 }
1429
1430 #[test]
1431 fn other_builtin_descriptor_types_keep_zero_precision_scale() {
1432 for name in [
1433 "CHAR",
1434 "NCHAR",
1435 "VARCHAR2",
1436 "VARCHAR",
1437 "NVARCHAR2",
1438 "NVARCHAR",
1439 "RAW",
1440 "DATE",
1441 "CLOB",
1442 "NCLOB",
1443 "BLOB",
1444 "XMLTYPE",
1445 "BINARY_FLOAT",
1446 "BINARY_DOUBLE",
1447 "BOOLEAN",
1448 "PL/SQL BOOLEAN",
1449 "BINARY_INTEGER",
1450 "PLS_INTEGER",
1451 "PL/SQL BINARY INTEGER",
1452 "PL/SQL PLS INTEGER",
1453 "LONG",
1454 "LONG RAW",
1455 "ROWID",
1456 "UROWID",
1457 "BFILE",
1458 "JSON",
1459 "VECTOR",
1460 ] {
1461 assert_eq!(
1462 dbobject_attr_precision_scale(name, Some(9), Some(3)),
1463 (0, 0),
1464 "{name} intentionally has no precision/scale default"
1465 );
1466 }
1467 }
1468
1469 #[test]
1474 fn in_out_number_writes_input_and_reserves_output_slot() {
1475 let inout = BindValue::InOut {
1476 value: Box::new(BindValue::Number("21".to_string())),
1477 out_buffer_size: ORA_TYPE_SIZE_NUMBER,
1478 };
1479 assert!(!inout.is_output_only(), "IN OUT carries an input value");
1481 assert!(!inout.is_return_output(), "IN OUT is not a function RETURN");
1482
1483 let info = bind_value_type_info(&inout).expect("IN OUT NUMBER has type info");
1484 assert_eq!(info.ora_type_num, ORA_TYPE_NUM_NUMBER);
1485 assert_eq!(info.csfrm, 0);
1486 assert_eq!(info.buffer_size, ORA_TYPE_SIZE_NUMBER);
1487
1488 assert_eq!(
1491 written_bytes(&inout, 0),
1492 written_bytes(&BindValue::Number("21".to_string()), 0),
1493 "IN OUT sends the input value, same bytes as a plain IN bind"
1494 );
1495 }
1496
1497 #[test]
1502 fn in_out_varchar_sizes_output_slot_and_writes_input_text() {
1503 let inout = BindValue::InOut {
1505 value: Box::new(BindValue::Text("ab".to_string())),
1506 out_buffer_size: 200,
1507 };
1508 let info = bind_value_type_info(&inout).expect("IN OUT VARCHAR has type info");
1509 assert_eq!(info.ora_type_num, ORA_TYPE_NUM_VARCHAR);
1510 assert_eq!(info.csfrm, CS_FORM_IMPLICIT);
1511 assert_eq!(
1512 info.buffer_size, 200,
1513 "the OUT slot is sized for the returned value, not the short input"
1514 );
1515
1516 assert_eq!(
1518 written_bytes(&inout, CS_FORM_IMPLICIT),
1519 written_bytes(&BindValue::Text("ab".to_string()), CS_FORM_IMPLICIT),
1520 );
1521
1522 let big_input = BindValue::InOut {
1524 value: Box::new(BindValue::Text("abcdefghij".to_string())), out_buffer_size: 4,
1526 };
1527 assert_eq!(
1528 bind_value_type_info(&big_input).unwrap().buffer_size,
1529 40,
1530 "buffer is max(input natural size, requested output size)"
1531 );
1532 }
1533
1534 #[test]
1538 fn in_out_read_back_metadata_matches_output_slot() {
1539 let inout = BindValue::InOut {
1540 value: Box::new(BindValue::Text("ab".to_string())),
1541 out_buffer_size: 200,
1542 };
1543 let column = crate::thin::bind_column_metadata(&inout);
1544 assert_eq!(column.ora_type_num, ORA_TYPE_NUM_VARCHAR);
1545 assert_eq!(column.csfrm, CS_FORM_IMPLICIT);
1546 assert_eq!(column.buffer_size, 200);
1547 assert_eq!(column.max_size, 200);
1548 assert!(!column.is_array);
1549 }
1550}