1#![forbid(unsafe_code)]
2
3use super::*;
4
5pub fn build_execute_query_payload(
6 sql: &str,
7 prefetch_rows: u32,
8 ttc_field_version: u8,
9) -> Result<Vec<u8>> {
10 build_execute_query_payload_with_seq(sql, prefetch_rows, 1, ttc_field_version)
11}
12
13pub fn build_execute_query_payload_with_seq(
14 sql: &str,
15 prefetch_rows: u32,
16 seq_num: u8,
17 ttc_field_version: u8,
18) -> Result<Vec<u8>> {
19 build_execute_payload_with_seq(sql, prefetch_rows, seq_num, true, ttc_field_version)
20}
21
22pub fn build_execute_payload_with_seq(
23 sql: &str,
24 prefetch_rows: u32,
25 seq_num: u8,
26 is_query: bool,
27 ttc_field_version: u8,
28) -> Result<Vec<u8>> {
29 build_execute_payload_with_binds_with_seq(
30 sql,
31 prefetch_rows,
32 seq_num,
33 is_query,
34 &[],
35 ttc_field_version,
36 )
37}
38
39pub fn build_execute_payload_with_binds_with_seq(
40 sql: &str,
41 prefetch_rows: u32,
42 seq_num: u8,
43 is_query: bool,
44 binds: &[BindValue],
45 ttc_field_version: u8,
46) -> Result<Vec<u8>> {
47 let bind_rows = if binds.is_empty() {
48 Vec::new()
49 } else {
50 vec![binds.to_vec()]
51 };
52 build_execute_payload_with_bind_rows_with_seq(
53 sql,
54 prefetch_rows,
55 seq_num,
56 is_query,
57 &bind_rows,
58 ttc_field_version,
59 )
60}
61
62pub fn build_execute_payload_with_bind_rows_with_seq(
63 sql: &str,
64 prefetch_rows: u32,
65 seq_num: u8,
66 is_query: bool,
67 bind_rows: &[Vec<BindValue>],
68 ttc_field_version: u8,
69) -> Result<Vec<u8>> {
70 build_execute_payload_with_bind_rows_and_options_with_seq(
71 sql,
72 prefetch_rows,
73 seq_num,
74 is_query,
75 bind_rows,
76 ExecuteOptions::default(),
77 ttc_field_version,
78 )
79}
80
81pub fn build_execute_payload_with_bind_rows_with_seq_and_token(
85 sql: &str,
86 prefetch_rows: u32,
87 seq_num: u8,
88 is_query: bool,
89 bind_rows: &[Vec<BindValue>],
90 token_num: u64,
91 ttc_field_version: u8,
92) -> Result<Vec<u8>> {
93 build_execute_payload_with_bind_rows_and_options_with_seq(
94 sql,
95 prefetch_rows,
96 seq_num,
97 is_query,
98 bind_rows,
99 ExecuteOptions {
100 token_num,
101 ..ExecuteOptions::default()
102 },
103 ttc_field_version,
104 )
105}
106
107pub fn build_close_cursors_piggyback(
112 cursor_ids: &[u32],
113 seq_num: u8,
114 ttc_field_version: u8,
115) -> Vec<u8> {
116 let mut writer = TtcWriter::new();
117 writer.write_piggyback_header(TNS_FUNC_CLOSE_CURSORS, seq_num, ttc_field_version);
118 writer.write_u8(1); writer.write_ub4(u32::try_from(cursor_ids.len()).unwrap_or(u32::MAX));
120 for cursor_id in cursor_ids {
121 writer.write_ub4(*cursor_id);
122 }
123 writer.into_bytes()
124}
125
126pub fn build_execute_payload_with_bind_rows_and_options_with_seq(
127 sql: &str,
128 prefetch_rows: u32,
129 seq_num: u8,
130 is_query: bool,
131 bind_rows: &[Vec<BindValue>],
132 exec_options: ExecuteOptions,
133 ttc_field_version: u8,
134) -> Result<Vec<u8>> {
135 let sql_bytes = sql.as_bytes();
136 let sql_len =
137 u32::try_from(sql_bytes.len()).map_err(|_| ProtocolError::InvalidPacketLength {
138 length: sql_bytes.len(),
139 minimum: 0,
140 })?;
141 let bind_count = bind_rows.first().map_or(0, Vec::len);
142 for row in bind_rows {
143 if row.len() != bind_count {
144 return Err(ProtocolError::TtcDecode("inconsistent bind row width"));
145 }
146 }
147 let bind_count = u32::try_from(bind_count).map_err(|_| ProtocolError::InvalidPacketLength {
148 length: bind_count,
149 minimum: 0,
150 })?;
151 let bind_row_count =
152 u32::try_from(bind_rows.len()).map_err(|_| ProtocolError::InvalidPacketLength {
153 length: bind_rows.len(),
154 minimum: 0,
155 })?;
156 let writer_capacity = 96 + sql_bytes.len();
164 let mut writer = TtcWriter::with_capacity(writer_capacity);
165 writer.write_function_code_with_seq(TNS_FUNC_EXECUTE, seq_num);
166 if version_gates::writes_pipeline_token(ttc_field_version) {
172 writer.write_ub8(exec_options.token_num);
173 } else {
174 debug_assert_eq!(
175 exec_options.token_num, 0,
176 "pipeline tokens require a 23ai-negotiated connection"
177 );
178 }
179
180 let is_plsql = statement_is_plsql(sql);
181 let parse_only = exec_options.parse_only;
182 let needs_parse = exec_options.cursor_id == 0 || crate::sql::statement_is_ddl(sql);
185 let scroll_operation = exec_options.scroll_operation;
188 let mut options = 0;
189 if needs_parse {
190 options |= TNS_EXEC_OPTION_PARSE;
191 }
192 if !parse_only && !scroll_operation {
193 options |= TNS_EXEC_OPTION_EXECUTE;
194 }
195 if is_query {
196 if parse_only {
197 options |= TNS_EXEC_OPTION_DESCRIBE;
198 } else if !exec_options.no_prefetch {
199 options |= TNS_EXEC_OPTION_FETCH;
203 }
204 }
205 if bind_count > 0 && !scroll_operation {
206 options |= TNS_EXEC_OPTION_BIND;
207 }
208 if is_plsql {
209 if bind_count > 0 {
210 options |= TNS_EXEC_OPTION_PLSQL_BIND;
211 }
212 } else if !parse_only {
213 options |= TNS_EXEC_OPTION_NOT_PLSQL;
214 }
215 if exec_options.batcherrors {
216 options |= TNS_EXEC_OPTION_BATCH_ERRORS;
217 }
218 let num_iters = if is_query && !parse_only {
219 prefetch_rows
220 } else {
221 1
222 };
223 let exec_count = if parse_only {
226 0
227 } else if is_query {
228 if exec_options.cursor_id == 0 {
229 0
230 } else {
231 num_iters
232 }
233 } else {
234 bind_row_count.max(1)
235 };
236 let query_flag = u32::from(is_query);
237 let mut exec_flags = if parse_only {
241 0
242 } else {
243 TNS_EXEC_FLAGS_IMPLICIT_RESULTSET
244 };
245 if exec_options.arraydmlrowcounts {
246 exec_flags |= TNS_EXEC_FLAGS_DML_ROWCOUNTS;
247 }
248 if exec_options.scrollable && !parse_only {
251 exec_flags |= TNS_EXEC_FLAGS_SCROLLABLE;
252 exec_flags |= TNS_EXEC_FLAGS_NO_CANCEL_ON_EOF;
253 }
254 writer.write_ub4(options);
255 writer.write_ub4(exec_options.cursor_id);
256 if needs_parse {
257 writer.write_u8(1); writer.write_ub4(sql_len);
259 } else {
260 writer.write_u8(0); writer.write_ub4(0);
262 }
263 writer.write_u8(1);
264 writer.write_ub4(13);
265 writer.write_u8(0);
266 writer.write_u8(0);
267 writer.write_ub4(0);
268 writer.write_ub4(num_iters);
269 writer.write_ub4(TNS_MAX_LONG_LENGTH);
270 if bind_count == 0 {
271 writer.write_u8(0);
272 writer.write_ub4(0);
273 } else {
274 writer.write_u8(1);
275 writer.write_ub4(bind_count);
276 }
277 let registration_id_lsb = (exec_options.registration_id & 0xffff_ffff) as u32;
280 let registration_id_msb = ((exec_options.registration_id >> 32) & 0xffff_ffff) as u32;
281 writer.write_u8(0);
282 writer.write_u8(0);
283 writer.write_u8(0);
284 writer.write_u8(0);
285 writer.write_u8(0);
286 writer.write_u8(0);
287 writer.write_ub4(0);
288 writer.write_ub4(registration_id_lsb); writer.write_u8(0); writer.write_u8(1); writer.write_u8(0); writer.write_ub4(0); writer.write_u8(0); writer.write_ub4(0); writer.write_ub4(registration_id_msb); if exec_options.arraydmlrowcounts {
297 writer.write_u8(1); writer.write_ub4(exec_count); writer.write_u8(1); } else {
301 writer.write_u8(0); writer.write_ub4(0); writer.write_u8(0); }
305 if version_gates::writes_al8sqlsig(ttc_field_version) {
313 writer.write_u8(0); writer.write_ub4(0); writer.write_u8(0); writer.write_ub4(0); writer.write_u8(0); if version_gates::writes_execute_chunk_ids(ttc_field_version) {
319 writer.write_u8(0); writer.write_ub4(0); }
322 }
323
324 if needs_parse {
325 writer.write_bytes_with_length(sql_bytes)?;
326 writer.write_ub4(1); } else {
328 writer.write_ub4(0); }
330 writer.write_ub4(exec_count);
331 writer.write_ub4(0);
332 writer.write_ub4(0);
333 writer.write_ub4(0);
334 writer.write_ub4(0);
335 writer.write_ub4(0);
336 writer.write_ub4(query_flag); writer.write_ub4(0); writer.write_ub4(exec_flags); writer.write_ub4(exec_options.fetch_orientation); writer.write_ub4(exec_options.fetch_pos); writer.write_ub4(0); if !bind_rows.is_empty() && !scroll_operation {
345 write_bind_params(
346 &mut writer,
347 bind_rows,
348 is_plsql,
349 exec_options.max_string_size,
350 ttc_field_version,
351 )?;
352 }
353 Ok(writer.into_bytes())
354}
355
356pub(crate) fn write_bind_params(
357 writer: &mut TtcWriter,
358 bind_rows: &[Vec<BindValue>],
359 is_plsql: bool,
360 max_string_size: u32,
361 ttc_field_version: u8,
362) -> Result<()> {
363 let Some(first_row) = bind_rows.first() else {
364 return Ok(());
365 };
366 let mut bind_metadata = Vec::with_capacity(first_row.len());
367 for index in 0..first_row.len() {
368 bind_metadata.push(write_bind_metadata_for_rows(
369 writer,
370 bind_rows,
371 index,
372 ttc_field_version,
373 )?);
374 }
375 for row in bind_rows {
376 if !is_plsql && row.iter().all(BindValue::is_output_only) {
377 continue;
378 }
379 writer.write_u8(TNS_MSG_TYPE_ROW_DATA);
380 for index in bind_row_value_order(row, &bind_metadata, is_plsql, max_string_size) {
381 let value = &row[index];
382 let (_ora_type_num, csfrm, _buffer_size) = bind_metadata
383 .get(index)
384 .copied()
385 .unwrap_or((ORA_TYPE_NUM_VARCHAR, CS_FORM_IMPLICIT, 1));
386 write_bind_value(writer, value, csfrm)?;
387 }
388 }
389 Ok(())
390}
391
392pub(crate) fn bind_row_value_order(
393 row: &[BindValue],
394 bind_metadata: &[(u8, u8, u32)],
395 is_plsql: bool,
396 max_string_size: u32,
397) -> Vec<usize> {
398 let mut non_long = Vec::with_capacity(row.len());
399 let mut long = Vec::new();
400 for (index, value) in row.iter().enumerate() {
401 if !is_plsql && value.is_output_only() {
402 continue;
403 }
404 if !is_plsql
409 && bind_metadata
410 .get(index)
411 .is_some_and(|(ora_type_num, _, buffer_size)| {
412 matches!(*ora_type_num, ORA_TYPE_NUM_LONG | ORA_TYPE_NUM_LONG_RAW)
413 || *buffer_size > max_string_size
414 })
415 {
416 long.push(index);
417 } else {
418 non_long.push(index);
419 }
420 }
421 non_long.extend(long);
422 non_long
423}
424
425pub(crate) fn write_bind_metadata_for_rows(
426 writer: &mut TtcWriter,
427 bind_rows: &[Vec<BindValue>],
428 index: usize,
429 ttc_field_version: u8,
430) -> Result<(u8, u8, u32)> {
431 let Some(first_row) = bind_rows.first() else {
432 return Ok((ORA_TYPE_NUM_VARCHAR, CS_FORM_IMPLICIT, 1));
433 };
434 let Some(first_value) = first_row.get(index) else {
435 return Ok((ORA_TYPE_NUM_VARCHAR, CS_FORM_IMPLICIT, 1));
436 };
437 let mut metadata_value = first_value;
438 let (mut ora_type_num, mut csfrm, mut buffer_size) = bind_metadata(first_value);
439 let mut needs_type_inference = matches!(first_value, BindValue::Null);
440 for row in bind_rows.iter().skip(1) {
441 let Some(value) = row.get(index) else {
442 continue;
443 };
444 if needs_type_inference {
445 if matches!(value, BindValue::Null) {
446 continue;
447 }
448 metadata_value = value;
449 (ora_type_num, csfrm, buffer_size) = bind_metadata(value);
450 needs_type_inference = false;
451 continue;
452 }
453 let (row_ora_type_num, row_csfrm, row_buffer_size) = bind_metadata(value);
454 if row_csfrm == csfrm && bind_metadata_types_are_compatible(ora_type_num, row_ora_type_num)
455 {
456 ora_type_num = promoted_bind_metadata_type(ora_type_num, row_ora_type_num);
457 buffer_size = buffer_size.max(row_buffer_size);
458 }
459 }
460 write_bind_metadata_with_type(
461 writer,
462 metadata_value,
463 ora_type_num,
464 csfrm,
465 buffer_size,
466 ttc_field_version,
467 )?;
468 Ok((ora_type_num, csfrm, buffer_size))
469}
470
471pub(crate) fn bind_metadata_types_are_compatible(left: u8, right: u8) -> bool {
472 left == right
473 || (matches!(
474 left,
475 ORA_TYPE_NUM_CHAR | ORA_TYPE_NUM_VARCHAR | ORA_TYPE_NUM_LONG
476 ) && matches!(
477 right,
478 ORA_TYPE_NUM_CHAR | ORA_TYPE_NUM_VARCHAR | ORA_TYPE_NUM_LONG
479 ))
480 || (matches!(left, ORA_TYPE_NUM_RAW | ORA_TYPE_NUM_LONG_RAW)
481 && matches!(right, ORA_TYPE_NUM_RAW | ORA_TYPE_NUM_LONG_RAW))
482}
483
484pub(crate) fn promoted_bind_metadata_type(left: u8, right: u8) -> u8 {
485 if matches!(left, ORA_TYPE_NUM_LONG | ORA_TYPE_NUM_LONG_RAW) {
486 left
487 } else if matches!(right, ORA_TYPE_NUM_LONG | ORA_TYPE_NUM_LONG_RAW) {
488 right
489 } else {
490 left
491 }
492}
493
494#[cfg(test)]
495mod tests {
496 use super::*;
497
498 fn find_subslice(haystack: &[u8], needle: &[u8], label: &str) -> usize {
499 haystack
500 .windows(needle.len())
501 .position(|window| window == needle)
502 .unwrap_or_else(|| panic!("{label} not found in execute payload"))
503 }
504
505 fn execute_payload_for_version(field_version: u8) -> Vec<u8> {
506 build_execute_payload_with_bind_rows_and_options_with_seq(
507 "select 1 from dual",
508 0,
509 7,
510 true,
511 &[],
512 ExecuteOptions::default(),
513 field_version,
514 )
515 .expect("execute payload")
516 }
517
518 #[test]
524 fn execute_gates_al8sqlsig_and_chunk_ids_on_field_version() {
525 let v7 = execute_payload_for_version(TNS_CCAP_FIELD_VERSION_12_2 - 1); let v8 = execute_payload_for_version(TNS_CCAP_FIELD_VERSION_12_2); let v9 = execute_payload_for_version(TNS_CCAP_FIELD_VERSION_12_2_EXT1); let v17 = execute_payload_for_version(TNS_CCAP_FIELD_VERSION_23_1); assert_eq!(
532 v8.len() - v7.len(),
533 5,
534 "the al8sqlsig block appears only at field version >= 12.2"
535 );
536 assert_eq!(
538 v9.len() - v8.len(),
539 2,
540 "the chunk-ids block appears only at field version >= 12.2 ext1"
541 );
542 assert_eq!(
547 v9, v17,
548 "field versions in [12.2 ext1, 23.1) produce identical EXECUTE bytes"
549 );
550 }
551
552 #[test]
556 fn bind_column_metadata_gates_oaccolid_on_field_version() {
557 let row = vec![BindValue::Text("x".to_string())];
558 let build = |field_version: u8| {
559 build_execute_payload_with_bind_rows_and_options_with_seq(
560 "insert into t values (:1)",
561 1,
562 7,
563 false,
564 &[row.clone()],
565 ExecuteOptions::default(),
566 field_version,
567 )
568 .expect("execute payload")
569 };
570 let v7 = build(TNS_CCAP_FIELD_VERSION_12_2 - 1);
571 let v8 = build(TNS_CCAP_FIELD_VERSION_12_2);
572 assert_eq!(
575 v8.len() - v7.len(),
576 6,
577 "12.2 adds the al8sqlsig block (5) and one per-column oaccolid (1)"
578 );
579 }
580
581 #[test]
582 fn register_query_execute_payload_splits_registration_id() {
583 let registration_id = 0x1122_3344_5566_7788_u64;
584 let payload = build_execute_payload_with_bind_rows_and_options_with_seq(
585 "select * from rust_register_query_t",
586 0,
587 7,
588 true,
589 &[],
590 ExecuteOptions {
591 registration_id,
592 ..ExecuteOptions::default()
593 },
594 ClientCapabilities::default().ttc_field_version,
595 )
596 .expect("execute payload");
597
598 let lsb = [0x55, 0x66, 0x77, 0x88];
599 let msb = [0x11, 0x22, 0x33, 0x44];
600 let lsb_pos = payload
601 .windows(lsb.len())
602 .position(|window| window == lsb)
603 .expect("registration id lsb is encoded");
604 let msb_pos = payload
605 .windows(msb.len())
606 .position(|window| window == msb)
607 .expect("registration id msb is encoded");
608
609 assert!(
610 lsb_pos < msb_pos,
611 "execute payload writes registration id lsb before msb"
612 );
613 }
614
615 #[test]
616 fn long_bind_split_uses_negotiated_max_string_size() {
617 let row = vec![
618 BindValue::Raw(vec![b'A'; 3_999]),
619 BindValue::Raw(vec![b'B'; 4_001]),
620 BindValue::Raw(vec![b'C'; 32_767]),
621 BindValue::Text("ZMARK".to_string()),
622 ];
623 let metadata = row.iter().map(bind_metadata).collect::<Vec<_>>();
624
625 assert_eq!(
626 bind_row_value_order(&row, &metadata, false, 4_000),
627 vec![0, 3, 1, 2],
628 "STANDARD max_string_size=4000 writes >4000-byte binds in the long section"
629 );
630 assert_eq!(
631 bind_row_value_order(&row, &metadata, false, 32_767),
632 vec![0, 1, 2, 3],
633 "32K-capable connections keep <=32767-byte binds in ordinary order"
634 );
635 assert_eq!(
636 bind_row_value_order(&row, &metadata, true, 4_000),
637 vec![0, 1, 2, 3],
638 "PL/SQL bind order is unchanged"
639 );
640
641 let standard = build_execute_payload_with_bind_rows_and_options_with_seq(
642 "insert into t values (:1, :2, :3, :4)",
643 1,
644 7,
645 false,
646 &[row.clone()],
647 ExecuteOptions::default().with_max_string_size(4_000),
648 ClientCapabilities::default().ttc_field_version,
649 )
650 .expect("STANDARD execute payload");
651 let standard_a = find_subslice(&standard, &[b'A'; 32], "STANDARD A value");
652 let standard_b = find_subslice(&standard, &[b'B'; 32], "STANDARD B value");
653 let standard_c = find_subslice(&standard, &[b'C'; 32], "STANDARD C value");
654 let standard_z = find_subslice(&standard, b"ZMARK", "STANDARD raw marker");
655 assert!(
656 standard_a < standard_z && standard_z < standard_b && standard_b < standard_c,
657 "STANDARD payload must write non-long values before >4000-byte long values"
658 );
659
660 let extended = build_execute_payload_with_bind_rows_and_options_with_seq(
661 "insert into t values (:1, :2, :3, :4)",
662 1,
663 8,
664 false,
665 &[row],
666 ExecuteOptions::default().with_max_string_size(32_767),
667 ClientCapabilities::default().ttc_field_version,
668 )
669 .expect("32K execute payload");
670 let extended_a = find_subslice(&extended, &[b'A'; 32], "32K A value");
671 let extended_b = find_subslice(&extended, &[b'B'; 32], "32K B value");
672 let extended_c = find_subslice(&extended, &[b'C'; 32], "32K C value");
673 let extended_z = find_subslice(&extended, b"ZMARK", "32K raw marker");
674 assert!(
675 extended_a < extended_b && extended_b < extended_c && extended_c < extended_z,
676 "32K payload must keep <=32767-byte values in bind order"
677 );
678 }
679}