use asupersync::Cx;
use oracledb_protocol::thin::{BindValue, QueryValue};
use crate::Connection;
use super::collection::{execute_with_binds_raw, SodaCollection};
use super::error::{Result, SodaError};
use super::metadata::parse_metadata;
#[derive(Debug, Clone, Copy, Default)]
pub struct SodaDatabase;
impl SodaDatabase {
pub fn new() -> Self {
SodaDatabase
}
pub async fn create_collection(
&self,
conn: &mut Connection,
cx: &Cx,
name: Option<&str>,
metadata: Option<&str>,
map_mode: bool,
) -> Result<SodaCollection> {
let create_mode = if map_mode { 2 } else { 1 };
let sql = "DECLARE c SODA_COLLECTION_T; \
BEGIN c := DBMS_SODA.CREATE_COLLECTION(:1, :2, :3); END;";
let binds = vec![
match name {
Some(n) => BindValue::Text(n.to_string()),
None => BindValue::Null,
},
match metadata {
Some(m) => BindValue::Text(m.to_string()),
None => BindValue::Null,
},
BindValue::Number(create_mode.to_string()),
];
execute_with_binds_raw(conn, cx, sql, 0, &binds).await?;
let name = name.unwrap_or_default();
self.open_collection(conn, cx, name).await?.ok_or_else(|| {
SodaError::InvalidMetadata(format!("collection {name} not found after create"))
})
}
pub async fn open_collection(
&self,
conn: &mut Connection,
cx: &Cx,
name: &str,
) -> Result<Option<SodaCollection>> {
let sql = "SELECT JSON_SERIALIZE(JSON_DESCRIPTOR) \
FROM USER_SODA_COLLECTIONS WHERE URI_NAME = :1";
let binds = vec![BindValue::Text(name.to_string())];
let result = execute_with_binds_raw(conn, cx, sql, 2, &binds).await?;
let Some(cell) = result.cell(0, 0) else {
return Ok(None);
};
let descriptor_text = cell
.as_text()
.ok_or_else(|| SodaError::InvalidMetadata("descriptor is not text".to_string()))?;
let descriptor: serde_json::Value = serde_json::from_str(descriptor_text)
.map_err(|e| SodaError::InvalidMetadata(format!("bad descriptor JSON: {e}")))?;
let meta = parse_metadata(&descriptor)?;
Ok(Some(SodaCollection::new(name.to_string(), meta)))
}
pub async fn get_collection_names(
&self,
conn: &mut Connection,
cx: &Cx,
start_name: Option<&str>,
limit: u32,
) -> Result<Vec<String>> {
let mut sql = String::from("SELECT URI_NAME FROM USER_SODA_COLLECTIONS");
let mut binds = Vec::new();
if let Some(start) = start_name {
sql.push_str(" WHERE URI_NAME >= :1");
binds.push(BindValue::Text(start.to_string()));
}
sql.push_str(" ORDER BY URI_NAME");
if limit > 0 {
sql.push_str(&format!(" FETCH FIRST {limit} ROWS ONLY"));
}
let result = execute_with_binds_raw(conn, cx, &sql, limit.max(100), &binds).await?;
let mut names = Vec::with_capacity(result.rows.len());
for row in &result.rows {
if let Some(name) = row
.first()
.and_then(|c| c.as_ref())
.and_then(QueryValue::as_text)
{
names.push(name.to_string());
}
}
Ok(names)
}
pub async fn drop_collection(
&self,
conn: &mut Connection,
cx: &Cx,
name: &str,
) -> Result<bool> {
let sql = "BEGIN :1 := DBMS_SODA.DROP_COLLECTION(:2); END;";
let binds = vec![
BindValue::Output {
ora_type_num: 2, csfrm: 0,
buffer_size: 22,
},
BindValue::Text(name.to_string()),
];
let result = execute_with_binds_raw(conn, cx, sql, 0, &binds).await?;
let dropped = result
.out_values
.first()
.and_then(|(_, v)| v.as_ref())
.and_then(QueryValue::as_i64)
.map(|n| n != 0)
.unwrap_or(false);
Ok(dropped)
}
}