use std::collections::HashMap;
use crate::constants::OracleType;
use crate::row::Value;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CollectionType {
PlsqlIndexTable,
NestedTable,
Varray,
}
#[derive(Debug, Clone)]
pub struct DbObjectAttr {
pub name: String,
pub oracle_type: OracleType,
pub max_size: u32,
pub precision: u8,
pub scale: i8,
pub nullable: bool,
pub object_type_name: Option<String>,
}
impl DbObjectAttr {
pub fn new(name: impl Into<String>, oracle_type: OracleType) -> Self {
Self {
name: name.into(),
oracle_type,
max_size: 0,
precision: 0,
scale: 0,
nullable: true,
object_type_name: None,
}
}
pub fn with_max_size(mut self, size: u32) -> Self {
self.max_size = size;
self
}
pub fn with_precision(mut self, precision: u8, scale: i8) -> Self {
self.precision = precision;
self.scale = scale;
self
}
pub fn not_null(mut self) -> Self {
self.nullable = false;
self
}
pub fn with_object_type(mut self, type_name: impl Into<String>) -> Self {
self.object_type_name = Some(type_name.into());
self
}
}
#[derive(Debug, Clone)]
pub struct DbObjectType {
pub schema: String,
pub name: String,
pub package_name: Option<String>,
pub is_collection: bool,
pub collection_type: Option<CollectionType>,
pub element_type: Option<OracleType>,
pub element_type_name: Option<String>,
pub attributes: Vec<DbObjectAttr>,
pub oid: Option<Vec<u8>>,
}
impl DbObjectType {
pub fn new(schema: impl Into<String>, name: impl Into<String>) -> Self {
Self {
schema: schema.into(),
name: name.into(),
package_name: None,
is_collection: false,
collection_type: None,
element_type: None,
element_type_name: None,
attributes: Vec::new(),
oid: None,
}
}
pub fn collection(
schema: impl Into<String>,
name: impl Into<String>,
collection_type: CollectionType,
element_type: OracleType,
) -> Self {
Self {
schema: schema.into(),
name: name.into(),
package_name: None,
is_collection: true,
collection_type: Some(collection_type),
element_type: Some(element_type),
element_type_name: None,
attributes: Vec::new(),
oid: None,
}
}
pub fn full_name(&self) -> String {
if let Some(ref pkg) = self.package_name {
format!("{}.{}.{}", self.schema, pkg, self.name)
} else {
format!("{}.{}", self.schema, self.name)
}
}
pub fn add_attribute(&mut self, attr: DbObjectAttr) {
self.attributes.push(attr);
}
pub fn attribute(&self, name: &str) -> Option<&DbObjectAttr> {
self.attributes.iter().find(|a| a.name.eq_ignore_ascii_case(name))
}
pub fn attribute_count(&self) -> usize {
self.attributes.len()
}
}
#[derive(Debug, Clone)]
pub struct DbObject {
pub type_name: String,
pub values: HashMap<String, Value>,
pub elements: Vec<Value>,
pub is_collection: bool,
}
impl DbObject {
pub fn new(type_name: impl Into<String>) -> Self {
Self {
type_name: type_name.into(),
values: HashMap::new(),
elements: Vec::new(),
is_collection: false,
}
}
pub fn collection(type_name: impl Into<String>) -> Self {
Self {
type_name: type_name.into(),
values: HashMap::new(),
elements: Vec::new(),
is_collection: true,
}
}
pub fn set(&mut self, name: impl Into<String>, value: impl Into<Value>) {
self.values.insert(name.into().to_uppercase(), value.into());
}
pub fn get(&self, name: &str) -> Option<&Value> {
self.values.get(&name.to_uppercase())
}
pub fn has(&self, name: &str) -> bool {
self.values.contains_key(&name.to_uppercase())
}
pub fn append(&mut self, value: impl Into<Value>) {
self.elements.push(value.into());
}
pub fn get_elements(&self) -> &[Value] {
&self.elements
}
pub fn len(&self) -> usize {
if self.is_collection {
self.elements.len()
} else {
self.values.len()
}
}
pub fn is_empty(&self) -> bool {
if self.is_collection {
self.elements.is_empty()
} else {
self.values.is_empty()
}
}
}
impl From<i64> for Value {
fn from(v: i64) -> Self {
Value::Integer(v)
}
}
impl From<f64> for Value {
fn from(v: f64) -> Self {
Value::Float(v)
}
}
impl From<String> for Value {
fn from(v: String) -> Self {
Value::String(v)
}
}
impl From<&str> for Value {
fn from(v: &str) -> Self {
Value::String(v.to_string())
}
}
impl From<bool> for Value {
fn from(v: bool) -> Self {
Value::Boolean(v)
}
}
impl From<Vec<u8>> for Value {
fn from(v: Vec<u8>) -> Self {
Value::Bytes(v)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_object_type_creation() {
let obj_type = DbObjectType::new("HR", "EMPLOYEE_TYPE");
assert_eq!(obj_type.schema, "HR");
assert_eq!(obj_type.name, "EMPLOYEE_TYPE");
assert_eq!(obj_type.full_name(), "HR.EMPLOYEE_TYPE");
assert!(!obj_type.is_collection);
}
#[test]
fn test_object_type_with_attributes() {
let mut obj_type = DbObjectType::new("HR", "EMPLOYEE_TYPE");
obj_type.add_attribute(DbObjectAttr::new("ID", OracleType::Number));
obj_type.add_attribute(
DbObjectAttr::new("NAME", OracleType::Varchar).with_max_size(100),
);
assert_eq!(obj_type.attribute_count(), 2);
assert!(obj_type.attribute("ID").is_some());
assert!(obj_type.attribute("name").is_some()); }
#[test]
fn test_collection_type() {
let col_type = DbObjectType::collection(
"HR",
"NUMBER_LIST",
CollectionType::Varray,
OracleType::Number,
);
assert!(col_type.is_collection);
assert_eq!(col_type.collection_type, Some(CollectionType::Varray));
assert_eq!(col_type.element_type, Some(OracleType::Number));
}
#[test]
fn test_object_instance() {
let mut obj = DbObject::new("HR.EMPLOYEE_TYPE");
obj.set("ID", 123i64);
obj.set("NAME", "John Doe");
assert_eq!(obj.len(), 2);
assert!(obj.has("ID"));
assert!(obj.has("name")); assert!(!obj.has("MISSING"));
match obj.get("ID") {
Some(Value::Integer(v)) => assert_eq!(*v, 123),
_ => panic!("Expected Integer"),
}
}
#[test]
fn test_collection_instance() {
let mut col = DbObject::collection("HR.NUMBER_LIST");
col.append(1i64);
col.append(2i64);
col.append(3i64);
assert!(col.is_collection);
assert_eq!(col.len(), 3);
assert_eq!(col.get_elements().len(), 3);
}
#[test]
fn test_attribute_builder() {
let attr = DbObjectAttr::new("SALARY", OracleType::Number)
.with_precision(10, 2)
.not_null();
assert_eq!(attr.name, "SALARY");
assert_eq!(attr.precision, 10);
assert_eq!(attr.scale, 2);
assert!(!attr.nullable);
}
#[test]
fn test_value_from_conversions() {
assert!(matches!(Value::from(42i64), Value::Integer(42)));
assert!(matches!(Value::from(3.14f64), Value::Float(_)));
assert!(matches!(Value::from("test"), Value::String(_)));
assert!(matches!(Value::from(true), Value::Boolean(true)));
}
}