oracle_rs/
dbobject.rs

1//! Oracle database object type support
2//!
3//! This module provides types for Oracle user-defined types (UDTs), including:
4//! - Object types (CREATE TYPE)
5//! - Collection types (VARRAY, nested tables)
6//! - PL/SQL record types
7//!
8//! # Example
9//!
10//! ```rust,ignore
11//! use oracle_rs::{Connection, DbObjectType, DbObject};
12//!
13//! let conn = Connection::connect("localhost:1521/ORCLPDB1", "user", "pass").await?;
14//!
15//! // Get a type definition
16//! let obj_type = conn.get_object_type("HR.EMPLOYEE_TYPE").await?;
17//!
18//! // Create a new object
19//! let mut obj = DbObject::new(&obj_type);
20//! obj.set("FIRST_NAME", "John")?;
21//! obj.set("LAST_NAME", "Doe")?;
22//!
23//! // Use the object in a query
24//! conn.execute("INSERT INTO employees VALUES (:1)", &[&obj]).await?;
25//! ```
26
27use std::collections::HashMap;
28
29use crate::constants::OracleType;
30use crate::row::Value;
31
32/// Collection type for Oracle collections
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum CollectionType {
35    /// PL/SQL index-by table
36    PlsqlIndexTable,
37    /// Nested table
38    NestedTable,
39    /// VARRAY
40    Varray,
41}
42
43/// An attribute of a database object type
44#[derive(Debug, Clone)]
45pub struct DbObjectAttr {
46    /// Attribute name
47    pub name: String,
48    /// Oracle data type
49    pub oracle_type: OracleType,
50    /// Maximum size (for strings/raw)
51    pub max_size: u32,
52    /// Precision (for numbers)
53    pub precision: u8,
54    /// Scale (for numbers)
55    pub scale: i8,
56    /// Whether the attribute is nullable
57    pub nullable: bool,
58    /// Nested object type name (for object attributes)
59    pub object_type_name: Option<String>,
60}
61
62impl DbObjectAttr {
63    /// Create a new attribute
64    pub fn new(name: impl Into<String>, oracle_type: OracleType) -> Self {
65        Self {
66            name: name.into(),
67            oracle_type,
68            max_size: 0,
69            precision: 0,
70            scale: 0,
71            nullable: true,
72            object_type_name: None,
73        }
74    }
75
76    /// Set maximum size
77    pub fn with_max_size(mut self, size: u32) -> Self {
78        self.max_size = size;
79        self
80    }
81
82    /// Set precision and scale
83    pub fn with_precision(mut self, precision: u8, scale: i8) -> Self {
84        self.precision = precision;
85        self.scale = scale;
86        self
87    }
88
89    /// Set as not nullable
90    pub fn not_null(mut self) -> Self {
91        self.nullable = false;
92        self
93    }
94
95    /// Set nested object type
96    pub fn with_object_type(mut self, type_name: impl Into<String>) -> Self {
97        self.object_type_name = Some(type_name.into());
98        self
99    }
100}
101
102/// A database object type definition
103#[derive(Debug, Clone)]
104pub struct DbObjectType {
105    /// Schema name
106    pub schema: String,
107    /// Type name
108    pub name: String,
109    /// Package name (for PL/SQL types)
110    pub package_name: Option<String>,
111    /// Whether this is a collection type
112    pub is_collection: bool,
113    /// Collection type (if this is a collection)
114    pub collection_type: Option<CollectionType>,
115    /// Element type for collections
116    pub element_type: Option<OracleType>,
117    /// Element type name for object collections
118    pub element_type_name: Option<String>,
119    /// Attributes (for object types)
120    pub attributes: Vec<DbObjectAttr>,
121    /// OID of the type
122    pub oid: Option<Vec<u8>>,
123}
124
125impl DbObjectType {
126    /// Create a new object type
127    pub fn new(schema: impl Into<String>, name: impl Into<String>) -> Self {
128        Self {
129            schema: schema.into(),
130            name: name.into(),
131            package_name: None,
132            is_collection: false,
133            collection_type: None,
134            element_type: None,
135            element_type_name: None,
136            attributes: Vec::new(),
137            oid: None,
138        }
139    }
140
141    /// Create a collection type
142    pub fn collection(
143        schema: impl Into<String>,
144        name: impl Into<String>,
145        collection_type: CollectionType,
146        element_type: OracleType,
147    ) -> Self {
148        Self {
149            schema: schema.into(),
150            name: name.into(),
151            package_name: None,
152            is_collection: true,
153            collection_type: Some(collection_type),
154            element_type: Some(element_type),
155            element_type_name: None,
156            attributes: Vec::new(),
157            oid: None,
158        }
159    }
160
161    /// Get the fully qualified name
162    pub fn full_name(&self) -> String {
163        if let Some(ref pkg) = self.package_name {
164            format!("{}.{}.{}", self.schema, pkg, self.name)
165        } else {
166            format!("{}.{}", self.schema, self.name)
167        }
168    }
169
170    /// Add an attribute
171    pub fn add_attribute(&mut self, attr: DbObjectAttr) {
172        self.attributes.push(attr);
173    }
174
175    /// Get an attribute by name
176    pub fn attribute(&self, name: &str) -> Option<&DbObjectAttr> {
177        self.attributes.iter().find(|a| a.name.eq_ignore_ascii_case(name))
178    }
179
180    /// Get the number of attributes
181    pub fn attribute_count(&self) -> usize {
182        self.attributes.len()
183    }
184}
185
186/// An instance of a database object
187#[derive(Debug, Clone)]
188pub struct DbObject {
189    /// The object type
190    pub type_name: String,
191    /// Attribute values (for object types)
192    pub values: HashMap<String, Value>,
193    /// Element values (for collections)
194    pub elements: Vec<Value>,
195    /// Whether this is a collection
196    pub is_collection: bool,
197}
198
199impl DbObject {
200    /// Create a new object instance
201    pub fn new(type_name: impl Into<String>) -> Self {
202        Self {
203            type_name: type_name.into(),
204            values: HashMap::new(),
205            elements: Vec::new(),
206            is_collection: false,
207        }
208    }
209
210    /// Create a new collection instance
211    pub fn collection(type_name: impl Into<String>) -> Self {
212        Self {
213            type_name: type_name.into(),
214            values: HashMap::new(),
215            elements: Vec::new(),
216            is_collection: true,
217        }
218    }
219
220    /// Set an attribute value
221    pub fn set(&mut self, name: impl Into<String>, value: impl Into<Value>) {
222        self.values.insert(name.into().to_uppercase(), value.into());
223    }
224
225    /// Get an attribute value
226    pub fn get(&self, name: &str) -> Option<&Value> {
227        self.values.get(&name.to_uppercase())
228    }
229
230    /// Check if an attribute is set
231    pub fn has(&self, name: &str) -> bool {
232        self.values.contains_key(&name.to_uppercase())
233    }
234
235    /// Append an element to a collection
236    pub fn append(&mut self, value: impl Into<Value>) {
237        self.elements.push(value.into());
238    }
239
240    /// Get collection elements
241    pub fn get_elements(&self) -> &[Value] {
242        &self.elements
243    }
244
245    /// Get collection length
246    pub fn len(&self) -> usize {
247        if self.is_collection {
248            self.elements.len()
249        } else {
250            self.values.len()
251        }
252    }
253
254    /// Check if empty
255    pub fn is_empty(&self) -> bool {
256        if self.is_collection {
257            self.elements.is_empty()
258        } else {
259            self.values.is_empty()
260        }
261    }
262}
263
264impl From<i64> for Value {
265    fn from(v: i64) -> Self {
266        Value::Integer(v)
267    }
268}
269
270impl From<f64> for Value {
271    fn from(v: f64) -> Self {
272        Value::Float(v)
273    }
274}
275
276impl From<String> for Value {
277    fn from(v: String) -> Self {
278        Value::String(v)
279    }
280}
281
282impl From<&str> for Value {
283    fn from(v: &str) -> Self {
284        Value::String(v.to_string())
285    }
286}
287
288impl From<bool> for Value {
289    fn from(v: bool) -> Self {
290        Value::Boolean(v)
291    }
292}
293
294impl From<Vec<u8>> for Value {
295    fn from(v: Vec<u8>) -> Self {
296        Value::Bytes(v)
297    }
298}
299
300#[cfg(test)]
301mod tests {
302    use super::*;
303
304    #[test]
305    fn test_object_type_creation() {
306        let obj_type = DbObjectType::new("HR", "EMPLOYEE_TYPE");
307        assert_eq!(obj_type.schema, "HR");
308        assert_eq!(obj_type.name, "EMPLOYEE_TYPE");
309        assert_eq!(obj_type.full_name(), "HR.EMPLOYEE_TYPE");
310        assert!(!obj_type.is_collection);
311    }
312
313    #[test]
314    fn test_object_type_with_attributes() {
315        let mut obj_type = DbObjectType::new("HR", "EMPLOYEE_TYPE");
316        obj_type.add_attribute(DbObjectAttr::new("ID", OracleType::Number));
317        obj_type.add_attribute(
318            DbObjectAttr::new("NAME", OracleType::Varchar).with_max_size(100),
319        );
320
321        assert_eq!(obj_type.attribute_count(), 2);
322        assert!(obj_type.attribute("ID").is_some());
323        assert!(obj_type.attribute("name").is_some()); // case-insensitive
324    }
325
326    #[test]
327    fn test_collection_type() {
328        let col_type = DbObjectType::collection(
329            "HR",
330            "NUMBER_LIST",
331            CollectionType::Varray,
332            OracleType::Number,
333        );
334
335        assert!(col_type.is_collection);
336        assert_eq!(col_type.collection_type, Some(CollectionType::Varray));
337        assert_eq!(col_type.element_type, Some(OracleType::Number));
338    }
339
340    #[test]
341    fn test_object_instance() {
342        let mut obj = DbObject::new("HR.EMPLOYEE_TYPE");
343        obj.set("ID", 123i64);
344        obj.set("NAME", "John Doe");
345
346        assert_eq!(obj.len(), 2);
347        assert!(obj.has("ID"));
348        assert!(obj.has("name")); // case-insensitive
349        assert!(!obj.has("MISSING"));
350
351        match obj.get("ID") {
352            Some(Value::Integer(v)) => assert_eq!(*v, 123),
353            _ => panic!("Expected Integer"),
354        }
355    }
356
357    #[test]
358    fn test_collection_instance() {
359        let mut col = DbObject::collection("HR.NUMBER_LIST");
360        col.append(1i64);
361        col.append(2i64);
362        col.append(3i64);
363
364        assert!(col.is_collection);
365        assert_eq!(col.len(), 3);
366        assert_eq!(col.get_elements().len(), 3);
367    }
368
369    #[test]
370    fn test_attribute_builder() {
371        let attr = DbObjectAttr::new("SALARY", OracleType::Number)
372            .with_precision(10, 2)
373            .not_null();
374
375        assert_eq!(attr.name, "SALARY");
376        assert_eq!(attr.precision, 10);
377        assert_eq!(attr.scale, 2);
378        assert!(!attr.nullable);
379    }
380
381    #[test]
382    fn test_value_from_conversions() {
383        assert!(matches!(Value::from(42i64), Value::Integer(42)));
384        assert!(matches!(Value::from(3.14f64), Value::Float(_)));
385        assert!(matches!(Value::from("test"), Value::String(_)));
386        assert!(matches!(Value::from(true), Value::Boolean(true)));
387    }
388}