1use crate::constants::{BindDirection, OracleType};
7use crate::row::Value;
8use crate::types::RefCursor;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
12pub enum StatementType {
13 #[default]
15 Unknown,
16 Query,
18 Dml,
20 Ddl,
22 PlSql,
24}
25
26#[derive(Debug, Clone)]
28pub struct BindInfo {
29 pub name: String,
31 pub is_return_bind: bool,
33 pub oracle_type: Option<OracleType>,
35 pub buffer_size: u32,
37 pub precision: i16,
39 pub scale: i16,
41 pub csfrm: u8,
43 pub bind_dir: u8,
45 pub is_array: bool,
47 pub num_elements: u32,
49}
50
51impl BindInfo {
52 pub fn new(name: impl Into<String>, is_return_bind: bool) -> Self {
54 Self {
55 name: name.into(),
56 is_return_bind,
57 oracle_type: None,
58 buffer_size: 0,
59 precision: 0,
60 scale: 0,
61 csfrm: 0,
62 bind_dir: crate::constants::bind_dir::INPUT,
63 is_array: false,
64 num_elements: 0,
65 }
66 }
67}
68
69#[derive(Debug, Clone)]
94pub struct BindParam {
95 pub value: Option<Value>,
97 pub direction: BindDirection,
99 pub oracle_type: OracleType,
101 pub buffer_size: u32,
103}
104
105impl BindParam {
106 pub fn input(value: Value) -> Self {
108 let oracle_type = Self::infer_oracle_type(&value);
109 Self {
110 value: Some(value),
111 direction: BindDirection::Input,
112 oracle_type,
113 buffer_size: 0, }
115 }
116
117 pub fn output(oracle_type: OracleType, buffer_size: u32) -> Self {
119 Self {
120 value: None,
121 direction: BindDirection::Output,
122 oracle_type,
123 buffer_size,
124 }
125 }
126
127 pub fn output_cursor() -> Self {
129 Self {
130 value: Some(Value::Cursor(RefCursor::new(0, vec![]))), direction: BindDirection::Output,
132 oracle_type: OracleType::Cursor,
133 buffer_size: 0,
134 }
135 }
136
137 pub fn output_collection(obj_type: &crate::dbobject::DbObjectType) -> Self {
141 use crate::dbobject::DbObject;
142 let mut placeholder = DbObject::collection(&obj_type.full_name());
144 placeholder.set("_type_schema", Value::String(obj_type.schema.clone()));
145 placeholder.set("_type_name", Value::String(obj_type.name.clone()));
146 if let Some(elem_type) = obj_type.element_type {
147 placeholder.set("_element_type", Value::Integer(elem_type as i64));
148 }
149 if let Some(ref oid) = obj_type.oid {
151 placeholder.set("_type_oid", Value::Bytes(oid.clone()));
152 }
153 Self {
154 value: Some(Value::Collection(placeholder)),
155 direction: BindDirection::Output,
156 oracle_type: OracleType::Object,
157 buffer_size: 0,
158 }
159 }
160
161 pub fn input_collection(
175 obj_type: &crate::dbobject::DbObjectType,
176 collection: crate::dbobject::DbObject,
177 ) -> Self {
178 use crate::constants::collection_type;
179 use crate::dbobject::{CollectionType, DbObject};
180 let mut coll = DbObject::collection(&obj_type.full_name());
182 coll.elements = collection.elements;
184 coll.set("_type_schema", Value::String(obj_type.schema.clone()));
186 coll.set("_type_name", Value::String(obj_type.name.clone()));
187 if let Some(elem_type) = obj_type.element_type {
188 coll.set("_element_type", Value::Integer(elem_type as i64));
189 }
190 if let Some(coll_type) = obj_type.collection_type {
192 let wire_code = match coll_type {
193 CollectionType::PlsqlIndexTable => collection_type::PLSQL_INDEX_TABLE,
194 CollectionType::NestedTable => collection_type::NESTED_TABLE,
195 CollectionType::Varray => collection_type::VARRAY,
196 };
197 coll.set("_collection_type", Value::Integer(wire_code as i64));
198 }
199 if let Some(ref oid) = obj_type.oid {
201 coll.set("_type_oid", Value::Bytes(oid.clone()));
202 }
203 Self {
204 value: Some(Value::Collection(coll)),
205 direction: BindDirection::Input,
206 oracle_type: OracleType::Object,
207 buffer_size: 0,
208 }
209 }
210
211 pub fn input_output(value: Value, buffer_size: u32) -> Self {
213 let oracle_type = Self::infer_oracle_type(&value);
214 Self {
215 value: Some(value),
216 direction: BindDirection::InputOutput,
217 oracle_type,
218 buffer_size,
219 }
220 }
221
222 pub fn placeholder_value(&self) -> Value {
225 if let Some(ref v) = self.value {
226 return v.clone();
227 }
228
229 match self.oracle_type {
231 OracleType::Varchar | OracleType::Char | OracleType::Long => {
232 Value::String(String::new())
233 }
234 OracleType::Number | OracleType::BinaryInteger => Value::Integer(0),
235 OracleType::BinaryDouble | OracleType::BinaryFloat => Value::Float(0.0),
236 OracleType::Date => Value::Null,
237 OracleType::Timestamp | OracleType::TimestampTz | OracleType::TimestampLtz => {
238 Value::Null
239 }
240 OracleType::Raw | OracleType::LongRaw => Value::Bytes(Vec::new()),
241 OracleType::Clob | OracleType::Blob => Value::Null,
242 OracleType::Cursor => Value::Cursor(RefCursor::new(0, vec![])),
243 OracleType::Boolean => Value::Boolean(false),
244 _ => Value::Null,
245 }
246 }
247
248 fn infer_oracle_type(value: &Value) -> OracleType {
250 match value {
251 Value::Null => OracleType::Varchar, Value::String(_) => OracleType::Varchar,
253 Value::Bytes(_) => OracleType::Raw,
254 Value::Integer(_) => OracleType::Number,
255 Value::Float(_) => OracleType::BinaryDouble,
256 Value::Number(_) => OracleType::Number,
257 Value::Date(_) => OracleType::Date,
258 Value::Timestamp(_) => OracleType::Timestamp,
259 Value::RowId(_) => OracleType::Rowid,
260 Value::Boolean(_) => OracleType::Boolean,
261 Value::Lob(_) => OracleType::Clob, Value::Json(_) => OracleType::Json,
263 Value::Vector(_) => OracleType::Vector,
264 Value::Cursor(_) => OracleType::Cursor,
265 Value::Collection(_) => OracleType::Object,
266 }
267 }
268}
269
270impl From<Value> for BindParam {
271 fn from(value: Value) -> Self {
272 BindParam::input(value)
273 }
274}
275
276impl From<i32> for BindParam {
277 fn from(v: i32) -> Self {
278 BindParam::input(Value::Integer(v as i64))
279 }
280}
281
282impl From<i64> for BindParam {
283 fn from(v: i64) -> Self {
284 BindParam::input(Value::Integer(v))
285 }
286}
287
288impl From<f64> for BindParam {
289 fn from(v: f64) -> Self {
290 BindParam::input(Value::Float(v))
291 }
292}
293
294impl From<&str> for BindParam {
295 fn from(v: &str) -> Self {
296 BindParam::input(Value::String(v.to_string()))
297 }
298}
299
300impl From<String> for BindParam {
301 fn from(v: String) -> Self {
302 BindParam::input(Value::String(v))
303 }
304}
305
306impl From<bool> for BindParam {
307 fn from(v: bool) -> Self {
308 BindParam::input(Value::Boolean(v))
309 }
310}
311
312#[derive(Debug, Clone)]
314pub struct ColumnInfo {
315 pub name: String,
317 pub oracle_type: OracleType,
319 pub data_size: u32,
321 pub buffer_size: u32,
323 pub precision: i16,
325 pub scale: i16,
327 pub nullable: bool,
329 pub csfrm: u8,
331 pub type_schema: Option<String>,
333 pub type_name: Option<String>,
335 pub domain_schema: Option<String>,
337 pub domain_name: Option<String>,
339 pub is_json: bool,
341 pub is_oson: bool,
343 pub vector_dimensions: Option<u32>,
345 pub vector_format: Option<u8>,
347 pub element_type: Option<OracleType>,
349}
350
351impl ColumnInfo {
352 pub fn new(name: impl Into<String>, oracle_type: OracleType) -> Self {
354 Self {
355 name: name.into(),
356 oracle_type,
357 data_size: 0,
358 buffer_size: 0,
359 precision: 0,
360 scale: 0,
361 nullable: true,
362 csfrm: 0,
363 type_schema: None,
364 type_name: None,
365 domain_schema: None,
366 domain_name: None,
367 is_json: false,
368 is_oson: false,
369 vector_dimensions: None,
370 vector_format: None,
371 element_type: None,
372 }
373 }
374
375 pub fn is_lob(&self) -> bool {
377 self.oracle_type.is_lob()
378 }
379
380 pub fn requires_no_prefetch(&self) -> bool {
382 self.oracle_type.requires_no_prefetch()
383 }
384}
385
386#[derive(Debug, Clone)]
388pub struct Statement {
389 sql: String,
391 sql_bytes: Vec<u8>,
393 statement_type: StatementType,
395 cursor_id: u16,
397 bind_info_list: Vec<BindInfo>,
399 columns: Vec<ColumnInfo>,
401 executed: bool,
403 binds_changed: bool,
405 requires_define: bool,
407 no_prefetch: bool,
409 is_returning: bool,
411}
412
413impl Statement {
414 pub fn new(sql: impl Into<String>) -> Self {
416 let sql = sql.into();
417 let sql_bytes = sql.as_bytes().to_vec();
418
419 let mut stmt = Self {
420 sql,
421 sql_bytes,
422 statement_type: StatementType::Unknown,
423 cursor_id: 0,
424 bind_info_list: Vec::new(),
425 columns: Vec::new(),
426 executed: false,
427 binds_changed: false,
428 requires_define: false,
429 no_prefetch: false,
430 is_returning: false,
431 };
432
433 stmt.parse();
434 stmt
435 }
436
437 pub fn sql(&self) -> &str {
439 &self.sql
440 }
441
442 pub fn sql_bytes(&self) -> &[u8] {
444 &self.sql_bytes
445 }
446
447 pub fn statement_type(&self) -> StatementType {
449 self.statement_type
450 }
451
452 pub fn is_query(&self) -> bool {
454 self.statement_type == StatementType::Query
455 }
456
457 pub fn is_dml(&self) -> bool {
459 self.statement_type == StatementType::Dml
460 }
461
462 pub fn is_ddl(&self) -> bool {
464 self.statement_type == StatementType::Ddl
465 }
466
467 pub fn is_plsql(&self) -> bool {
469 self.statement_type == StatementType::PlSql
470 }
471
472 pub fn is_returning(&self) -> bool {
474 self.is_returning
475 }
476
477 pub fn cursor_id(&self) -> u16 {
479 self.cursor_id
480 }
481
482 pub fn set_cursor_id(&mut self, id: u16) {
484 self.cursor_id = id;
485 }
486
487 pub fn bind_info(&self) -> &[BindInfo] {
489 &self.bind_info_list
490 }
491
492 pub fn columns(&self) -> &[ColumnInfo] {
494 &self.columns
495 }
496
497 pub fn set_columns(&mut self, columns: Vec<ColumnInfo>) {
499 self.columns = columns;
500 }
501
502 pub fn column_count(&self) -> usize {
504 self.columns.len()
505 }
506
507 pub fn executed(&self) -> bool {
509 self.executed
510 }
511
512 pub fn set_executed(&mut self, executed: bool) {
514 self.executed = executed;
515 }
516
517 pub fn binds_changed(&self) -> bool {
519 self.binds_changed
520 }
521
522 pub fn set_binds_changed(&mut self, changed: bool) {
524 self.binds_changed = changed;
525 }
526
527 pub fn requires_define(&self) -> bool {
529 self.requires_define
530 }
531
532 pub fn set_requires_define(&mut self, required: bool) {
534 self.requires_define = required;
535 }
536
537 pub fn no_prefetch(&self) -> bool {
539 self.no_prefetch
540 }
541
542 pub fn set_no_prefetch(&mut self, no_prefetch: bool) {
544 self.no_prefetch = no_prefetch;
545 }
546
547 pub fn set_statement_type(&mut self, stmt_type: StatementType) {
549 self.statement_type = stmt_type;
550 }
551
552 fn parse(&mut self) {
554 let sql_upper = self.sql.to_uppercase();
555 let trimmed = sql_upper.trim_start();
556
557 if let Some(first_word) = trimmed.split_whitespace().next() {
559 self.statement_type = match first_word {
560 "SELECT" | "WITH" => StatementType::Query,
561 "INSERT" | "UPDATE" | "DELETE" | "MERGE" => StatementType::Dml,
562 "CREATE" | "ALTER" | "DROP" | "GRANT" | "REVOKE" | "ANALYZE" | "AUDIT"
563 | "COMMENT" | "TRUNCATE" => StatementType::Ddl,
564 "DECLARE" | "BEGIN" | "CALL" => StatementType::PlSql,
565 _ => StatementType::Unknown,
566 };
567 }
568
569 if self.statement_type == StatementType::Ddl {
571 return;
572 }
573
574 self.parse_bind_variables();
576 }
577
578 fn parse_bind_variables(&mut self) {
580 let sql = &self.sql;
581 let sql_upper = sql.to_uppercase();
582 let chars: Vec<char> = sql.chars().collect();
583 let chars_upper: Vec<char> = sql_upper.chars().collect();
584 let len = chars.len();
585
586 let mut i = 0;
587 let mut in_string = false;
588 let mut in_comment = false;
589 let mut in_line_comment = false;
590 let mut returning_found = false;
591 let mut into_found = false;
592
593 while i < len {
594 let ch = chars[i];
595
596 if ch == '\'' && !in_comment && !in_line_comment {
598 in_string = !in_string;
599 i += 1;
600 continue;
601 }
602
603 if in_string {
604 i += 1;
605 continue;
606 }
607
608 if ch == '-' && i + 1 < len && chars[i + 1] == '-' {
610 in_line_comment = true;
611 i += 2;
612 continue;
613 }
614
615 if in_line_comment {
616 if ch == '\n' {
617 in_line_comment = false;
618 }
619 i += 1;
620 continue;
621 }
622
623 if ch == '/' && i + 1 < len && chars[i + 1] == '*' {
625 in_comment = true;
626 i += 2;
627 continue;
628 }
629
630 if in_comment {
631 if ch == '*' && i + 1 < len && chars[i + 1] == '/' {
632 in_comment = false;
633 i += 2;
634 } else {
635 i += 1;
636 }
637 continue;
638 }
639
640 if self.statement_type == StatementType::Dml && !returning_found {
642 if self.match_keyword(&chars_upper, i, "RETURNING") {
643 returning_found = true;
644 i += 9;
645 continue;
646 }
647 }
648
649 if returning_found && !into_found {
651 if self.match_keyword(&chars_upper, i, "INTO") {
652 into_found = true;
653 self.is_returning = true;
654 i += 4;
655 continue;
656 }
657 }
658
659 if ch == ':' && i + 1 < len {
661 let bind_name = self.extract_bind_name(&chars, i + 1);
662 if !bind_name.is_empty() {
663 let should_add = if self.statement_type == StatementType::PlSql {
665 !self.bind_info_list.iter().any(|b| b.name == bind_name)
666 } else {
667 true
668 };
669
670 if should_add {
671 self.bind_info_list
672 .push(BindInfo::new(bind_name.clone(), into_found));
673 }
674 i += 1 + bind_name.len();
675 continue;
676 }
677 }
678
679 i += 1;
680 }
681 }
684
685 fn match_keyword(&self, chars: &[char], pos: usize, keyword: &str) -> bool {
687 let keyword_chars: Vec<char> = keyword.chars().collect();
688 let len = chars.len();
689
690 if pos + keyword.len() > len {
692 return false;
693 }
694
695 if pos > 0 && chars[pos - 1].is_alphanumeric() {
697 return false;
698 }
699
700 for (i, kc) in keyword_chars.iter().enumerate() {
702 if chars[pos + i] != *kc {
703 return false;
704 }
705 }
706
707 let end_pos = pos + keyword.len();
709 if end_pos < len && chars[end_pos].is_alphanumeric() {
710 return false;
711 }
712
713 true
714 }
715
716 fn extract_bind_name(&self, chars: &[char], start: usize) -> String {
718 let len = chars.len();
719
720 let mut i = start;
722 while i < len && chars[i].is_whitespace() {
723 i += 1;
724 }
725
726 if i >= len {
727 return String::new();
728 }
729
730 let first_char = chars[i];
731
732 if first_char == '"' {
734 i += 1;
735 let name_start = i;
736 while i < len && chars[i] != '"' {
737 i += 1;
738 }
739 if i > name_start {
740 return chars[name_start..i].iter().collect();
741 }
742 return String::new();
743 }
744
745 if first_char.is_ascii_digit() {
747 let name_start = i;
748 while i < len && chars[i].is_ascii_digit() {
749 i += 1;
750 }
751 return chars[name_start..i].iter().collect();
752 }
753
754 if !first_char.is_alphabetic() {
756 return String::new();
757 }
758
759 let name_start = i;
760 while i < len {
761 let ch = chars[i];
762 if ch.is_alphanumeric() || ch == '_' || ch == '$' || ch == '#' {
763 i += 1;
764 } else {
765 break;
766 }
767 }
768
769 chars[name_start..i]
771 .iter()
772 .collect::<String>()
773 .to_uppercase()
774 }
775
776 pub fn clear(&mut self) {
778 self.cursor_id = 0;
779 self.columns.clear();
780 self.executed = false;
781 self.binds_changed = false;
782 self.requires_define = false;
783 self.no_prefetch = false;
784 }
785
786 pub fn clone_for_reuse(&self) -> Self {
792 Self {
793 sql: self.sql.clone(),
794 sql_bytes: self.sql_bytes.clone(),
795 statement_type: self.statement_type,
796 cursor_id: self.cursor_id, bind_info_list: self.bind_info_list.clone(),
798 columns: self.columns.clone(),
799 executed: self.executed,
800 binds_changed: false, requires_define: false,
802 no_prefetch: self.no_prefetch,
803 is_returning: self.is_returning,
804 }
805 }
806}
807
808#[cfg(test)]
809mod tests {
810 use super::*;
811
812 #[test]
813 fn test_statement_type_detection() {
814 assert_eq!(
815 Statement::new("SELECT * FROM dual").statement_type(),
816 StatementType::Query
817 );
818 assert_eq!(
819 Statement::new("INSERT INTO t VALUES (1)").statement_type(),
820 StatementType::Dml
821 );
822 assert_eq!(
823 Statement::new("UPDATE t SET x = 1").statement_type(),
824 StatementType::Dml
825 );
826 assert_eq!(
827 Statement::new("DELETE FROM t").statement_type(),
828 StatementType::Dml
829 );
830 assert_eq!(
831 Statement::new("CREATE TABLE t (x NUMBER)").statement_type(),
832 StatementType::Ddl
833 );
834 assert_eq!(
835 Statement::new("BEGIN NULL; END;").statement_type(),
836 StatementType::PlSql
837 );
838 assert_eq!(
839 Statement::new("DECLARE x NUMBER; BEGIN NULL; END;").statement_type(),
840 StatementType::PlSql
841 );
842 }
843
844 #[test]
845 fn test_bind_variable_extraction() {
846 let stmt = Statement::new("SELECT * FROM t WHERE x = :x AND y = :y");
847 assert_eq!(stmt.bind_info().len(), 2);
848 assert_eq!(stmt.bind_info()[0].name, "X");
849 assert_eq!(stmt.bind_info()[1].name, "Y");
850 }
851
852 #[test]
853 fn test_numeric_bind_variables() {
854 let stmt = Statement::new("SELECT * FROM t WHERE x = :1 AND y = :2");
855 assert_eq!(stmt.bind_info().len(), 2);
856 assert_eq!(stmt.bind_info()[0].name, "1");
857 assert_eq!(stmt.bind_info()[1].name, "2");
858 }
859
860 #[test]
861 fn test_duplicate_binds_plsql() {
862 let stmt = Statement::new("BEGIN :x := :x + 1; END;");
864 assert_eq!(stmt.bind_info().len(), 1);
865 assert_eq!(stmt.bind_info()[0].name, "X");
866 }
867
868 #[test]
869 fn test_duplicate_binds_sql() {
870 let stmt = Statement::new("SELECT * FROM t WHERE x = :x OR y = :x");
872 assert_eq!(stmt.bind_info().len(), 2);
873 }
874
875 #[test]
876 fn test_returning_into() {
877 let stmt = Statement::new("INSERT INTO t (x) VALUES (:val) RETURNING id INTO :id");
878 assert!(stmt.is_returning());
879 assert_eq!(stmt.bind_info().len(), 2);
880 assert!(!stmt.bind_info()[0].is_return_bind); assert!(stmt.bind_info()[1].is_return_bind); }
883
884 #[test]
885 fn test_binds_in_comments_ignored() {
886 let stmt = Statement::new("SELECT * FROM t WHERE x = :x -- AND y = :y");
887 assert_eq!(stmt.bind_info().len(), 1);
888 assert_eq!(stmt.bind_info()[0].name, "X");
889 }
890
891 #[test]
892 fn test_binds_in_strings_ignored() {
893 let stmt = Statement::new("SELECT * FROM t WHERE x = ':not_a_bind' AND y = :y");
894 assert_eq!(stmt.bind_info().len(), 1);
895 assert_eq!(stmt.bind_info()[0].name, "Y");
896 }
897
898 #[test]
899 fn test_with_query() {
900 let stmt = Statement::new("WITH cte AS (SELECT 1 x FROM dual) SELECT * FROM cte");
901 assert!(stmt.is_query());
902 }
903
904 #[test]
905 fn test_quoted_bind_name() {
906 let stmt = Statement::new("SELECT * FROM t WHERE x = :\"MyBind\"");
907 assert_eq!(stmt.bind_info().len(), 1);
908 assert_eq!(stmt.bind_info()[0].name, "MyBind");
909 }
910
911 #[test]
912 fn test_case_insensitive_keywords() {
913 assert!(Statement::new("select * from dual").is_query());
914 assert!(Statement::new("Select * From dual").is_query());
915 assert!(Statement::new("INSERT into t values (1)").is_dml());
916 }
917}