#![forbid(unsafe_code)]
use super::*;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct AcceptInfo {
pub protocol_version: u16,
pub protocol_options: u16,
pub sdu: u32,
pub supports_fast_auth: bool,
pub supports_oob_check: bool,
pub supports_oob: bool,
pub supports_end_of_response: bool,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct AuthResponse {
pub session_data: BTreeMap<String, String>,
pub verifier_type: Option<u32>,
pub capabilities: Option<ClientCapabilities>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ClientCapabilities {
pub ttc_field_version: u8,
pub max_string_size: u32,
pub charset_id: u16,
}
impl Default for ClientCapabilities {
fn default() -> Self {
Self {
ttc_field_version: 24,
max_string_size: 32_767,
charset_id: 873,
}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct ColumnMetadata {
pub name: String,
pub ora_type_num: u8,
pub csfrm: u8,
pub precision: i8,
pub scale: i8,
pub buffer_size: u32,
pub max_size: u32,
pub nulls_allowed: bool,
pub is_json: bool,
pub is_oson: bool,
pub object_schema: Option<String>,
pub object_type_name: Option<String>,
pub is_array: bool,
pub vector_dimensions: Option<u32>,
pub vector_format: u8,
pub vector_flags: u8,
pub domain_schema: Option<String>,
pub domain_name: Option<String>,
pub annotations: Option<Vec<(String, String)>>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct BindTypeInfo {
pub ora_type_num: u8,
pub csfrm: u8,
pub buffer_size: u32,
}
#[derive(Clone, Debug, PartialEq)]
pub struct CursorValue {
pub columns: Vec<ColumnMetadata>,
pub cursor_id: u32,
}
#[derive(Clone, Debug, PartialEq)]
pub struct ObjectValue {
pub schema: Option<String>,
pub type_name: Option<String>,
pub packed_data: Vec<u8>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct LobValue {
pub ora_type_num: u8,
pub csfrm: u8,
pub locator: Vec<u8>,
pub size: u64,
pub chunk_size: u32,
}
#[derive(Clone, Debug, PartialEq)]
pub enum QueryValue {
Text(String),
TextRaw {
bytes: Vec<u8>,
csfrm: u8,
},
Raw(Vec<u8>),
Rowid(String),
BinaryDouble(String),
IntervalDS {
days: i32,
hours: i32,
minutes: i32,
seconds: i32,
fseconds: i32,
},
IntervalYM {
years: i32,
months: i32,
},
Number(OracleNumber),
Boolean(bool),
Cursor(Box<CursorValue>),
DateTime {
year: i32,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
nanosecond: u32,
},
Object(Box<ObjectValue>),
Lob(Box<LobValue>),
Vector(Box<crate::vector::Vector>),
Json(Box<crate::oson::OsonValue>),
Array(Vec<Option<QueryValue>>),
}
const _: () = assert!(core::mem::size_of::<QueryValue>() <= 32);
const _: () = assert!(core::mem::size_of::<OracleNumber>() <= 24);
impl QueryValue {
pub fn number_from_text(text: &str, is_integer: bool) -> Self {
QueryValue::Number(OracleNumber::from_canonical_text_with_flag(
text, is_integer,
))
}
pub fn as_text(&self) -> Option<&str> {
match self {
QueryValue::Text(value) => Some(value.as_str()),
_ => None,
}
}
pub fn as_i64(&self) -> Option<i64> {
match self {
QueryValue::Number(num) => num
.to_i64()
.or_else(|| num.to_canonical_string().parse::<i64>().ok()),
QueryValue::Boolean(value) => Some(i64::from(*value)),
_ => None,
}
}
pub fn as_f64(&self) -> Option<f64> {
match self {
QueryValue::Number(num) => num.to_canonical_string().parse::<f64>().ok(),
QueryValue::BinaryDouble(text) => text.parse::<f64>().ok(),
_ => None,
}
}
pub fn as_number_text(&self) -> Option<std::borrow::Cow<'_, str>> {
match self {
QueryValue::Number(num) => Some(num.to_canonical_cow()),
_ => None,
}
}
pub fn as_number(&self) -> Option<&OracleNumber> {
match self {
QueryValue::Number(num) => Some(num),
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match self {
QueryValue::Boolean(value) => Some(*value),
_ => None,
}
}
pub fn as_raw(&self) -> Option<&[u8]> {
match self {
QueryValue::Raw(bytes) => Some(bytes.as_slice()),
_ => None,
}
}
pub fn as_rowid(&self) -> Option<&str> {
match self {
QueryValue::Rowid(value) => Some(value.as_str()),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum QueryValueRef<'buf> {
Text(&'buf str),
Raw(&'buf [u8]),
Number {
text: &'buf str,
is_integer: bool,
},
Boolean(bool),
IntervalDS {
days: i32,
hours: i32,
minutes: i32,
seconds: i32,
fseconds: i32,
},
IntervalYM {
years: i32,
months: i32,
},
DateTime {
year: i32,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
nanosecond: u32,
},
Owned(&'buf QueryValue),
}
impl QueryValueRef<'_> {
pub fn to_owned_value(&self) -> QueryValue {
match *self {
QueryValueRef::Text(text) => QueryValue::Text(text.to_string()),
QueryValueRef::Raw(bytes) => QueryValue::Raw(bytes.to_vec()),
QueryValueRef::Number { text, is_integer } => QueryValue::Number(
OracleNumber::from_canonical_text_with_flag(text, is_integer),
),
QueryValueRef::Boolean(value) => QueryValue::Boolean(value),
QueryValueRef::IntervalDS {
days,
hours,
minutes,
seconds,
fseconds,
} => QueryValue::IntervalDS {
days,
hours,
minutes,
seconds,
fseconds,
},
QueryValueRef::IntervalYM { years, months } => QueryValue::IntervalYM { years, months },
QueryValueRef::DateTime {
year,
month,
day,
hour,
minute,
second,
nanosecond,
} => QueryValue::DateTime {
year,
month,
day,
hour,
minute,
second,
nanosecond,
},
QueryValueRef::Owned(value) => value.clone(),
}
}
pub fn as_text(&self) -> Option<&str> {
match self {
QueryValueRef::Text(value) => Some(value),
QueryValueRef::Owned(QueryValue::Text(value)) => Some(value.as_str()),
_ => None,
}
}
pub fn as_number_text(&self) -> Option<&str> {
match self {
QueryValueRef::Number { text, .. } => Some(text),
QueryValueRef::Owned(QueryValue::Number(num)) => num.as_borrowed_text(),
_ => None,
}
}
pub fn as_raw(&self) -> Option<&[u8]> {
match self {
QueryValueRef::Raw(bytes) => Some(bytes),
QueryValueRef::Owned(QueryValue::Raw(bytes)) => Some(bytes.as_slice()),
_ => None,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum BindValue {
Null,
TypedNull {
ora_type_num: u8,
csfrm: u8,
buffer_size: u32,
},
Output {
ora_type_num: u8,
csfrm: u8,
buffer_size: u32,
},
ReturnOutput {
ora_type_num: u8,
csfrm: u8,
buffer_size: u32,
},
ObjectOutput {
schema: String,
type_name: String,
oid: Vec<u8>,
version: u32,
buffer_size: u32,
is_return: bool,
},
ObjectInput {
schema: String,
type_name: String,
oid: Vec<u8>,
version: u32,
image: Vec<u8>,
buffer_size: u32,
},
Text(String),
Raw(Vec<u8>),
Lob {
ora_type_num: u8,
csfrm: u8,
locator: Vec<u8>,
},
Number(String),
BinaryInteger(String),
BinaryDouble(f64),
BinaryFloat(f64),
Boolean(bool),
IntervalDS {
days: i32,
seconds: i32,
microseconds: i32,
},
IntervalYM {
years: i32,
months: i32,
},
DateTime {
year: i32,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
},
Timestamp {
ora_type_num: u8,
year: i32,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
nanosecond: u32,
},
Array {
ora_type_num: u8,
csfrm: u8,
buffer_size: u32,
max_elements: u32,
values: Vec<Option<BindValue>>,
},
Vector(crate::vector::Vector),
Json(Vec<u8>),
Cursor {
cursor_id: u32,
},
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct QueryResult {
pub columns: Vec<ColumnMetadata>,
pub rows: Vec<Vec<Option<QueryValue>>>,
pub out_values: Vec<(usize, Option<QueryValue>)>,
pub return_values: Vec<(usize, Vec<Option<QueryValue>>)>,
pub cursor_id: u32,
pub row_count: u64,
pub more_rows: bool,
pub compilation_error_warning: bool,
pub last_rowid: Option<String>,
pub batch_errors: Vec<BatchServerError>,
pub array_dml_row_counts: Option<Vec<u64>>,
pub implicit_resultsets: Option<Vec<QueryValue>>,
pub token_num: Option<u64>,
pub sessionless_txn_state: Option<SessionlessTxnState>,
pub query_id: Option<u64>,
pub txn_in_progress: Option<bool>,
}
impl QueryResult {
pub fn cell(&self, row: usize, col: usize) -> Option<&QueryValue> {
self.rows.get(row)?.get(col)?.as_ref()
}
pub fn column_index(&self, name: &str) -> Option<usize> {
self.columns
.iter()
.position(|col| col.name.eq_ignore_ascii_case(name))
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct LobReadResult {
pub data: Option<Vec<u8>>,
pub locator: Vec<u8>,
pub amount: u64,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SessionlessTxnState {
Set { started_on_server: bool },
Unset,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct TpcSwitchResponse {
pub context: Vec<u8>,
pub txn_in_progress: bool,
pub sessionless_state: Option<SessionlessTxnState>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct TpcChangeStateResponse {
pub state: u32,
pub txn_in_progress: bool,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ExecuteOptions {
pub batcherrors: bool,
pub arraydmlrowcounts: bool,
pub parse_only: bool,
pub token_num: u64,
pub cursor_id: u32,
pub cache_statement: bool,
pub scrollable: bool,
pub fetch_orientation: u32,
pub fetch_pos: u32,
pub scroll_operation: bool,
pub suspend_on_success: bool,
pub no_prefetch: bool,
pub registration_id: u64,
}
impl Default for ExecuteOptions {
fn default() -> Self {
Self {
batcherrors: false,
arraydmlrowcounts: false,
parse_only: false,
token_num: 0,
cursor_id: 0,
cache_statement: true,
scrollable: false,
fetch_orientation: 0,
fetch_pos: 0,
scroll_operation: false,
suspend_on_success: false,
no_prefetch: false,
registration_id: 0,
}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct BatchServerError {
pub code: u32,
pub offset: u32,
pub message: String,
}
#[cfg(test)]
mod accessor_tests {
use super::*;
#[test]
fn query_value_typed_accessors() {
let text = QueryValue::Text("hello".to_string());
assert_eq!(text.as_text(), Some("hello"));
assert_eq!(text.as_i64(), None);
let int = QueryValue::number_from_text("42", true);
assert_eq!(int.as_i64(), Some(42));
assert_eq!(int.as_f64(), Some(42.0));
assert_eq!(int.as_number_text().as_deref(), Some("42"));
let dbl = QueryValue::BinaryDouble("2.5".to_string());
assert_eq!(dbl.as_f64(), Some(2.5));
let boolean = QueryValue::Boolean(true);
assert_eq!(boolean.as_bool(), Some(true));
assert_eq!(boolean.as_i64(), Some(1));
let raw = QueryValue::Raw(vec![0xDE, 0xAD]);
assert_eq!(raw.as_raw(), Some([0xDE, 0xAD].as_slice()));
let rowid = QueryValue::Rowid("AAAR".to_string());
assert_eq!(rowid.as_rowid(), Some("AAAR"));
}
#[test]
fn query_result_cell_and_column_index() {
let result = QueryResult {
columns: vec![
ColumnMetadata {
name: "ID".to_string(),
..ColumnMetadata::default()
},
ColumnMetadata {
name: "NAME".to_string(),
..ColumnMetadata::default()
},
],
rows: vec![vec![Some(QueryValue::number_from_text("7", true)), None]],
..QueryResult::default()
};
assert_eq!(result.cell(0, 0).and_then(QueryValue::as_i64), Some(7));
assert!(result.cell(0, 1).is_none(), "NULL cell is None");
assert!(result.cell(1, 0).is_none(), "out-of-range row is None");
assert_eq!(result.column_index("name"), Some(1));
assert_eq!(result.column_index("missing"), None);
}
}
#[cfg(test)]
mod query_value_ref_tests {
use super::*;
#[test]
fn borrowed_scalars_to_owned_equal_owned_values() {
let buf = String::from("héllo");
let text = QueryValueRef::Text(buf.as_str());
assert_eq!(text.to_owned_value(), QueryValue::Text("héllo".to_string()));
let raw_buf = [0xDEu8, 0xAD, 0xBE, 0xEF];
let raw = QueryValueRef::Raw(&raw_buf);
assert_eq!(
raw.to_owned_value(),
QueryValue::Raw(vec![0xDE, 0xAD, 0xBE, 0xEF])
);
let num_buf = String::from("-12.5");
let number = QueryValueRef::Number {
text: num_buf.as_str(),
is_integer: false,
};
assert_eq!(
number.to_owned_value(),
QueryValue::number_from_text("-12.5", false)
);
let boolean = QueryValueRef::Boolean(true);
assert_eq!(boolean.to_owned_value(), QueryValue::Boolean(true));
let ds = QueryValueRef::IntervalDS {
days: 1,
hours: 2,
minutes: 3,
seconds: 4,
fseconds: 5,
};
assert_eq!(
ds.to_owned_value(),
QueryValue::IntervalDS {
days: 1,
hours: 2,
minutes: 3,
seconds: 4,
fseconds: 5,
}
);
}
#[test]
fn query_value_ref_is_small_and_copy() {
const fn is_copy<T: Copy>() {}
is_copy::<QueryValueRef<'static>>();
assert!(core::mem::size_of::<QueryValueRef<'static>>() <= 32);
}
}