use asupersync::Cx;
use oracledb_protocol::thin::{BindValue, QueryResult, QueryValue};
use crate::Connection;
use super::collection::{execute_with_binds_raw, finish_cursor_operation, 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?;
conn.release_cursor(result.cursor_id);
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 array_size = limit.max(100);
let result = execute_with_binds_raw(conn, cx, &sql, array_size, &binds).await?;
let result = collect_owned_query_result(conn, cx, array_size, result).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)
}
}
pub(super) async fn collect_owned_query_result(
conn: &mut Connection,
cx: &Cx,
array_size: u32,
mut result: QueryResult,
) -> Result<QueryResult> {
let cursor_id = result.cursor_id;
let columns = result.columns.clone();
let mut previous_row = result.rows.last().cloned();
while cursor_id != 0 && result.more_rows {
if let Err(err) = crate::observe_cancellation_between_round_trips(cx) {
conn.release_cursor(cursor_id);
return Err(SodaError::Driver(err));
}
let fetched = conn
.fetch_rows_with_columns(cx, cursor_id, array_size, &columns, previous_row.as_deref())
.await;
let fetched = finish_cursor_operation(conn, cursor_id, fetched)?;
result.more_rows = fetched.more_rows;
if let Some(last) = fetched.rows.last() {
previous_row = Some(last.clone());
}
result.rows.extend(fetched.rows);
}
conn.release_cursor(cursor_id);
Ok(result)
}