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
608pub fn public_dbtype_name_from_oracle_type_name(type_name: &str) -> &'static str {
609 let upper = type_name.to_ascii_uppercase();
610 if upper.starts_with("TIMESTAMP") {
611 if upper.contains("LOCAL TIME ZONE") || upper.contains("LOCAL TZ") {
612 return "DB_TYPE_TIMESTAMP_LTZ";
613 }
614 if upper.contains("TIME ZONE") || upper.contains("WITH TZ") {
615 return "DB_TYPE_TIMESTAMP_TZ";
616 }
617 return "DB_TYPE_TIMESTAMP";
618 }
619 match upper.as_str() {
620 "CHAR" => "DB_TYPE_CHAR",
621 "NCHAR" => "DB_TYPE_NCHAR",
622 "VARCHAR2" | "VARCHAR" => "DB_TYPE_VARCHAR",
623 "NVARCHAR2" | "NVARCHAR" => "DB_TYPE_NVARCHAR",
624 "RAW" => "DB_TYPE_RAW",
625 "DATE" => "DB_TYPE_DATE",
626 "TIMESTAMP" => "DB_TYPE_TIMESTAMP",
627 "TIMESTAMP WITH TIME ZONE" | "TIMESTAMP WITH TZ" => "DB_TYPE_TIMESTAMP_TZ",
628 "TIMESTAMP WITH LOCAL TIME ZONE" | "TIMESTAMP WITH LOCAL TZ" => "DB_TYPE_TIMESTAMP_LTZ",
629 "CLOB" => "DB_TYPE_CLOB",
630 "NCLOB" => "DB_TYPE_NCLOB",
631 "BLOB" => "DB_TYPE_BLOB",
632 "XMLTYPE" => "DB_TYPE_XMLTYPE",
633 "BINARY_FLOAT" => "DB_TYPE_BINARY_FLOAT",
634 "BINARY_DOUBLE" => "DB_TYPE_BINARY_DOUBLE",
635 "NUMBER" | "INTEGER" | "SMALLINT" | "REAL" | "DOUBLE PRECISION" | "FLOAT" => {
636 "DB_TYPE_NUMBER"
637 }
638 "BOOLEAN" | "PL/SQL BOOLEAN" => "DB_TYPE_BOOLEAN",
643 "BINARY_INTEGER" | "PLS_INTEGER" | "PL/SQL BINARY INTEGER" | "PL/SQL PLS INTEGER" => {
644 "DB_TYPE_BINARY_INTEGER"
645 }
646 "LONG" => "DB_TYPE_LONG",
647 "LONG RAW" => "DB_TYPE_LONG_RAW",
648 "ROWID" => "DB_TYPE_ROWID",
649 "UROWID" => "DB_TYPE_UROWID",
650 "BFILE" => "DB_TYPE_BFILE",
651 "JSON" => "DB_TYPE_JSON",
652 "VECTOR" => "DB_TYPE_VECTOR",
653 "INTERVAL DAY TO SECOND" => "DB_TYPE_INTERVAL_DS",
654 "INTERVAL YEAR TO MONTH" => "DB_TYPE_INTERVAL_YM",
655 _ => "DB_TYPE_OBJECT",
658 }
659}
660
661pub fn dbobject_attr_precision_scale(
662 type_name: &str,
663 precision: Option<i8>,
664 scale: Option<i8>,
665) -> (i8, i8) {
666 match type_name.to_ascii_uppercase().as_str() {
667 "NUMBER" => (
668 precision.unwrap_or(if scale == Some(0) { 38 } else { 0 }),
669 scale.unwrap_or(-127),
670 ),
671 "INTEGER" | "SMALLINT" => (precision.unwrap_or(38), scale.unwrap_or(0)),
672 "REAL" => (precision.unwrap_or(63), scale.unwrap_or(-127)),
673 "DOUBLE PRECISION" | "FLOAT" => (precision.unwrap_or(126), scale.unwrap_or(-127)),
674 _ => (0, 0),
675 }
676}
677
678pub fn dbobject_attr_max_size(type_name: &str, length: Option<u32>) -> u32 {
679 let length = length.unwrap_or(0);
680 match type_name.to_ascii_uppercase().as_str() {
681 "NCHAR" | "NVARCHAR2" | "NVARCHAR" => length.saturating_mul(2),
682 _ => length,
683 }
684}
685
686pub fn dbobject_rowtype_attr_max_size(
687 type_name: &str,
688 data_length: Option<u32>,
689 char_length: Option<u32>,
690) -> u32 {
691 match type_name.to_ascii_uppercase().as_str() {
692 "CHAR" | "VARCHAR" | "VARCHAR2" | "RAW" => data_length.unwrap_or(0),
693 "NCHAR" | "NVARCHAR" | "NVARCHAR2" => dbobject_attr_max_size(
694 type_name,
695 char_length.filter(|length| *length > 0).or(data_length),
696 ),
697 _ => 0,
698 }
699}
700
701pub fn public_dbtype_name_from_bind(value: &BindValue) -> &'static str {
702 match value {
703 BindValue::TypedNull {
704 ora_type_num,
705 csfrm,
706 ..
707 }
708 | BindValue::Output {
709 ora_type_num,
710 csfrm,
711 ..
712 }
713 | BindValue::ReturnOutput {
714 ora_type_num,
715 csfrm,
716 ..
717 }
718 | BindValue::Array {
719 ora_type_num,
720 csfrm,
721 ..
722 } => public_dbtype_name_from_type_info(*ora_type_num, *csfrm),
723 BindValue::ObjectOutput { .. } | BindValue::ObjectInput { .. } => "DB_TYPE_OBJECT",
724 BindValue::InOut { value, .. } => public_dbtype_name_from_bind(value),
726 BindValue::Text(_) => "DB_TYPE_VARCHAR",
727 BindValue::Raw(_) => "DB_TYPE_RAW",
728 BindValue::Lob {
729 ora_type_num,
730 csfrm,
731 ..
732 } => match (*ora_type_num, *csfrm) {
733 (ORA_TYPE_NUM_BLOB, _) => "DB_TYPE_BLOB",
734 (ORA_TYPE_NUM_CLOB, CS_FORM_NCHAR) => "DB_TYPE_NCLOB",
735 (ORA_TYPE_NUM_CLOB, _) => "DB_TYPE_CLOB",
736 _ => "DB_TYPE_CLOB",
737 },
738 BindValue::Number(_) => "DB_TYPE_NUMBER",
739 BindValue::BinaryInteger(_) => "DB_TYPE_BINARY_INTEGER",
740 BindValue::BinaryDouble(_) => "DB_TYPE_BINARY_DOUBLE",
741 BindValue::BinaryFloat(_) => "DB_TYPE_BINARY_FLOAT",
742 BindValue::Boolean(_) => "DB_TYPE_BOOLEAN",
743 BindValue::IntervalDS { .. } => "DB_TYPE_INTERVAL_DS",
744 BindValue::IntervalYM { .. } => "DB_TYPE_INTERVAL_YM",
745 BindValue::DateTime { .. } => "DB_TYPE_DATE",
746 BindValue::Timestamp { ora_type_num, .. } => match *ora_type_num {
747 ORA_TYPE_NUM_TIMESTAMP_LTZ => "DB_TYPE_TIMESTAMP_LTZ",
748 ORA_TYPE_NUM_TIMESTAMP_TZ => "DB_TYPE_TIMESTAMP_TZ",
749 _ => "DB_TYPE_TIMESTAMP",
750 },
751 BindValue::TimestampTz { .. } => "DB_TYPE_TIMESTAMP_TZ",
752 BindValue::Vector(_) => "DB_TYPE_VECTOR",
753 BindValue::Json(_) => "DB_TYPE_JSON",
754 BindValue::Cursor { .. } => "DB_TYPE_CURSOR",
755 BindValue::Null => "DB_TYPE_VARCHAR",
756 }
757}
758
759pub fn bind_template_from_type_name(type_name: &str, size: u32) -> BindValue {
760 let text_buffer_size = if size == 0 { 4000 } else { size.max(1) };
761 let nchar_buffer_size = text_buffer_size.saturating_mul(4);
762 match type_name {
763 "NUMBER" | "DB_TYPE_NUMBER" | "int" | "float" | "Decimal" => BindValue::TypedNull {
764 ora_type_num: ORA_TYPE_NUM_NUMBER,
765 csfrm: 0,
766 buffer_size: ORA_TYPE_SIZE_NUMBER,
767 },
768 "NATIVE_INT" | "DB_TYPE_BINARY_INTEGER" => BindValue::TypedNull {
769 ora_type_num: ORA_TYPE_NUM_BINARY_INTEGER,
770 csfrm: 0,
771 buffer_size: ORA_TYPE_SIZE_NUMBER,
772 },
773 "NATIVE_FLOAT" | "DB_TYPE_BINARY_DOUBLE" => BindValue::TypedNull {
774 ora_type_num: ORA_TYPE_NUM_BINARY_DOUBLE,
775 csfrm: 0,
776 buffer_size: ORA_TYPE_SIZE_BINARY_DOUBLE,
777 },
778 "DB_TYPE_BINARY_FLOAT" | "BINARY_FLOAT" => BindValue::TypedNull {
779 ora_type_num: ORA_TYPE_NUM_BINARY_FLOAT,
780 csfrm: 0,
781 buffer_size: ORA_TYPE_SIZE_BINARY_FLOAT,
782 },
783 "DB_TYPE_BOOLEAN" | "BOOLEAN" | "bool" => BindValue::TypedNull {
784 ora_type_num: ORA_TYPE_NUM_BOOLEAN,
785 csfrm: 0,
786 buffer_size: ORA_TYPE_SIZE_BOOLEAN,
787 },
788 "DB_TYPE_INTERVAL_DS" | "INTERVAL DAY TO SECOND" | "timedelta" => BindValue::TypedNull {
789 ora_type_num: ORA_TYPE_NUM_INTERVAL_DS,
790 csfrm: 0,
791 buffer_size: ORA_TYPE_SIZE_INTERVAL_DS,
792 },
793 "DB_TYPE_INTERVAL_YM" | "INTERVAL YEAR TO MONTH" | "IntervalYM" => BindValue::TypedNull {
794 ora_type_num: ORA_TYPE_NUM_INTERVAL_YM,
795 csfrm: 0,
796 buffer_size: ORA_TYPE_SIZE_INTERVAL_YM,
797 },
798 "STRING" | "DB_TYPE_VARCHAR" | "DB_TYPE_CHAR" | "str" => BindValue::TypedNull {
799 ora_type_num: ORA_TYPE_NUM_VARCHAR,
800 csfrm: CS_FORM_IMPLICIT,
801 buffer_size: text_buffer_size,
802 },
803 "DB_TYPE_NCHAR" | "DB_TYPE_NVARCHAR" => BindValue::TypedNull {
804 ora_type_num: ORA_TYPE_NUM_VARCHAR,
805 csfrm: CS_FORM_NCHAR,
806 buffer_size: nchar_buffer_size,
807 },
808 "DB_TYPE_CLOB" | "CLOB" => BindValue::TypedNull {
809 ora_type_num: ORA_TYPE_NUM_LONG,
810 csfrm: CS_FORM_IMPLICIT,
811 buffer_size: TNS_MAX_LONG_LENGTH,
812 },
813 "DB_TYPE_NCLOB" | "NCLOB" => BindValue::TypedNull {
814 ora_type_num: ORA_TYPE_NUM_LONG,
815 csfrm: CS_FORM_NCHAR,
816 buffer_size: TNS_MAX_LONG_LENGTH,
817 },
818 "DB_TYPE_BLOB" | "BLOB" => BindValue::TypedNull {
819 ora_type_num: ORA_TYPE_NUM_LONG_RAW,
820 csfrm: 0,
821 buffer_size: TNS_MAX_LONG_LENGTH,
822 },
823 "DB_TYPE_LONG" | "LONG" | "LONG_STRING" => BindValue::TypedNull {
824 ora_type_num: ORA_TYPE_NUM_LONG,
825 csfrm: CS_FORM_IMPLICIT,
826 buffer_size: TNS_MAX_LONG_LENGTH,
827 },
828 "DB_TYPE_LONG_NVARCHAR" | "LONG NVARCHAR" => BindValue::TypedNull {
829 ora_type_num: ORA_TYPE_NUM_LONG,
830 csfrm: CS_FORM_NCHAR,
831 buffer_size: TNS_MAX_LONG_LENGTH,
832 },
833 "DB_TYPE_LONG_RAW" | "LONG RAW" | "LONG_BINARY" => BindValue::TypedNull {
834 ora_type_num: ORA_TYPE_NUM_LONG_RAW,
835 csfrm: 0,
836 buffer_size: TNS_MAX_LONG_LENGTH,
837 },
838 "DB_TYPE_RAW" | "BINARY" | "bytes" => BindValue::TypedNull {
839 ora_type_num: ORA_TYPE_NUM_RAW,
840 csfrm: 0,
841 buffer_size: size.max(1).max(4000),
842 },
843 "ROWID" | "DB_TYPE_ROWID" | "DB_TYPE_UROWID" => BindValue::TypedNull {
844 ora_type_num: ORA_TYPE_NUM_VARCHAR,
845 csfrm: CS_FORM_IMPLICIT,
846 buffer_size: 5267,
847 },
848 "DATETIME" | "DB_TYPE_DATE" | "date" | "datetime" => BindValue::TypedNull {
849 ora_type_num: ORA_TYPE_NUM_DATE,
850 csfrm: 0,
851 buffer_size: ORA_TYPE_SIZE_DATE,
852 },
853 "DB_TYPE_TIMESTAMP" | "TIMESTAMP" => BindValue::TypedNull {
854 ora_type_num: ORA_TYPE_NUM_TIMESTAMP,
855 csfrm: 0,
856 buffer_size: ORA_TYPE_SIZE_TIMESTAMP,
857 },
858 "DB_TYPE_TIMESTAMP_LTZ" | "TIMESTAMP WITH LOCAL TIME ZONE" => BindValue::TypedNull {
859 ora_type_num: ORA_TYPE_NUM_TIMESTAMP_LTZ,
860 csfrm: 0,
861 buffer_size: ORA_TYPE_SIZE_TIMESTAMP,
862 },
863 "DB_TYPE_TIMESTAMP_TZ" | "TIMESTAMP WITH TIME ZONE" => BindValue::TypedNull {
864 ora_type_num: ORA_TYPE_NUM_TIMESTAMP_TZ,
865 csfrm: 0,
866 buffer_size: ORA_TYPE_SIZE_TIMESTAMP_TZ,
867 },
868 "DB_TYPE_CURSOR" | "CURSOR" => cursor_bind_template(),
869 "DB_TYPE_VECTOR" | "VECTOR" => BindValue::TypedNull {
870 ora_type_num: ORA_TYPE_NUM_VECTOR,
871 csfrm: 0,
872 buffer_size: TNS_VECTOR_MAX_LENGTH,
873 },
874 "DB_TYPE_JSON" | "JSON" => BindValue::TypedNull {
875 ora_type_num: ORA_TYPE_NUM_JSON,
876 csfrm: 0,
877 buffer_size: TNS_VECTOR_MAX_LENGTH,
878 },
879 _ => BindValue::Null,
880 }
881}
882
883pub fn dbobject_element_bind_type_info(dbtype_name: &str, max_size: u32) -> BindTypeInfo {
884 let buffer_size = max_size.max(1);
885 let (ora_type_num, csfrm, buffer_size) = match dbtype_name {
886 "DB_TYPE_NUMBER" => (ORA_TYPE_NUM_NUMBER, 0, ORA_TYPE_SIZE_NUMBER),
887 "DB_TYPE_RAW" | "DB_TYPE_BLOB" => (ORA_TYPE_NUM_RAW, 0, buffer_size.max(4000)),
888 "DB_TYPE_NCHAR" | "DB_TYPE_NVARCHAR" | "DB_TYPE_NCLOB" => {
889 (ORA_TYPE_NUM_VARCHAR, CS_FORM_NCHAR, buffer_size.max(4000))
890 }
891 "DB_TYPE_DATE" => (ORA_TYPE_NUM_DATE, 0, ORA_TYPE_SIZE_DATE),
892 "DB_TYPE_TIMESTAMP" => (ORA_TYPE_NUM_TIMESTAMP, 0, ORA_TYPE_SIZE_TIMESTAMP),
893 "DB_TYPE_TIMESTAMP_LTZ" => (ORA_TYPE_NUM_TIMESTAMP_LTZ, 0, ORA_TYPE_SIZE_TIMESTAMP),
894 "DB_TYPE_TIMESTAMP_TZ" => (ORA_TYPE_NUM_TIMESTAMP_TZ, 0, ORA_TYPE_SIZE_TIMESTAMP_TZ),
895 _ => (
896 ORA_TYPE_NUM_VARCHAR,
897 CS_FORM_IMPLICIT,
898 buffer_size.max(4000),
899 ),
900 };
901 BindTypeInfo {
902 ora_type_num,
903 csfrm,
904 buffer_size,
905 }
906}
907
908pub(crate) fn public_dbtype_name_from_type_info(ora_type_num: u8, csfrm: u8) -> &'static str {
909 match (ora_type_num, csfrm) {
910 (ORA_TYPE_NUM_BINARY_DOUBLE, _) => "DB_TYPE_BINARY_DOUBLE",
911 (ORA_TYPE_NUM_BINARY_FLOAT, _) => "DB_TYPE_BINARY_FLOAT",
912 (ORA_TYPE_NUM_INTERVAL_DS, _) => "DB_TYPE_INTERVAL_DS",
913 (ORA_TYPE_NUM_INTERVAL_YM, _) => "DB_TYPE_INTERVAL_YM",
914 (ORA_TYPE_NUM_BOOLEAN, _) => "DB_TYPE_BOOLEAN",
915 (ORA_TYPE_NUM_BINARY_INTEGER, _) => "DB_TYPE_BINARY_INTEGER",
916 (ORA_TYPE_NUM_NUMBER, _) => "DB_TYPE_NUMBER",
917 (ORA_TYPE_NUM_CHAR, CS_FORM_NCHAR) | (ORA_TYPE_NUM_VARCHAR, CS_FORM_NCHAR) => {
918 "DB_TYPE_NVARCHAR"
919 }
920 (ORA_TYPE_NUM_CHAR, _) => "DB_TYPE_CHAR",
921 (ORA_TYPE_NUM_VARCHAR, _) => "DB_TYPE_VARCHAR",
922 (ORA_TYPE_NUM_LONG, CS_FORM_NCHAR) => "DB_TYPE_LONG_NVARCHAR",
923 (ORA_TYPE_NUM_LONG, _) => "DB_TYPE_LONG",
924 (ORA_TYPE_NUM_LONG_RAW, _) => "DB_TYPE_LONG_RAW",
925 (ORA_TYPE_NUM_RAW, _) => "DB_TYPE_RAW",
926 (ORA_TYPE_NUM_DATE, _) => "DB_TYPE_DATE",
927 (ORA_TYPE_NUM_TIMESTAMP, _) => "DB_TYPE_TIMESTAMP",
928 (ORA_TYPE_NUM_TIMESTAMP_LTZ, _) => "DB_TYPE_TIMESTAMP_LTZ",
929 (ORA_TYPE_NUM_TIMESTAMP_TZ, _) => "DB_TYPE_TIMESTAMP_TZ",
930 (ORA_TYPE_NUM_CURSOR, _) => "DB_TYPE_CURSOR",
931 (ORA_TYPE_NUM_OBJECT, _) => "DB_TYPE_OBJECT",
932 (ORA_TYPE_NUM_VECTOR, _) => "DB_TYPE_VECTOR",
933 (ORA_TYPE_NUM_JSON, _) => "DB_TYPE_JSON",
934 _ => "DB_TYPE_VARCHAR",
935 }
936}
937
938pub(crate) fn bind_metadata(value: &BindValue) -> (u8, u8, u32) {
939 bind_value_type_info(value)
940 .map(|info| (info.ora_type_num, info.csfrm, info.buffer_size))
941 .unwrap_or((ORA_TYPE_NUM_VARCHAR, CS_FORM_IMPLICIT, 1))
942}
943
944pub(crate) fn write_bind_value(writer: &mut TtcWriter, value: &BindValue, csfrm: u8) -> Result<()> {
945 match value {
946 BindValue::TypedNull {
947 ora_type_num: ORA_TYPE_NUM_CURSOR,
948 ..
949 } => {
950 writer.write_u8(1);
951 writer.write_u8(0);
952 Ok(())
953 }
954 BindValue::TypedNull {
959 ora_type_num: ORA_TYPE_NUM_BOOLEAN,
960 ..
961 } => {
962 writer.write_u8(TNS_ESCAPE_CHAR);
963 writer.write_u8(1);
964 Ok(())
965 }
966 BindValue::Null | BindValue::TypedNull { .. } => {
967 writer.write_u8(0);
968 Ok(())
969 }
970 BindValue::Output { .. } | BindValue::ReturnOutput { .. } => {
971 writer.write_u8(0);
972 Ok(())
973 }
974 BindValue::InOut { value, .. } => write_bind_value(writer, value, csfrm),
979 BindValue::ObjectOutput { .. } => {
980 writer.write_ub4(0);
983 writer.write_ub4(0);
984 writer.write_ub4(0);
985 writer.write_ub2(0);
986 writer.write_ub4(0);
987 writer.write_ub4(TNS_OBJ_TOP_LEVEL);
988 Ok(())
989 }
990 BindValue::ObjectInput { oid, image, .. } => write_dbobject_bind(writer, oid, image),
991 BindValue::Text(value) => {
992 if csfrm == CS_FORM_NCHAR {
997 let bytes = encode_text_value(value, csfrm);
998 writer.write_bytes_with_length(&bytes)
999 } else {
1000 writer.write_bytes_with_length(value.as_bytes())
1001 }
1002 }
1003 BindValue::Raw(value) => writer.write_bytes_with_length(value),
1004 BindValue::Lob { locator, .. } => writer.write_bytes_with_two_lengths(Some(locator)),
1005 BindValue::Number(value) | BindValue::BinaryInteger(value) => {
1006 let bytes = encode_number_text(value)?;
1007 writer.write_bytes_with_length(&bytes)
1008 }
1009 BindValue::Boolean(value) => {
1012 let bytes: &[u8] = if *value { &[1, 1] } else { &[0] };
1013 writer.write_bytes_with_length(bytes)
1014 }
1015 BindValue::BinaryDouble(value) => {
1016 let bytes = encode_binary_double(*value);
1017 writer.write_bytes_with_length(&bytes)
1018 }
1019 BindValue::BinaryFloat(value) => {
1020 let bytes = encode_binary_float(*value as f32);
1021 writer.write_bytes_with_length(&bytes)
1022 }
1023 BindValue::IntervalDS {
1024 days,
1025 seconds,
1026 microseconds,
1027 } => {
1028 let nanoseconds = microseconds
1029 .checked_mul(1000)
1030 .ok_or(ProtocolError::TtcDecode(
1031 "INTERVAL DS fractional seconds out of range",
1032 ))?;
1033 let bytes = encode_interval_ds(*days, *seconds, nanoseconds)?;
1034 writer.write_bytes_with_length(&bytes)
1035 }
1036 BindValue::IntervalYM { years, months } => {
1037 let bytes = encode_interval_ym(*years, *months)?;
1038 writer.write_bytes_with_length(&bytes)
1039 }
1040 BindValue::DateTime {
1041 year,
1042 month,
1043 day,
1044 hour,
1045 minute,
1046 second,
1047 } => {
1048 let bytes = encode_oracle_date(*year, *month, *day, *hour, *minute, *second)?;
1049 writer.write_bytes_with_length(&bytes)
1050 }
1051 BindValue::Timestamp {
1052 year,
1053 month,
1054 day,
1055 hour,
1056 minute,
1057 second,
1058 nanosecond,
1059 ora_type_num,
1060 } => {
1061 let bytes = if matches!(*ora_type_num, ORA_TYPE_NUM_TIMESTAMP_TZ) {
1062 encode_oracle_timestamp_tz(
1063 *year,
1064 *month,
1065 *day,
1066 *hour,
1067 *minute,
1068 *second,
1069 *nanosecond,
1070 )?
1071 } else {
1072 encode_oracle_timestamp(*year, *month, *day, *hour, *minute, *second, *nanosecond)?
1073 };
1074 writer.write_bytes_with_length(&bytes)
1075 }
1076 BindValue::TimestampTz {
1077 year,
1078 month,
1079 day,
1080 hour,
1081 minute,
1082 second,
1083 nanosecond,
1084 offset_minutes,
1085 } => {
1086 let bytes = encode_oracle_timestamp_tz_with_offset(
1087 *year,
1088 *month,
1089 *day,
1090 *hour,
1091 *minute,
1092 *second,
1093 *nanosecond,
1094 *offset_minutes,
1095 )?;
1096 writer.write_bytes_with_length(&bytes)
1097 }
1098 BindValue::Array {
1099 values,
1100 csfrm: array_csfrm,
1101 ..
1102 } => {
1103 writer.write_ub4(u32::try_from(values.len()).map_err(|_| {
1104 ProtocolError::InvalidPacketLength {
1105 length: values.len(),
1106 minimum: 0,
1107 }
1108 })?);
1109 for value in values {
1110 match value {
1111 Some(value) => write_bind_value(writer, value, *array_csfrm)?,
1112 None => writer.write_u8(0),
1113 }
1114 }
1115 Ok(())
1116 }
1117 BindValue::Vector(vector) => {
1120 let image = crate::vector::encode_vector_checked(vector)?;
1121 crate::vector::write_vector_image(writer, &image)
1122 }
1123 BindValue::Json(image) => crate::vector::write_vector_image(writer, image),
1126 BindValue::Cursor { cursor_id } => {
1127 if *cursor_id == 0 {
1128 writer.write_u8(1);
1129 writer.write_u8(0);
1130 } else {
1131 writer.write_ub4(1);
1132 writer.write_ub4(*cursor_id);
1133 }
1134 Ok(())
1135 }
1136 }
1137}
1138
1139pub(crate) fn encode_text_value(value: &str, csfrm: u8) -> Vec<u8> {
1140 if csfrm == CS_FORM_NCHAR {
1141 let mut bytes = Vec::with_capacity(value.len().saturating_mul(2));
1142 for unit in value.encode_utf16() {
1143 bytes.extend_from_slice(&unit.to_be_bytes());
1144 }
1145 bytes
1146 } else {
1147 value.as_bytes().to_vec()
1148 }
1149}
1150
1151#[cfg(test)]
1152mod in_out_bind_tests {
1153 use super::*;
1154
1155 fn written_bytes(value: &BindValue, csfrm: u8) -> Vec<u8> {
1156 let mut writer = TtcWriter::new();
1157 write_bind_value(&mut writer, value, csfrm).expect("write bind value");
1158 writer.into_bytes()
1159 }
1160
1161 #[test]
1166 fn in_out_number_writes_input_and_reserves_output_slot() {
1167 let inout = BindValue::InOut {
1168 value: Box::new(BindValue::Number("21".to_string())),
1169 out_buffer_size: ORA_TYPE_SIZE_NUMBER,
1170 };
1171 assert!(!inout.is_output_only(), "IN OUT carries an input value");
1173 assert!(!inout.is_return_output(), "IN OUT is not a function RETURN");
1174
1175 let info = bind_value_type_info(&inout).expect("IN OUT NUMBER has type info");
1176 assert_eq!(info.ora_type_num, ORA_TYPE_NUM_NUMBER);
1177 assert_eq!(info.csfrm, 0);
1178 assert_eq!(info.buffer_size, ORA_TYPE_SIZE_NUMBER);
1179
1180 assert_eq!(
1183 written_bytes(&inout, 0),
1184 written_bytes(&BindValue::Number("21".to_string()), 0),
1185 "IN OUT sends the input value, same bytes as a plain IN bind"
1186 );
1187 }
1188
1189 #[test]
1194 fn in_out_varchar_sizes_output_slot_and_writes_input_text() {
1195 let inout = BindValue::InOut {
1197 value: Box::new(BindValue::Text("ab".to_string())),
1198 out_buffer_size: 200,
1199 };
1200 let info = bind_value_type_info(&inout).expect("IN OUT VARCHAR has type info");
1201 assert_eq!(info.ora_type_num, ORA_TYPE_NUM_VARCHAR);
1202 assert_eq!(info.csfrm, CS_FORM_IMPLICIT);
1203 assert_eq!(
1204 info.buffer_size, 200,
1205 "the OUT slot is sized for the returned value, not the short input"
1206 );
1207
1208 assert_eq!(
1210 written_bytes(&inout, CS_FORM_IMPLICIT),
1211 written_bytes(&BindValue::Text("ab".to_string()), CS_FORM_IMPLICIT),
1212 );
1213
1214 let big_input = BindValue::InOut {
1216 value: Box::new(BindValue::Text("abcdefghij".to_string())), out_buffer_size: 4,
1218 };
1219 assert_eq!(
1220 bind_value_type_info(&big_input).unwrap().buffer_size,
1221 40,
1222 "buffer is max(input natural size, requested output size)"
1223 );
1224 }
1225
1226 #[test]
1230 fn in_out_read_back_metadata_matches_output_slot() {
1231 let inout = BindValue::InOut {
1232 value: Box::new(BindValue::Text("ab".to_string())),
1233 out_buffer_size: 200,
1234 };
1235 let column = crate::thin::bind_column_metadata(&inout);
1236 assert_eq!(column.ora_type_num, ORA_TYPE_NUM_VARCHAR);
1237 assert_eq!(column.csfrm, CS_FORM_IMPLICIT);
1238 assert_eq!(column.buffer_size, 200);
1239 assert_eq!(column.max_size, 200);
1240 assert!(!column.is_array);
1241 }
1242}