use std::fmt::{self, Debug};
use super::{
fed_auth_info::{FedAuthInfoToken, SspiToken},
login_ack::LoginAckToken,
tokenitems::ReturnValueStatus,
};
use crate::datatypes::column_values::ColumnValues;
use crate::{
error::Error,
message::login::{FeatureExtension, RoutingInfo},
query::metadata::{CekTableEntry, ColumnMetadata},
};
#[derive(Eq, PartialEq, Hash, Debug)]
#[repr(u8)]
#[allow(clippy::upper_case_acronyms)]
pub(crate) enum TokenType {
AltMetadata = 0x88,
AltRow = 0xD3,
ColMetadata = 0x81,
ColInfo = 0xA5,
Done = 0xFD,
DoneProc = 0xFE,
DoneInProc = 0xFF,
EnvChange = 0xE3,
Error = 0xAA,
FeatureExtAck = 0xAE,
FedAuthInfo = 0xEE,
Info = 0xAB,
LoginAck = 0xAD,
NbcRow = 0xD2,
Offset = 0x78,
Order = 0xA9,
ReturnStatus = 0x79,
ReturnValue = 0xAC,
Row = 0xD1,
SessionState = 0xE4,
SSPI = 0xED,
TabName = 0xA4,
}
impl TryFrom<u8> for TokenType {
type Error = crate::error::Error;
fn try_from(value: u8) -> Result<Self, <Self as TryFrom<u8>>::Error> {
match value {
0x88 => Ok(TokenType::AltMetadata),
0xD3 => Ok(TokenType::AltRow),
0x81 => Ok(TokenType::ColMetadata),
0xA5 => Ok(TokenType::ColInfo),
0xFD => Ok(TokenType::Done),
0xFE => Ok(TokenType::DoneProc),
0xFF => Ok(TokenType::DoneInProc),
0xE3 => Ok(TokenType::EnvChange),
0xAA => Ok(TokenType::Error),
0xAE => Ok(TokenType::FeatureExtAck),
0xEE => Ok(TokenType::FedAuthInfo),
0xAB => Ok(TokenType::Info),
0xAD => Ok(TokenType::LoginAck),
0xD2 => Ok(TokenType::NbcRow),
0x78 => Ok(TokenType::Offset),
0xA9 => Ok(TokenType::Order),
0x79 => Ok(TokenType::ReturnStatus),
0xAC => Ok(TokenType::ReturnValue),
0xD1 => Ok(TokenType::Row),
0xE4 => Ok(TokenType::SessionState),
0xED => Ok(TokenType::SSPI),
0xA4 => Ok(TokenType::TabName),
_ => Err(crate::error::Error::ProtocolError(format!(
"Unknown token type: {value:#X}"
))),
}
}
}
pub(crate) trait Token {
fn token_type(&self) -> TokenType;
}
#[derive(Debug)]
#[cfg(not(fuzzing))]
pub(crate) enum Tokens {
Done(DoneToken),
DoneInProc(DoneToken),
DoneProc(DoneToken),
EnvChange(EnvChangeToken),
Error(ErrorToken),
Info(InfoToken),
LoginAck(LoginAckToken),
FeatureExtAck(FeatureExtAckToken),
FedAuthInfo(FedAuthInfoToken),
SessionState(SessionStateToken),
Sspi(SspiToken),
Row(RowToken),
ColMetadata(ColMetadataToken),
Order(OrderToken),
ReturnStatus(ReturnStatusToken),
ReturnValue(ReturnValueToken),
TabName,
ColInfo,
}
#[derive(Debug)]
#[cfg(fuzzing)]
#[allow(private_interfaces)]
pub enum Tokens {
Done(DoneToken),
DoneInProc(DoneToken),
DoneProc(DoneToken),
EnvChange(EnvChangeToken),
Error(ErrorToken),
Info(InfoToken),
LoginAck(LoginAckToken),
FeatureExtAck(FeatureExtAckToken),
FedAuthInfo(FedAuthInfoToken),
SessionState(SessionStateToken),
Sspi(SspiToken),
Row(RowToken),
ColMetadata(ColMetadataToken),
Order(OrderToken),
ReturnStatus(ReturnStatusToken),
ReturnValue(ReturnValueToken),
TabName,
ColInfo,
}
macro_rules! impl_from_token {
($token_type:ty, $variant:ident) => {
impl From<$token_type> for Tokens {
fn from(token: $token_type) -> Self {
Tokens::$variant(token)
}
}
};
}
impl_from_token!(EnvChangeToken, EnvChange);
impl_from_token!(ErrorToken, Error);
impl_from_token!(InfoToken, Info);
impl_from_token!(LoginAckToken, LoginAck);
impl_from_token!(FeatureExtAckToken, FeatureExtAck);
impl_from_token!(FedAuthInfoToken, FedAuthInfo);
impl_from_token!(SspiToken, Sspi);
impl_from_token!(RowToken, Row);
impl_from_token!(ColMetadataToken, ColMetadata);
impl_from_token!(OrderToken, Order);
impl_from_token!(ReturnStatusToken, ReturnStatus);
impl_from_token!(ReturnValueToken, ReturnValue);
impl_from_token!(SessionStateToken, SessionState);
impl Token for Tokens {
fn token_type(&self) -> TokenType {
match self {
Tokens::Done(token) => token.token_type(),
Tokens::DoneInProc(token) => token.token_type(),
Tokens::DoneProc(token) => token.token_type(),
Tokens::EnvChange(token) => token.token_type(),
Tokens::Error(token) => token.token_type(),
Tokens::Info(token) => token.token_type(),
Tokens::LoginAck(token) => token.token_type(),
Tokens::FeatureExtAck(token) => token.token_type(),
Tokens::FedAuthInfo(token) => token.token_type(),
Tokens::Sspi(token) => token.token_type(),
Tokens::Row(token) => token.token_type(),
Tokens::ColMetadata(token) => token.token_type(),
Tokens::Order(token) => token.token_type(),
Tokens::ReturnStatus(token) => token.token_type(),
Tokens::ReturnValue(token) => token.token_type(),
Tokens::SessionState(token) => token.token_type(),
Tokens::TabName => TokenType::TabName,
Tokens::ColInfo => TokenType::ColInfo,
}
}
}
#[derive(Clone, PartialEq, Eq)]
pub(crate) enum EnvChangeContainer {
String(EnvChangeTokenValuePairs<String>),
SqlCollation(EnvChangeTokenValuePairs<Option<SqlCollation>>),
UInt32(EnvChangeTokenValuePairs<u32>),
RoutingType(EnvChangeTokenValuePairs<Option<RoutingInfo>>),
BytesType(EnvChangeTokenValuePairs<Vec<u8>>),
UInt64(EnvChangeTokenValuePairs<u64>),
}
impl From<(String, String)> for EnvChangeContainer {
fn from(value: (String, String)) -> Self {
EnvChangeContainer::String(EnvChangeTokenValuePairs::<String>::new(value.0, value.1))
}
}
impl From<(Option<SqlCollation>, Option<SqlCollation>)> for EnvChangeContainer {
fn from(value: (Option<SqlCollation>, Option<SqlCollation>)) -> Self {
EnvChangeContainer::SqlCollation(EnvChangeTokenValuePairs::<Option<SqlCollation>>::new(
value.0, value.1,
))
}
}
impl From<(u32, u32)> for EnvChangeContainer {
fn from(value: (u32, u32)) -> Self {
EnvChangeContainer::UInt32(EnvChangeTokenValuePairs::<u32>::new(value.0, value.1))
}
}
impl From<(Option<RoutingInfo>, Option<RoutingInfo>)> for EnvChangeContainer {
fn from(value: (Option<RoutingInfo>, Option<RoutingInfo>)) -> Self {
EnvChangeContainer::RoutingType(EnvChangeTokenValuePairs::<Option<RoutingInfo>>::new(
value.0, value.1,
))
}
}
impl From<(Vec<u8>, Vec<u8>)> for EnvChangeContainer {
fn from(value: (Vec<u8>, Vec<u8>)) -> Self {
EnvChangeContainer::BytesType(EnvChangeTokenValuePairs::<Vec<u8>>::new(value.0, value.1))
}
}
impl From<(u64, u64)> for EnvChangeContainer {
fn from(value: (u64, u64)) -> Self {
EnvChangeContainer::UInt64(EnvChangeTokenValuePairs::<u64>::new(value.0, value.1))
}
}
impl fmt::Debug for EnvChangeContainer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
EnvChangeContainer::String(value) => write!(f, "String: {value:?}"),
EnvChangeContainer::SqlCollation(value) => write!(f, "SqlCollation: {value:?}"),
EnvChangeContainer::UInt32(value) => write!(f, "UInt32: {value:?}"),
EnvChangeContainer::RoutingType(value) => write!(f, "RoutingType: {value:?}"),
EnvChangeContainer::BytesType(value) => write!(f, "ByteType: {value:?}"),
EnvChangeContainer::UInt64(value) => write!(f, "UInt64 {value:?}"),
}
}
}
#[derive(Debug)]
pub(crate) struct EnvChangeToken {
pub sub_type: EnvChangeTokenSubType,
pub change_type: EnvChangeContainer,
}
#[derive(Debug)]
pub(crate) struct FeatureExtAckToken {
features: Vec<(FeatureExtension, Vec<u8>)>,
}
impl FeatureExtAckToken {
pub(crate) fn new(features: Vec<(FeatureExtension, Vec<u8>)>) -> Self {
Self { features }
}
pub(crate) fn acknowledged_features(&self) -> &[(FeatureExtension, Vec<u8>)] {
&self.features
}
}
impl Token for EnvChangeToken {
fn token_type(&self) -> TokenType {
TokenType::EnvChange
}
}
impl Token for FeatureExtAckToken {
fn token_type(&self) -> TokenType {
TokenType::FeatureExtAck
}
}
#[derive(Debug, Clone)]
pub(crate) struct SessionStateEntry {
pub state_id: u8,
pub recoverable: bool,
pub data: Vec<u8>,
}
#[derive(Debug)]
pub(crate) struct SessionStateToken {
pub sequence_number: u32,
#[allow(dead_code)] pub status: u8,
pub states: Vec<SessionStateEntry>,
}
impl Token for SessionStateToken {
fn token_type(&self) -> TokenType {
TokenType::SessionState
}
}
#[derive(Debug, Clone, Default)]
pub(crate) struct ColMetadataToken {
pub column_count: u16,
pub columns: Vec<ColumnMetadata>,
#[allow(dead_code)] pub cek_table: Vec<CekTableEntry>,
}
impl Token for ColMetadataToken {
fn token_type(&self) -> TokenType {
TokenType::ColMetadata
}
}
#[derive(Debug, Default)]
pub(crate) struct OrderToken {
pub _order_columns: Vec<u16>,
}
impl Token for OrderToken {
fn token_type(&self) -> TokenType {
TokenType::Order
}
}
#[derive(Clone, Default, PartialEq, Eq, Copy)]
pub struct SqlCollation {
pub info: u32,
pub lcid_language_id: i32,
pub col_flags: u8,
pub sort_id: u8,
}
impl TryFrom<&[u8]> for SqlCollation {
type Error = Error;
fn try_from(collation_bytes: &[u8]) -> Result<Self, Self::Error> {
if collation_bytes.len() != 5 {
return Err(Error::Io(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!(
"Invalid collation length: {} (expected 5)",
collation_bytes.len()
),
)));
}
let info = u32::from_ne_bytes([
collation_bytes[0],
collation_bytes[1],
collation_bytes[2],
collation_bytes[3],
]);
let lcid_language_id = (info & 0x000FFFFF) as i32; let col_flags = ((info >> 20) & 0xFF) as u8; let sort_id = collation_bytes[4];
Ok(SqlCollation {
info,
lcid_language_id,
col_flags,
sort_id,
})
}
}
impl SqlCollation {
pub fn lcid_language_id(&self) -> i32 {
(self.info & 0x000FFFFF) as i32
}
pub fn comparison_style(&self) -> u8 {
((self.info >> 20) & 0xFF) as u8 }
pub fn sort_id(&self) -> u8 {
self.sort_id
}
pub fn version(&self) -> u8 {
(self.info >> 28) as u8
}
pub fn ignore_case(&self) -> bool {
(self.col_flags & 0x1) != 0
}
pub fn ignore_accent(&self) -> bool {
(self.col_flags & 0x2) != 0
}
pub fn ignore_kana(&self) -> bool {
(self.col_flags & 0x4) != 0
}
pub fn ignore_width(&self) -> bool {
(self.col_flags & 0x8) != 0
}
pub fn binary(&self) -> bool {
(self.col_flags & 0x10) != 0
}
pub fn binary2(&self) -> bool {
(self.col_flags & 0x20) != 0
}
pub fn utf8(&self) -> bool {
(self.col_flags & 0x40) != 0
}
}
impl Debug for SqlCollation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"INFO: {} LCID: {}, ComparisonStyle: {}, SortID: {}, IsUtf8: {}, IgnoreCase: {}",
self.info,
self.lcid_language_id,
self.col_flags,
self.sort_id,
self.utf8(),
self.ignore_case()
)
}
}
impl fmt::Display for SqlCollation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"INFO: {} LCID: {}, ComparisonStyle: {}, SortID: {}, IsUtf8: {}",
self.info,
self.lcid_language_id,
self.col_flags,
self.sort_id,
self.utf8()
)
}
}
#[cfg(test)]
mod sql_collation_tests {
use super::*;
#[test]
fn test_try_from_valid() {
let collation_bytes = [0x09, 0x04, 0xd0, 0x00, 0x34];
let collation: SqlCollation = collation_bytes.as_slice().try_into().unwrap();
assert_eq!(collation.sort_id, 0x34);
}
#[test]
fn test_try_from_invalid_length() {
let collation_bytes = [0x09, 0x04, 0xd0, 0x00];
let result: Result<SqlCollation, _> = collation_bytes.as_slice().try_into();
assert!(result.is_err());
let collation_bytes = [0x09, 0x04, 0xd0, 0x00, 0x34, 0xff];
let result: Result<SqlCollation, _> = collation_bytes.as_slice().try_into();
assert!(result.is_err());
let collation_bytes: &[u8] = &[];
let result: Result<SqlCollation, _> = collation_bytes.try_into();
assert!(result.is_err());
}
#[test]
fn test_collation_flags() {
let collation_bytes = [0x00, 0x00, 0x00, 0x04, 0x00];
let collation: SqlCollation = collation_bytes.as_slice().try_into().unwrap();
assert!(collation.utf8());
}
}
#[allow(dead_code)]
pub(crate) static CODE_PAGE_FROM_SORT_ID: [Option<u16>; 256] = [
None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, Some(437), Some(437), Some(437), Some(437), Some(437), None, None, None, None, None, Some(850), Some(850), Some(850), Some(850), Some(850), None, None, None, None, Some(850), Some(1252), Some(1252), Some(1252), Some(1252), Some(1252), Some(850), Some(850), Some(850), Some(850), Some(850), Some(850), Some(850), None, None, None, None, None, None, None, None, None, Some(1252), Some(1252), Some(1252), Some(1252), Some(1252), None, None, None, None, None, None, None, None, None, Some(1250), Some(1250), Some(1250), Some(1250), Some(1250), Some(1250), Some(1250), Some(1250), Some(1250), Some(1250), Some(1250), Some(1250), Some(1250), Some(1250), Some(1250), Some(1250), Some(1250), Some(1250), Some(1250), None, None, None, None, None, Some(1251), Some(1251), Some(1251), Some(1251), Some(1251), None, None, None, Some(1253), Some(1253), Some(1253), None, None, None, None, None, None, Some(1253), Some(1253), Some(1253), None, Some(1253), None, None, None, None, Some(1254), Some(1254), Some(1254), None, None, None, None, None, None, Some(1255), Some(1255), Some(1255), None, None, None, None, None, None, Some(1256), Some(1256), Some(1256), None, None, None, None, None, None, Some(1257), Some(1257), Some(1257), Some(1257), Some(1257), Some(1257), Some(1257), Some(1257), Some(1257), None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, Some(1252), Some(1252), Some(1252), Some(1252), None, None, None, None, None, Some(932), Some(932), Some(949), Some(949), Some(950), Some(950), Some(936), Some(936), Some(932), Some(949), Some(950), Some(936), Some(874), Some(874), Some(874), None, None, None, Some(1252), Some(1252), Some(1252), Some(1252), Some(1252), Some(1252), Some(1252), None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, ];
#[derive(Debug)]
pub(crate) struct ErrorToken {
pub number: u32,
pub state: u8,
pub severity: u8,
pub message: String,
pub server_name: String,
pub proc_name: String,
pub line_number: u32,
}
impl Token for ErrorToken {
fn token_type(&self) -> TokenType {
TokenType::Error
}
}
#[derive(Debug)]
#[allow(dead_code)] pub(crate) struct InfoToken {
pub number: u32,
pub state: u8,
pub severity: u8,
pub message: String,
pub server_name: String,
pub proc_name: String,
pub line_number: u32,
}
impl Token for InfoToken {
fn token_type(&self) -> TokenType {
TokenType::Info
}
}
#[derive(Debug)]
pub(crate) struct DoneToken {
pub status: DoneStatus,
pub cur_cmd: CurrentCommand,
pub row_count: u64,
}
impl Token for DoneToken {
fn token_type(&self) -> TokenType {
TokenType::Done
}
}
impl DoneToken {
pub fn has_more(&self) -> bool {
self.status.contains(DoneStatus::MORE)
}
pub fn has_error(&self) -> bool {
self.status.contains(DoneStatus::ERROR)
}
pub fn has_count(&self) -> bool {
self.status.contains(DoneStatus::COUNT)
}
}
#[derive(Debug)]
pub(crate) struct ReturnStatusToken {
pub value: i32,
}
impl Token for ReturnStatusToken {
fn token_type(&self) -> TokenType {
TokenType::ReturnStatus
}
}
#[derive(Debug)]
pub(crate) struct ReturnValueToken {
pub param_ordinal: u16,
pub param_name: String,
pub value: ColumnValues,
pub column_metadata: Box<ColumnMetadata>,
pub status: ReturnValueStatus,
}
impl Token for ReturnValueToken {
fn token_type(&self) -> TokenType {
TokenType::ReturnValue
}
}
#[derive(Debug)]
pub(crate) struct RowToken {
#[allow(dead_code)]
pub all_values: Vec<ColumnValues>,
}
impl RowToken {
pub fn new(all_values: Vec<ColumnValues>) -> Self {
Self { all_values }
}
}
impl Token for RowToken {
fn token_type(&self) -> TokenType {
TokenType::Row
}
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct DoneStatus: u16 {
const FINAL = 0x0000;
const MORE = 0x0001;
const ERROR = 0x0002;
const IN_XACT = 0x0004;
const COUNT = 0x0010;
const ATTN = 0x0020;
const RPC_IN_BATCH = 0x0080;
const SERVER_ERROR = 0x0100;
}
}
impl From<u16> for DoneStatus {
fn from(value: u16) -> Self {
DoneStatus::from_bits_truncate(value)
}
}
#[repr(u16)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) enum CurrentCommand {
None = 0x00,
Select = 0xc1,
Insert = 0xc3,
Delete = 0xc4,
Update = 0xc5,
Abort = 0xd2,
BeginXact = 0xd4,
EndXact = 0xd5,
BulkInsert = 0xf0,
OpenCursor = 0x20,
Merge = 0x117,
}
impl TryFrom<u16> for CurrentCommand {
type Error = &'static str;
fn try_from(value: u16) -> Result<Self, Self::Error> {
match value {
0xc1 => Ok(CurrentCommand::Select),
0xc3 => Ok(CurrentCommand::Insert),
0xc4 => Ok(CurrentCommand::Delete),
0xc5 => Ok(CurrentCommand::Update),
0xd2 => Ok(CurrentCommand::Abort),
0xd4 => Ok(CurrentCommand::BeginXact),
0xd5 => Ok(CurrentCommand::EndXact),
0xf0 => Ok(CurrentCommand::BulkInsert),
0x20 => Ok(CurrentCommand::OpenCursor),
0x117 => Ok(CurrentCommand::Merge),
_ => Ok(CurrentCommand::None),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum EnvChangeTokenSubType {
Database,
Language,
CharacterSet,
PacketSize,
UnicodeDataSortingLocalId,
UnicodeDataSortingComparisonFlags,
SqlCollation,
BeginTransaction,
CommitTransaction,
RollbackTransaction,
EnlistDtcTransaction,
DefectTransaction,
DatabaseMirroringPartner,
PromoteTransaction,
TransactionManagerAddress,
TransactionEnded,
ResetConnection,
UserInstanceName,
Routing,
Unknown(u8),
}
impl TryFrom<u8> for EnvChangeTokenSubType {
type Error = crate::error::Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
Ok(match value {
1 => EnvChangeTokenSubType::Database,
2 => EnvChangeTokenSubType::Language,
3 => EnvChangeTokenSubType::CharacterSet,
4 => EnvChangeTokenSubType::PacketSize,
5 => EnvChangeTokenSubType::UnicodeDataSortingLocalId,
6 => EnvChangeTokenSubType::UnicodeDataSortingComparisonFlags,
7 => EnvChangeTokenSubType::SqlCollation,
8 => EnvChangeTokenSubType::BeginTransaction,
9 => EnvChangeTokenSubType::CommitTransaction,
10 => EnvChangeTokenSubType::RollbackTransaction,
11 => EnvChangeTokenSubType::EnlistDtcTransaction,
12 => EnvChangeTokenSubType::DefectTransaction,
13 => EnvChangeTokenSubType::DatabaseMirroringPartner,
15 => EnvChangeTokenSubType::PromoteTransaction,
16 => EnvChangeTokenSubType::TransactionManagerAddress,
17 => EnvChangeTokenSubType::TransactionEnded,
18 => EnvChangeTokenSubType::ResetConnection,
19 => EnvChangeTokenSubType::UserInstanceName,
20 => EnvChangeTokenSubType::Routing,
unknown => EnvChangeTokenSubType::Unknown(unknown),
})
}
}
impl EnvChangeTokenSubType {
#[allow(dead_code)]
pub fn as_u8(&self) -> u8 {
match self {
EnvChangeTokenSubType::Database => 1,
EnvChangeTokenSubType::Language => 2,
EnvChangeTokenSubType::CharacterSet => 3,
EnvChangeTokenSubType::PacketSize => 4,
EnvChangeTokenSubType::UnicodeDataSortingLocalId => 5,
EnvChangeTokenSubType::UnicodeDataSortingComparisonFlags => 6,
EnvChangeTokenSubType::SqlCollation => 7,
EnvChangeTokenSubType::BeginTransaction => 8,
EnvChangeTokenSubType::CommitTransaction => 9,
EnvChangeTokenSubType::RollbackTransaction => 10,
EnvChangeTokenSubType::EnlistDtcTransaction => 11,
EnvChangeTokenSubType::DefectTransaction => 12,
EnvChangeTokenSubType::DatabaseMirroringPartner => 13,
EnvChangeTokenSubType::PromoteTransaction => 15,
EnvChangeTokenSubType::TransactionManagerAddress => 16,
EnvChangeTokenSubType::TransactionEnded => 17,
EnvChangeTokenSubType::ResetConnection => 18,
EnvChangeTokenSubType::UserInstanceName => 19,
EnvChangeTokenSubType::Routing => 20,
EnvChangeTokenSubType::Unknown(val) => *val,
}
}
}
#[cfg(test)]
mod env_change_tests {
use super::*;
#[test]
fn test_env_change_token_subtype_try_from() {
assert!(matches!(
EnvChangeTokenSubType::try_from(1).unwrap(),
EnvChangeTokenSubType::Database
));
assert!(matches!(
EnvChangeTokenSubType::try_from(20).unwrap(),
EnvChangeTokenSubType::Routing
));
assert!(matches!(
EnvChangeTokenSubType::try_from(30).unwrap(),
EnvChangeTokenSubType::Unknown(30)
));
assert!(matches!(
EnvChangeTokenSubType::try_from(255).unwrap(),
EnvChangeTokenSubType::Unknown(255)
));
}
#[test]
fn test_env_change_token_subtype_as_u8() {
assert_eq!(EnvChangeTokenSubType::Database.as_u8(), 1);
assert_eq!(EnvChangeTokenSubType::Routing.as_u8(), 20);
assert_eq!(EnvChangeTokenSubType::Unknown(30).as_u8(), 30);
}
}
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
pub(crate) struct EnvChangeTokenValuePairs<T> {
old_value: T,
new_value: T,
}
impl<T> EnvChangeTokenValuePairs<T> {
pub fn new(old_value: T, new_value: T) -> Self {
Self {
old_value,
new_value,
}
}
#[allow(dead_code)]
pub fn old_value(&self) -> &T {
&self.old_value
}
pub fn new_value(&self) -> &T {
&self.new_value
}
}
#[cfg(test)]
mod coverage_tests {
use super::*;
#[test]
fn token_type_try_from_all_valid() {
let cases: &[(u8, TokenType)] = &[
(0x88, TokenType::AltMetadata),
(0xD3, TokenType::AltRow),
(0x81, TokenType::ColMetadata),
(0xA5, TokenType::ColInfo),
(0xFD, TokenType::Done),
(0xFE, TokenType::DoneProc),
(0xFF, TokenType::DoneInProc),
(0xE3, TokenType::EnvChange),
(0xAA, TokenType::Error),
(0xAE, TokenType::FeatureExtAck),
(0xEE, TokenType::FedAuthInfo),
(0xAB, TokenType::Info),
(0xAD, TokenType::LoginAck),
(0xD2, TokenType::NbcRow),
(0x78, TokenType::Offset),
(0xA9, TokenType::Order),
(0x79, TokenType::ReturnStatus),
(0xAC, TokenType::ReturnValue),
(0xD1, TokenType::Row),
(0xED, TokenType::SSPI),
(0xA4, TokenType::TabName),
];
for &(byte, ref expected) in cases {
assert_eq!(&TokenType::try_from(byte).unwrap(), expected);
}
}
#[test]
fn token_type_try_from_invalid() {
assert!(TokenType::try_from(0x00).is_err());
assert!(TokenType::try_from(0x42).is_err());
}
#[test]
fn sql_collation_accessors() {
let col = SqlCollation {
info: 0x237A_BCDE,
lcid_language_id: 0xABCDE_i32,
col_flags: 0x37,
sort_id: 0x42,
};
assert_eq!(col.lcid_language_id(), 0xABCDE_i32);
assert_eq!(col.comparison_style(), 0x37);
assert_eq!(col.sort_id(), 0x42);
assert_eq!(col.version(), 0x2);
}
#[test]
fn sql_collation_flag_bits() {
let make = |flags: u8| SqlCollation {
info: 0,
lcid_language_id: 0,
col_flags: flags,
sort_id: 0,
};
assert!(make(0x01).ignore_case());
assert!(!make(0x00).ignore_case());
assert!(make(0x02).ignore_accent());
assert!(!make(0x00).ignore_accent());
assert!(make(0x04).ignore_kana());
assert!(!make(0x00).ignore_kana());
assert!(make(0x08).ignore_width());
assert!(!make(0x00).ignore_width());
assert!(make(0x10).binary());
assert!(!make(0x00).binary());
assert!(make(0x20).binary2());
assert!(!make(0x00).binary2());
assert!(make(0x40).utf8());
assert!(!make(0x00).utf8());
}
#[test]
fn sql_collation_display() {
let col = SqlCollation {
info: 0x0400_0409,
lcid_language_id: 0x0409,
col_flags: 0x40,
sort_id: 52,
};
let s = format!("{col}");
assert!(s.contains("INFO:"));
assert!(s.contains("IsUtf8: true"));
}
#[test]
fn sql_collation_debug() {
let col = SqlCollation {
info: 0x0400_0409,
lcid_language_id: 0x0409,
col_flags: 0x01,
sort_id: 0,
};
let s = format!("{col:?}");
assert!(s.contains("IgnoreCase: true"));
}
#[test]
fn env_change_container_debug_routing() {
let routing = RoutingInfo {
protocol: 0,
port: 1433,
server: "host.example.com".into(),
};
let container: EnvChangeContainer = (Some(routing), None).into();
let dbg = format!("{container:?}");
assert!(dbg.starts_with("RoutingType:"));
}
#[test]
fn env_change_container_debug_bytes() {
let container: EnvChangeContainer = (vec![1u8, 2, 3], vec![4u8, 5]).into();
let dbg = format!("{container:?}");
assert!(dbg.starts_with("ByteType:"));
}
#[test]
fn env_change_container_debug_u64() {
let container: EnvChangeContainer = (100u64, 200u64).into();
let dbg = format!("{container:?}");
assert!(dbg.starts_with("UInt64"));
}
#[test]
fn env_change_container_debug_string() {
let container: EnvChangeContainer = ("old".to_string(), "new".to_string()).into();
let dbg = format!("{container:?}");
assert!(dbg.starts_with("String:"));
}
#[test]
fn env_change_container_debug_u32() {
let container: EnvChangeContainer = (4096u32, 8192u32).into();
let dbg = format!("{container:?}");
assert!(dbg.starts_with("UInt32:"));
}
#[test]
fn env_change_container_debug_sql_collation() {
let col = SqlCollation {
info: 0,
lcid_language_id: 0,
col_flags: 0,
sort_id: 0,
};
let container: EnvChangeContainer = (Some(col), None).into();
let dbg = format!("{container:?}");
assert!(dbg.starts_with("SqlCollation:"));
}
#[test]
fn done_status_from_u16_and_flags() {
let status = DoneStatus::from(0x0001);
assert!(status.contains(DoneStatus::MORE));
let status = DoneStatus::from(0x0002);
assert!(status.contains(DoneStatus::ERROR));
let status = DoneStatus::from(0x0004);
assert!(status.contains(DoneStatus::IN_XACT));
let status = DoneStatus::from(0x0010);
assert!(status.contains(DoneStatus::COUNT));
let status = DoneStatus::from(0x0020);
assert!(status.contains(DoneStatus::ATTN));
let status = DoneStatus::from(0x0080);
assert!(status.contains(DoneStatus::RPC_IN_BATCH));
let status = DoneStatus::from(0x0100);
assert!(status.contains(DoneStatus::SERVER_ERROR));
let status = DoneStatus::from(0x0013);
assert!(status.contains(DoneStatus::MORE));
assert!(status.contains(DoneStatus::ERROR));
assert!(status.contains(DoneStatus::COUNT));
let status = DoneStatus::from(0xFFFF);
assert!(status.contains(DoneStatus::MORE | DoneStatus::ERROR | DoneStatus::SERVER_ERROR));
}
#[test]
fn done_token_helpers() {
let token = DoneToken {
status: DoneStatus::MORE | DoneStatus::ERROR,
cur_cmd: CurrentCommand::Select,
row_count: 0,
};
assert!(token.has_more());
assert!(token.has_error());
let token2 = DoneToken {
status: DoneStatus::FINAL,
cur_cmd: CurrentCommand::None,
row_count: 0,
};
assert!(!token2.has_more());
assert!(!token2.has_error());
}
#[test]
fn current_command_try_from_all_known() {
let cases: &[(u16, CurrentCommand)] = &[
(0xc1, CurrentCommand::Select),
(0xc3, CurrentCommand::Insert),
(0xc4, CurrentCommand::Delete),
(0xc5, CurrentCommand::Update),
(0xd2, CurrentCommand::Abort),
(0xd4, CurrentCommand::BeginXact),
(0xd5, CurrentCommand::EndXact),
(0xf0, CurrentCommand::BulkInsert),
(0x20, CurrentCommand::OpenCursor),
(0x117, CurrentCommand::Merge),
];
for &(val, expected) in cases {
assert_eq!(CurrentCommand::try_from(val).unwrap(), expected);
}
}
#[test]
fn msodbcsql_only_exclusions_are_not_modelled() {
assert_eq!(
CurrentCommand::try_from(0x21).unwrap(),
CurrentCommand::None
);
assert_eq!(
CurrentCommand::try_from(0xe6).unwrap(),
CurrentCommand::None
);
}
#[test]
fn current_command_try_from_unknown_maps_to_none() {
assert_eq!(
CurrentCommand::try_from(0x00).unwrap(),
CurrentCommand::None
);
assert_eq!(
CurrentCommand::try_from(0x9999).unwrap(),
CurrentCommand::None
);
}
#[test]
fn env_change_container_eq() {
let a: EnvChangeContainer = (1u64, 2u64).into();
let b: EnvChangeContainer = (1u64, 2u64).into();
let c: EnvChangeContainer = (1u64, 3u64).into();
assert_eq!(a, b);
assert_ne!(a, c);
}
#[test]
fn env_change_value_pairs_accessors() {
let pair = EnvChangeTokenValuePairs::new("old".to_string(), "new".to_string());
assert_eq!(pair.old_value(), "old");
assert_eq!(pair.new_value(), "new");
}
}