mod bind_info;
mod cache;
mod holder;
mod options;
mod public;
mod sql_parser;
use std::collections::HashMap;
use std::collections::HashSet;
use crate::bind_params::BindParameters;
use crate::client::Client;
use crate::constants;
use crate::db_value::ToDbValue;
use crate::error::Error;
use crate::metadata::Metadata;
use crate::response::Response;
#[derive(Clone)]
pub(crate) struct CachedStatement {
sql: String,
cursor_id: u16,
no_prefetch: bool,
requires_define: bool,
is_query: bool,
is_plsql: bool,
is_ddl: bool,
is_dml: bool,
is_returning: bool,
binds: Vec<BindInfo>,
bind_names: Vec<String>,
bind_names_set: HashSet<String>,
binds_changed: bool,
out_metadata: Vec<Metadata>,
cache_slot_num: usize,
is_nested: bool,
options: StatementOptions,
}
impl CachedStatement {
fn add_bind(&mut self, name: String) {
let exists = self.bind_names_set.contains(&name);
if !self.is_plsql || !exists {
self.binds.push(BindInfo {
name: name.clone(),
is_return_bind: self.is_returning,
metadata: None,
bind_direction: constants::TTC_BIND_DIR_INPUT,
});
if !exists {
self.bind_names.push(name.clone());
self.bind_names_set.insert(name);
}
}
}
fn determine_statement_type(&mut self, keyword: &str) {
match keyword.to_uppercase().as_str() {
"DECLARE" | "BEGIN" | "CALL" => {
self.is_plsql = true;
}
"SELECT" | "WITH" => {
self.is_query = true;
}
"INSERT" | "UPDATE" | "DELETE" | "MERGE" => {
self.is_dml = true;
}
"CREATE" | "ALTER" | "DROP" | "GRANT" | "REVOKE" | "ANALYZE"
| "AUDIT" | "COMMENT" | "TRUNCATE" => {
self.is_ddl = true;
}
_ => {}
}
}
pub(crate) fn binds(&self) -> &Vec<BindInfo> {
&self.binds
}
pub(crate) fn binds_changed(&self) -> bool {
self.binds_changed
}
pub(crate) fn cache_slot_num(&self) -> usize {
self.cache_slot_num
}
pub(crate) fn check_binds(
&mut self,
params: &BindParameters,
) -> Result<(), Error> {
params.validate(&mut self.binds)?;
if !self.is_query {
self.out_metadata.clear();
}
for bind_info in self.binds.iter() {
if bind_info.is_output_bind() {
self.out_metadata.push(bind_info.metadata.clone().unwrap());
}
}
Ok(())
}
pub(crate) fn check_named_binds<'a>(
&mut self,
params: &[(&str, &'a dyn ToDbValue)],
) -> Result<Vec<&'a dyn ToDbValue>, Error> {
let mut bind_map: HashMap<String, &dyn ToDbValue> = HashMap::new();
for (name, value) in params.iter() {
let normalized_name =
if name.starts_with('"') && name.ends_with('"') {
name[1..name.len() - 1].to_string()
} else {
name.to_uppercase()
};
bind_map.insert(normalized_name, *value);
}
if bind_map.len() != self.bind_names.len() {
for name in bind_map.keys() {
if !self.bind_names_set.contains(name) {
return Err(Error::invalid_bind_name(name));
}
}
}
let mut checked_binds: Vec<&dyn ToDbValue> = Vec::new();
for bind_info in self.binds.iter_mut() {
if let Some(param) = bind_map.get(&bind_info.name) {
checked_binds.push(*param);
} else {
return Err(Error::missing_bind_value(&bind_info.name));
}
}
Ok(checked_binds)
}
pub(crate) fn clear_cursor(&mut self) {
self.cursor_id = 0;
}
pub(crate) fn clear_requires_define(&mut self) {
self.requires_define = false;
}
pub(crate) fn clone_with_options(
&self,
options: &StatementOptions,
) -> Self {
let mut cloned = self.clone();
cloned.options = options.clone();
cloned
}
pub(crate) fn create_empty(
sql: String,
is_nested: bool,
options: &StatementOptions,
) -> Self {
let is_query = sql.is_empty();
Self {
sql,
cursor_id: 0,
no_prefetch: false,
requires_define: false,
is_query,
is_plsql: false,
is_ddl: false,
is_dml: false,
is_returning: false,
binds: Vec::new(),
bind_names: Vec::new(),
bind_names_set: HashSet::new(),
binds_changed: false,
out_metadata: Vec::new(),
cache_slot_num: 0,
is_nested,
options: options.clone(),
}
}
pub(crate) fn cursor_id(&self) -> u16 {
self.cursor_id
}
pub(crate) fn has_binds(&self) -> bool {
!self.binds.is_empty()
}
pub(crate) fn has_cursor(&self) -> bool {
self.cursor_id != 0
}
pub(crate) fn has_input_binds(&self) -> bool {
for bind_info in &self.binds {
if bind_info.is_input_bind() {
return true;
}
}
false
}
pub(crate) fn is_cached(&self) -> bool {
self.cache_slot_num != 0
}
pub(crate) fn is_ddl(&self) -> bool {
self.is_ddl
}
pub(crate) fn is_nested(&self) -> bool {
self.is_nested
}
pub(crate) fn is_plsql(&self) -> bool {
self.is_plsql
}
pub(crate) fn is_query(&self) -> bool {
self.is_query
}
pub(crate) fn new(
sql: &str,
options: &StatementOptions,
) -> Result<Self, Error> {
let mut statement = Self::create_empty(sql.into(), false, options);
sql_parser::SqlParser::new(sql).parse(&mut statement)?;
Ok(statement)
}
pub(crate) fn no_prefetch(&self) -> bool {
self.no_prefetch
}
pub(crate) fn options(&self) -> &StatementOptions {
&self.options
}
pub(crate) fn out_metadata(&self) -> &Vec<Metadata> {
&self.out_metadata
}
pub(crate) fn populate_from_describe_info(
&mut self,
client: &Client,
resp: &mut Response,
) -> Result<(), Error> {
resp.read_ub4()?; let num_columns = resp.read_ub4()?;
if num_columns > 0 {
resp.read_u8()?;
}
self.out_metadata.clear();
for _ in 0..num_columns {
let mut metadata = Metadata::from_response(resp, client)?;
if metadata.requires_define() {
self.requires_define = true;
self.no_prefetch = true;
if !self.options.fetch_lobs() {
metadata = metadata.define_metadata();
}
}
self.out_metadata.push(metadata);
}
let _current_date = resp.read_bytes_with_double_length()?;
let _dcbflag = resp.read_ub4()?;
let _dcbmdbz = resp.read_ub4()?;
let _dcbmnpr = resp.read_ub4()?;
let _dcbmxpr = resp.read_ub4()?;
let _dcbqcky = resp.read_bytes_with_double_length()?;
Ok(())
}
pub(crate) fn requires_define(&self) -> bool {
self.requires_define
}
pub(crate) fn requires_single_execute(&self) -> bool {
self.is_plsql && (self.cursor_id == 0 || self.binds_changed)
}
pub(crate) fn set_bind_directions(
&mut self,
resp: &mut Response,
) -> Result<(), Error> {
self.out_metadata.clear();
for bind_info in self.binds.iter_mut() {
bind_info.bind_direction = resp.read_u8()?;
if bind_info.is_output_bind() {
let metadata = bind_info.metadata.as_ref().unwrap();
self.out_metadata.push(metadata.clone());
}
}
Ok(())
}
pub(crate) fn set_cache_slot_num(&mut self, slot_num: usize) {
self.cache_slot_num = slot_num;
}
pub(crate) fn set_cursor_id(&mut self, cursor_id: u16) {
self.cursor_id = cursor_id;
}
pub(crate) fn sql(&self) -> &str {
&self.sql
}
pub(crate) fn sql_len(&self) -> u32 {
self.sql.len().try_into().unwrap()
}
}
pub(crate) use bind_info::BindInfo;
pub(crate) use cache::StatementCache;
pub(crate) use holder::StatementHolder;
pub(crate) use options::StatementOptions;
pub use public::Statement;