#![allow(non_upper_case_globals)]
use crate::util::*;
use bytes::{BufMut, Bytes, BytesMut};
use rsfbclient_core::{ibase, FbError, StmtType};
use std::{convert::TryFrom, mem};
use crate::consts;
pub const XSQLDA_DESCRIBE_VARS: [u8; 17] = [
ibase::isc_info_sql_stmt_type as u8, ibase::isc_info_sql_bind as u8, ibase::isc_info_sql_describe_vars as u8, ibase::isc_info_sql_describe_end as u8, ibase::isc_info_sql_select as u8, ibase::isc_info_sql_describe_vars as u8, ibase::isc_info_sql_sqlda_seq as u8, ibase::isc_info_sql_type as u8, ibase::isc_info_sql_sub_type as u8, ibase::isc_info_sql_scale as u8, ibase::isc_info_sql_length as u8, ibase::isc_info_sql_null_ind as u8, ibase::isc_info_sql_field as u8, ibase::isc_info_sql_relation as u8, ibase::isc_info_sql_owner as u8, ibase::isc_info_sql_alias as u8, ibase::isc_info_sql_describe_end as u8, ];
#[derive(Debug, Default)]
pub struct XSqlVar {
pub sqltype: i16,
pub scale: i16,
pub sqlsubtype: i16,
pub data_length: i16,
pub null_ind: bool,
pub field_name: String,
pub relation_name: String,
pub owner_name: String,
pub alias_name: String,
}
impl XSqlVar {
pub fn coerce(&mut self) -> Result<(), FbError> {
let sqltype = self.sqltype & (!1);
let sqlsubtype = self.sqlsubtype;
match sqltype as u32 {
ibase::SQL_TEXT | ibase::SQL_VARYING => {
self.sqltype = ibase::SQL_VARYING as i16 + 1;
}
ibase::SQL_SHORT | ibase::SQL_LONG | ibase::SQL_INT64 => {
self.data_length = mem::size_of::<i64>() as i16;
if self.scale == 0 {
self.sqltype = ibase::SQL_INT64 as i16 + 1;
} else {
self.scale = 0;
self.sqltype = ibase::SQL_DOUBLE as i16 + 1;
}
}
ibase::SQL_FLOAT | ibase::SQL_DOUBLE => {
self.data_length = mem::size_of::<i64>() as i16;
self.sqltype = ibase::SQL_DOUBLE as i16 + 1;
}
ibase::SQL_TIMESTAMP | ibase::SQL_TYPE_DATE | ibase::SQL_TYPE_TIME => {
self.data_length = mem::size_of::<ibase::ISC_TIMESTAMP>() as i16;
self.sqltype = ibase::SQL_TIMESTAMP as i16 + 1;
}
ibase::SQL_BLOB if (sqlsubtype == 0 || sqlsubtype == 1) => {
self.sqltype = ibase::SQL_BLOB as i16 + 1;
}
ibase::SQL_BOOLEAN => {
self.sqltype = ibase::SQL_BOOLEAN as i16 + 1;
}
sqltype => {
return Err(format!("Unsupported column type ({})", sqltype).into());
}
}
Ok(())
}
}
pub fn xsqlda_to_blr(xsqlda: &[XSqlVar]) -> Result<Bytes, FbError> {
let mut blr = BytesMut::with_capacity(256);
blr.put_slice(&[
consts::blr::VERSION5,
consts::blr::BEGIN,
consts::blr::MESSAGE,
0, ]);
blr.put_u16_le(xsqlda.len() as u16 * 2);
for var in xsqlda {
let sqltype = var.sqltype as u32 & (!1);
match sqltype as u32 {
ibase::SQL_VARYING => {
blr.put_u8(consts::blr::VARYING);
blr.put_i16_le(var.data_length);
}
ibase::SQL_INT64 => blr.put_slice(&[
consts::blr::INT64,
0, ]),
ibase::SQL_DOUBLE => blr.put_u8(consts::blr::DOUBLE),
ibase::SQL_TIMESTAMP => blr.put_u8(consts::blr::TIMESTAMP),
ibase::SQL_BLOB => blr.put_slice(&[consts::blr::QUAD, var.sqlsubtype as u8]),
ibase::SQL_BOOLEAN => blr.put_u8(consts::blr::BOOL),
sqltype => {
return Err(format!("Conversion from sql type {} not implemented", sqltype).into());
}
}
blr.put_slice(&[consts::blr::SHORT, 0]);
}
blr.put_slice(&[consts::blr::END, consts::blr::EOC]);
Ok(blr.freeze())
}
pub struct PrepareInfo {
pub stmt_type: StmtType,
pub param_count: usize,
pub truncated: bool,
}
pub fn parse_xsqlda(resp: &mut Bytes, xsqlda: &mut Vec<XSqlVar>) -> Result<PrepareInfo, FbError> {
if resp.remaining() < 7 || resp[..3] != [ibase::isc_info_sql_stmt_type as u8, 0x04, 0x00] {
return err_invalid_xsqlda();
}
resp.advance(3)?;
let stmt_type =
StmtType::try_from(resp.get_u32_le()? as u8).map_err(|e| FbError::Other(e.to_string()))?;
if resp.remaining() < 8
|| resp[..2]
!= [
ibase::isc_info_sql_bind as u8, ibase::isc_info_sql_describe_vars as u8, ]
{
return err_invalid_xsqlda();
}
resp.advance(2)?;
resp.advance(2)?;
let param_count = resp.get_u32_le()? as usize;
while resp.remaining() > 0 && resp[0] == ibase::isc_info_sql_describe_end as u8 {
resp.advance(1)?;
}
if resp.remaining() < 8
|| resp[..2]
!= [
ibase::isc_info_sql_select as u8, ibase::isc_info_sql_describe_vars as u8, ]
{
return err_invalid_xsqlda();
}
resp.advance(2)?;
resp.advance(2)?;
let col_len = resp.get_u32_le()? as usize;
if col_len > 1024 {
return err_invalid_xsqlda();
}
if xsqlda.is_empty() {
xsqlda.reserve(col_len);
}
let truncated = parse_select_items(resp, xsqlda)?;
Ok(PrepareInfo {
stmt_type,
param_count,
truncated,
})
}
pub fn parse_select_items(resp: &mut Bytes, xsqlda: &mut Vec<XSqlVar>) -> Result<bool, FbError> {
if resp.remaining() == 0 {
return Ok(false);
}
let mut col_index = 0;
let truncated = loop {
match resp.get_u8()? as u32 {
ibase::isc_info_sql_sqlda_seq => {
resp.advance(2)?;
let col = resp.get_u32_le()?;
if col == 0 {
return err_invalid_xsqlda();
}
col_index = col as usize - 1;
if col_index >= xsqlda.len() {
xsqlda.push(Default::default());
#[cfg(not(feature = "fuzz_testing"))]
debug_assert_eq!(xsqlda.len() - 1, col_index);
}
}
ibase::isc_info_sql_type => {
resp.advance(2)?;
if let Some(var) = xsqlda.get_mut(col_index) {
var.sqltype = resp.get_i32_le()? as i16;
} else {
return err_invalid_xsqlda();
}
}
ibase::isc_info_sql_sub_type => {
resp.advance(2)?;
if let Some(var) = xsqlda.get_mut(col_index) {
var.sqlsubtype = resp.get_i32_le()? as i16;
} else {
return err_invalid_xsqlda();
}
}
ibase::isc_info_sql_scale => {
resp.advance(2)?;
if let Some(var) = xsqlda.get_mut(col_index) {
var.scale = resp.get_i32_le()? as i16;
} else {
return err_invalid_xsqlda();
}
}
ibase::isc_info_sql_length => {
resp.advance(2)?;
if let Some(var) = xsqlda.get_mut(col_index) {
var.data_length = resp.get_i32_le()? as i16;
} else {
return err_invalid_xsqlda();
}
}
ibase::isc_info_sql_null_ind => {
resp.advance(2)?;
if let Some(var) = xsqlda.get_mut(col_index) {
var.null_ind = resp.get_i32_le()? != 0;
} else {
return err_invalid_xsqlda();
}
}
ibase::isc_info_sql_field => {
let len = resp.get_u16_le()? as usize;
let mut buff = vec![0; len];
resp.copy_to_slice(&mut buff)?;
if let Some(var) = xsqlda.get_mut(col_index) {
var.field_name = String::from_utf8(buff).unwrap_or_default();
} else {
return err_invalid_xsqlda();
}
}
ibase::isc_info_sql_relation => {
let len = resp.get_u16_le()? as usize;
let mut buff = vec![0; len];
resp.copy_to_slice(&mut buff)?;
if let Some(var) = xsqlda.get_mut(col_index) {
var.relation_name = String::from_utf8(buff).unwrap_or_default();
} else {
return err_invalid_xsqlda();
}
}
ibase::isc_info_sql_owner => {
let len = resp.get_u16_le()? as usize;
let mut buff = vec![0; len];
resp.copy_to_slice(&mut buff)?;
if let Some(var) = xsqlda.get_mut(col_index) {
var.owner_name = String::from_utf8(buff).unwrap_or_default();
} else {
return err_invalid_xsqlda();
}
}
ibase::isc_info_sql_alias => {
let len = resp.get_u16_le()? as usize;
let mut buff = vec![0; len];
resp.copy_to_slice(&mut buff)?;
if let Some(var) = xsqlda.get_mut(col_index) {
var.alias_name = String::from_utf8(buff).unwrap_or_default();
} else {
return err_invalid_xsqlda();
}
}
ibase::isc_info_sql_describe_end => {}
ibase::isc_info_truncated => break true,
ibase::isc_info_end => break false,
item => {
return Err(FbError::Other(format!(
"Invalid item received in the xsqlda: {}",
item
)));
}
}
};
Ok(truncated)
}
fn err_invalid_xsqlda<T>() -> Result<T, FbError> {
Err(FbError::Other(
"Invalid Xsqlda received from server".to_string(),
))
}