use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct LoginInitRequest {
pub command: String,
pub protocol_version: i32,
}
impl LoginInitRequest {
pub fn new() -> Self {
Self {
command: "login".to_string(),
protocol_version: 3,
}
}
}
impl Default for LoginInitRequest {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PublicKeyResponse {
pub status: String,
pub response_data: Option<PublicKeyData>,
pub exception: Option<ExceptionInfo>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PublicKeyData {
pub public_key_pem: String,
pub public_key_modulus: String,
pub public_key_exponent: String,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AuthRequest {
pub username: String,
pub password: String,
pub use_compression: bool,
pub client_name: String,
pub driver_name: String,
pub client_version: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub client_os_username: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub attributes: Option<HashMap<String, serde_json::Value>>,
}
impl AuthRequest {
pub fn new(username: String, encrypted_password: String, client_name: String) -> Self {
Self {
username,
password: encrypted_password,
use_compression: false,
client_name: client_name.clone(),
driver_name: client_name,
client_version: env!("CARGO_PKG_VERSION").to_string(),
client_os_username: None,
attributes: None,
}
}
pub fn with_driver_name(mut self, driver_name: String) -> Self {
self.driver_name = driver_name;
self
}
pub fn with_client_version(mut self, version: String) -> Self {
self.client_version = version;
self
}
pub fn with_os_username(mut self, username: String) -> Self {
self.client_os_username = Some(username);
self
}
pub fn with_attributes(mut self, attributes: HashMap<String, serde_json::Value>) -> Self {
self.attributes = Some(attributes);
self
}
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct LoginRequest {
pub command: String,
pub protocol_version: i32,
#[serde(flatten)]
pub credentials: Credentials,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Credentials {
pub username: String,
pub password: String,
}
impl LoginRequest {
pub fn new(username: String, password: String) -> Self {
Self {
command: "login".to_string(),
protocol_version: 3,
credentials: Credentials { username, password },
}
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LoginResponse {
pub status: String,
pub response_data: Option<LoginResponseData>,
pub exception: Option<ExceptionInfo>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LoginResponseData {
pub session_id: i64,
pub protocol_version: i32,
pub release_version: String,
pub database_name: String,
pub product_name: String,
pub max_data_message_size: Option<i64>,
pub max_varchar_size: Option<i64>,
pub max_identifier_length: Option<i64>,
pub identifier_quote_string: Option<String>,
pub time_zone: Option<String>,
pub time_zone_behavior: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ExecuteRequest {
pub command: String,
pub sql_text: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub result_set_max_rows: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub attributes: Option<HashMap<String, String>>,
}
impl ExecuteRequest {
pub fn new(sql: String) -> Self {
Self {
command: "execute".to_string(),
sql_text: sql,
result_set_max_rows: None,
attributes: None,
}
}
pub fn with_max_rows(mut self, max_rows: i64) -> Self {
self.result_set_max_rows = Some(max_rows);
self
}
pub fn with_attribute(mut self, key: String, value: String) -> Self {
self.attributes
.get_or_insert_with(HashMap::new)
.insert(key, value);
self
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ExecuteResponse {
pub status: String,
pub response_data: Option<ExecuteResponseData>,
pub exception: Option<ExceptionInfo>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ExecuteResponseData {
pub num_results: i32,
pub results: Vec<ResultSetInfo>,
pub attributes: Option<HashMap<String, String>>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ResultSetInfo {
pub result_type: String,
pub row_count: Option<i64>,
pub result_set: Option<ResultSetData>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ResultSetData {
pub result_set_handle: Option<i32>,
pub num_columns: Option<i32>,
pub num_rows: Option<i64>,
pub num_rows_in_message: Option<i64>,
pub columns: Option<Vec<ColumnInfo>>,
#[serde(default, deserialize_with = "super::deserialize::to_row_major_option")]
pub data: Option<Vec<Vec<serde_json::Value>>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ColumnInfo {
pub name: String,
pub data_type: DataType,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DataType {
#[serde(rename = "type")]
pub type_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub precision: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub scale: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub size: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub character_set: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub with_local_time_zone: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fraction: Option<i32>,
}
impl DataType {
pub fn decimal(precision: i32, scale: i32) -> Self {
Self {
type_name: "DECIMAL".to_string(),
precision: Some(precision),
scale: Some(scale),
size: None,
character_set: None,
with_local_time_zone: None,
fraction: None,
}
}
pub fn double() -> Self {
Self {
type_name: "DOUBLE".to_string(),
precision: None,
scale: None,
size: None,
character_set: None,
with_local_time_zone: None,
fraction: None,
}
}
pub fn varchar(size: i64) -> Self {
Self {
type_name: "VARCHAR".to_string(),
precision: None,
scale: None,
size: Some(size),
character_set: Some("UTF8".to_string()),
with_local_time_zone: None,
fraction: None,
}
}
pub fn boolean() -> Self {
Self {
type_name: "BOOLEAN".to_string(),
precision: None,
scale: None,
size: None,
character_set: None,
with_local_time_zone: None,
fraction: None,
}
}
pub fn infer_from_json(value: &serde_json::Value) -> Self {
match value {
serde_json::Value::Null => Self::varchar(2_000_000),
serde_json::Value::Bool(_) => Self::boolean(),
serde_json::Value::Number(n) => {
if n.is_f64() {
Self::double()
} else {
Self::decimal(18, 0)
}
}
serde_json::Value::String(_) => Self::varchar(2_000_000),
serde_json::Value::Array(_) | serde_json::Value::Object(_) => Self::varchar(2_000_000),
}
}
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FetchRequest {
pub command: String,
pub result_set_handle: i32,
pub start_position: i64,
pub num_bytes: i64,
}
impl FetchRequest {
pub fn new(result_set_handle: i32, start_position: i64, num_bytes: i64) -> Self {
Self {
command: "fetch".to_string(),
result_set_handle,
start_position,
num_bytes,
}
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FetchResponse {
pub status: String,
pub response_data: Option<FetchResponseData>,
pub exception: Option<ExceptionInfo>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FetchResponseData {
pub num_rows: i64,
#[serde(deserialize_with = "super::deserialize::to_row_major")]
pub data: Vec<Vec<serde_json::Value>>,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CloseResultSetRequest {
pub command: String,
pub result_set_handles: Vec<i32>,
}
impl CloseResultSetRequest {
pub fn new(handles: Vec<i32>) -> Self {
Self {
command: "closeResultSet".to_string(),
result_set_handles: handles,
}
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CloseResultSetResponse {
pub status: String,
pub exception: Option<ExceptionInfo>,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CreatePreparedStatementRequest {
pub command: String,
pub sql_text: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub attributes: Option<serde_json::Value>,
}
impl CreatePreparedStatementRequest {
pub fn new(sql: impl Into<String>) -> Self {
Self {
command: "createPreparedStatement".to_string(),
sql_text: sql.into(),
attributes: None,
}
}
pub fn with_attributes(mut self, attributes: serde_json::Value) -> Self {
self.attributes = Some(attributes);
self
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ParameterInfo {
#[serde(default)]
pub name: Option<String>,
pub data_type: DataType,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ParameterData {
pub num_columns: i32,
pub columns: Vec<ParameterInfo>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreatePreparedStatementResponse {
pub status: String,
pub response_data: Option<PreparedStatementResponseData>,
pub exception: Option<ExceptionInfo>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PreparedStatementResponseData {
pub statement_handle: i32,
pub parameter_data: Option<ParameterData>,
pub num_results: Option<i32>,
pub results: Option<Vec<ResultSetInfo>>,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ExecutePreparedStatementRequest {
pub command: String,
pub statement_handle: i32,
pub num_columns: i32,
pub num_rows: i32,
#[serde(skip_serializing_if = "Option::is_none")]
pub columns: Option<Vec<ColumnInfo>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Vec<Vec<serde_json::Value>>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub attributes: Option<serde_json::Value>,
}
impl ExecutePreparedStatementRequest {
pub fn new(statement_handle: i32) -> Self {
Self {
command: "executePreparedStatement".to_string(),
statement_handle,
num_columns: 0,
num_rows: 0,
columns: None,
data: None,
attributes: None,
}
}
pub fn with_data(
mut self,
columns: Vec<ColumnInfo>,
data: Vec<Vec<serde_json::Value>>,
) -> Self {
self.num_columns = columns.len() as i32;
self.num_rows = if data.is_empty() {
0
} else {
data[0].len() as i32
};
self.columns = Some(columns);
self.data = Some(data);
self
}
pub fn with_attributes(mut self, attributes: serde_json::Value) -> Self {
self.attributes = Some(attributes);
self
}
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ClosePreparedStatementRequest {
pub command: String,
pub statement_handle: i32,
}
impl ClosePreparedStatementRequest {
pub fn new(statement_handle: i32) -> Self {
Self {
command: "closePreparedStatement".to_string(),
statement_handle,
}
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ClosePreparedStatementResponse {
pub status: String,
pub exception: Option<ExceptionInfo>,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DisconnectRequest {
pub command: String,
}
impl DisconnectRequest {
pub fn new() -> Self {
Self {
command: "disconnect".to_string(),
}
}
}
impl Default for DisconnectRequest {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DisconnectResponse {
pub status: String,
pub exception: Option<ExceptionInfo>,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SetAttributesRequest {
pub command: String,
pub attributes: std::collections::HashMap<String, serde_json::Value>,
}
impl SetAttributesRequest {
pub fn new(attributes: std::collections::HashMap<String, serde_json::Value>) -> Self {
Self {
command: "setAttributes".to_string(),
attributes,
}
}
pub fn autocommit(enabled: bool) -> Self {
let mut attributes = std::collections::HashMap::new();
attributes.insert("autocommit".to_string(), serde_json::Value::Bool(enabled));
Self::new(attributes)
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SetAttributesResponse {
pub status: String,
pub exception: Option<ExceptionInfo>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ExceptionInfo {
pub sql_code: Option<String>,
pub text: String,
}
#[derive(Debug, Clone)]
pub struct SessionInfo {
pub session_id: String,
pub protocol_version: i32,
pub release_version: String,
pub database_name: String,
pub product_name: String,
pub max_data_message_size: i64,
pub time_zone: Option<String>,
}
impl From<LoginResponseData> for SessionInfo {
fn from(data: LoginResponseData) -> Self {
Self {
session_id: data.session_id.to_string(),
protocol_version: data.protocol_version,
release_version: data.release_version,
database_name: data.database_name,
product_name: data.product_name,
max_data_message_size: data.max_data_message_size.unwrap_or(1024 * 1024), time_zone: data.time_zone,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ResultSetHandle(pub i32);
impl ResultSetHandle {
pub fn new(handle: i32) -> Self {
Self(handle)
}
pub fn as_i32(&self) -> i32 {
self.0
}
}
impl From<i32> for ResultSetHandle {
fn from(handle: i32) -> Self {
Self(handle)
}
}
#[derive(Debug, Clone)]
pub struct ResultData {
pub columns: Vec<ColumnInfo>,
pub data: Vec<Vec<serde_json::Value>>,
pub total_rows: i64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_login_init_request_serialization() {
let request = LoginInitRequest::new();
let json = serde_json::to_string(&request).unwrap();
assert!(json.contains("\"command\":\"login\""));
assert!(json.contains("\"protocolVersion\":3"));
assert!(!json.contains("username"));
assert!(!json.contains("password"));
}
#[test]
fn test_public_key_response_deserialization() {
let json = r#"{
"status": "ok",
"responseData": {
"publicKeyPem": "-----BEGIN RSA PUBLIC KEY-----\nMIIBCg...\n-----END RSA PUBLIC KEY-----",
"publicKeyModulus": "abc123",
"publicKeyExponent": "010001"
}
}"#;
let response: PublicKeyResponse = serde_json::from_str(json).unwrap();
assert_eq!(response.status, "ok");
let data = response.response_data.unwrap();
assert!(data.public_key_pem.contains("BEGIN RSA PUBLIC KEY"));
assert_eq!(data.public_key_modulus, "abc123");
assert_eq!(data.public_key_exponent, "010001");
}
#[test]
fn test_auth_request_serialization() {
let request = AuthRequest::new(
"sys".to_string(),
"encrypted_password_base64".to_string(),
"exarrow-rs".to_string(),
);
let json = serde_json::to_string(&request).unwrap();
assert!(json.contains("\"username\":\"sys\""));
assert!(json.contains("\"password\":\"encrypted_password_base64\""));
assert!(json.contains("\"useCompression\":false"));
assert!(json.contains("\"clientName\":\"exarrow-rs\""));
assert!(json.contains("\"driverName\":\"exarrow-rs\""));
assert!(json.contains("\"clientVersion\":"));
assert!(!json.contains("\"attributes\":null"));
}
#[test]
fn test_login_request_serialization() {
let request = LoginRequest::new("sys".to_string(), "exasol".to_string());
let json = serde_json::to_string(&request).unwrap();
assert!(json.contains("\"command\":\"login\""));
assert!(json.contains("\"username\":\"sys\""));
assert!(json.contains("\"password\":\"exasol\""));
assert!(json.contains("\"protocolVersion\":3"));
}
#[test]
fn test_execute_request_serialization() {
let request = ExecuteRequest::new("SELECT * FROM test".to_string())
.with_max_rows(1000)
.with_attribute("autocommit".to_string(), "true".to_string());
let json = serde_json::to_string(&request).unwrap();
assert!(json.contains("\"command\":\"execute\""));
assert!(json.contains("SELECT * FROM test"));
assert!(json.contains("\"resultSetMaxRows\":1000"));
assert!(json.contains("\"autocommit\""));
}
#[test]
fn test_fetch_request_serialization() {
let request = FetchRequest::new(1, 0, 1024 * 1024);
let json = serde_json::to_string(&request).unwrap();
assert!(json.contains("\"command\":\"fetch\""));
assert!(json.contains("\"resultSetHandle\":1"));
assert!(json.contains("\"startPosition\":0"));
}
#[test]
fn test_disconnect_request_serialization() {
let request = DisconnectRequest::new();
let json = serde_json::to_string(&request).unwrap();
assert!(json.contains("\"command\":\"disconnect\""));
}
#[test]
fn test_login_response_deserialization() {
let json = r#"{
"status": "ok",
"responseData": {
"sessionId": 1234567890,
"protocolVersion": 3,
"releaseVersion": "8.0.0",
"databaseName": "MYDB",
"productName": "Exasol",
"maxDataMessageSize": 2097152,
"timeZone": "UTC"
}
}"#;
let response: LoginResponse = serde_json::from_str(json).unwrap();
assert_eq!(response.status, "ok");
let data = response.response_data.unwrap();
assert_eq!(data.session_id, 1234567890);
assert_eq!(data.protocol_version, 3);
assert_eq!(data.release_version, "8.0.0");
assert_eq!(data.database_name, "MYDB");
}
#[test]
fn test_execute_response_deserialization() {
let json = r#"{
"status": "ok",
"responseData": {
"numResults": 1,
"results": [
{
"resultType": "resultSet",
"resultSet": {
"resultSetHandle": 1,
"numColumns": 2,
"numRows": 2,
"numRowsInMessage": 2,
"columns": [
{
"name": "ID",
"dataType": {
"type": "DECIMAL",
"precision": 18,
"scale": 0
}
},
{
"name": "NAME",
"dataType": {
"type": "VARCHAR",
"size": 100,
"characterSet": "UTF8"
}
}
],
"data": [
[1, 2],
["Alice", "Bob"]
]
}
}
]
}
}"#;
let response: ExecuteResponse = serde_json::from_str(json).unwrap();
assert_eq!(response.status, "ok");
let data = response.response_data.unwrap();
assert_eq!(data.num_results, 1);
assert_eq!(data.results.len(), 1);
let result = &data.results[0];
assert_eq!(result.result_type, "resultSet");
let result_set = result.result_set.as_ref().unwrap();
assert_eq!(result_set.result_set_handle.unwrap(), 1);
assert_eq!(result_set.num_rows.unwrap(), 2);
let columns = result_set.columns.as_ref().unwrap();
assert_eq!(columns.len(), 2);
assert_eq!(columns[0].name, "ID");
assert_eq!(columns[0].data_type.type_name, "DECIMAL");
assert_eq!(columns[1].name, "NAME");
assert_eq!(columns[1].data_type.type_name, "VARCHAR");
let data_cols = result_set.data.as_ref().unwrap();
assert_eq!(data_cols.len(), 2);
assert_eq!(data_cols[0].len(), 2); assert_eq!(data_cols[1].len(), 2); }
#[test]
fn test_error_response_deserialization() {
let json = r#"{
"status": "error",
"exception": {
"sqlCode": "42000",
"text": "Syntax error at position 10"
}
}"#;
let response: ExecuteResponse = serde_json::from_str(json).unwrap();
assert_eq!(response.status, "error");
let exception = response.exception.unwrap();
assert_eq!(exception.sql_code.unwrap(), "42000");
assert!(exception.text.contains("Syntax error"));
}
#[test]
fn test_result_set_handle() {
let handle = ResultSetHandle::new(42);
assert_eq!(handle.as_i32(), 42);
let handle2: ResultSetHandle = 42.into();
assert_eq!(handle, handle2);
}
#[test]
fn test_session_info_from_login_response() {
let response_data = LoginResponseData {
session_id: 1234567890,
protocol_version: 3,
release_version: "8.0.0".to_string(),
database_name: "TEST_DB".to_string(),
product_name: "Exasol".to_string(),
max_data_message_size: Some(5242880),
max_varchar_size: None,
max_identifier_length: None,
identifier_quote_string: None,
time_zone: Some("Europe/Berlin".to_string()),
time_zone_behavior: None,
};
let session_info: SessionInfo = response_data.into();
assert_eq!(session_info.session_id, "1234567890");
assert_eq!(session_info.protocol_version, 3);
assert_eq!(session_info.max_data_message_size, 5242880);
assert_eq!(session_info.time_zone.unwrap(), "Europe/Berlin");
}
#[test]
fn test_create_prepared_statement_request_serialization() {
let request = CreatePreparedStatementRequest::new("SELECT * FROM test WHERE id = ?");
let json = serde_json::to_string(&request).unwrap();
assert!(json.contains("\"command\":\"createPreparedStatement\""));
assert!(json.contains("\"sqlText\":\"SELECT * FROM test WHERE id = ?\""));
assert!(!json.contains("\"attributes\":null"));
}
#[test]
fn test_create_prepared_statement_response_deserialization() {
let json = r#"{
"status": "ok",
"responseData": {
"statementHandle": 42,
"parameterData": {
"numColumns": 1,
"columns": [
{
"dataType": {
"type": "DECIMAL",
"precision": 18,
"scale": 0
}
}
]
}
}
}"#;
let response: CreatePreparedStatementResponse = serde_json::from_str(json).unwrap();
assert_eq!(response.status, "ok");
let data = response.response_data.unwrap();
assert_eq!(data.statement_handle, 42);
let param_data = data.parameter_data.unwrap();
assert_eq!(param_data.num_columns, 1);
assert_eq!(param_data.columns.len(), 1);
assert_eq!(param_data.columns[0].data_type.type_name, "DECIMAL");
}
#[test]
fn test_execute_prepared_statement_request_serialization() {
let columns = vec![ColumnInfo {
name: "ID".to_string(),
data_type: DataType {
type_name: "DECIMAL".to_string(),
precision: Some(18),
scale: Some(0),
size: None,
character_set: None,
with_local_time_zone: None,
fraction: None,
},
}];
let data = vec![vec![serde_json::json!(1), serde_json::json!(2)]];
let request = ExecutePreparedStatementRequest::new(42).with_data(columns, data);
let json = serde_json::to_string(&request).unwrap();
assert!(json.contains("\"command\":\"executePreparedStatement\""));
assert!(json.contains("\"statementHandle\":42"));
assert!(json.contains("\"numColumns\":1"));
assert!(json.contains("\"numRows\":2"));
assert!(json.contains("\"columns\""));
assert!(json.contains("\"data\""));
}
#[test]
fn test_execute_prepared_statement_request_no_params() {
let request = ExecutePreparedStatementRequest::new(42);
let json = serde_json::to_string(&request).unwrap();
assert!(json.contains("\"command\":\"executePreparedStatement\""));
assert!(json.contains("\"statementHandle\":42"));
assert!(json.contains("\"numColumns\":0"));
assert!(json.contains("\"numRows\":0"));
assert!(!json.contains("\"columns\":null"));
assert!(!json.contains("\"data\":null"));
}
#[test]
fn test_close_prepared_statement_request_serialization() {
let request = ClosePreparedStatementRequest::new(42);
let json = serde_json::to_string(&request).unwrap();
assert!(json.contains("\"command\":\"closePreparedStatement\""));
assert!(json.contains("\"statementHandle\":42"));
}
#[test]
fn test_close_prepared_statement_response_deserialization() {
let json = r#"{
"status": "ok"
}"#;
let response: ClosePreparedStatementResponse = serde_json::from_str(json).unwrap();
assert_eq!(response.status, "ok");
assert!(response.exception.is_none());
}
#[test]
fn test_close_prepared_statement_error_response() {
let json = r#"{
"status": "error",
"exception": {
"sqlCode": "00000",
"text": "Invalid statement handle"
}
}"#;
let response: ClosePreparedStatementResponse = serde_json::from_str(json).unwrap();
assert_eq!(response.status, "error");
let exception = response.exception.unwrap();
assert!(exception.text.contains("Invalid statement handle"));
}
#[test]
fn test_data_type_decimal() {
let dt = DataType::decimal(18, 0);
assert_eq!(dt.type_name, "DECIMAL");
assert_eq!(dt.precision, Some(18));
assert_eq!(dt.scale, Some(0));
assert!(dt.size.is_none());
}
#[test]
fn test_data_type_double() {
let dt = DataType::double();
assert_eq!(dt.type_name, "DOUBLE");
assert!(dt.precision.is_none());
assert!(dt.scale.is_none());
}
#[test]
fn test_data_type_varchar() {
let dt = DataType::varchar(2_000_000);
assert_eq!(dt.type_name, "VARCHAR");
assert_eq!(dt.size, Some(2_000_000));
assert_eq!(dt.character_set, Some("UTF8".to_string()));
}
#[test]
fn test_data_type_boolean() {
let dt = DataType::boolean();
assert_eq!(dt.type_name, "BOOLEAN");
}
#[test]
fn test_data_type_infer_from_json_null() {
let dt = DataType::infer_from_json(&serde_json::Value::Null);
assert_eq!(dt.type_name, "VARCHAR");
assert_eq!(dt.size, Some(2_000_000));
}
#[test]
fn test_data_type_infer_from_json_bool() {
let dt = DataType::infer_from_json(&serde_json::json!(true));
assert_eq!(dt.type_name, "BOOLEAN");
}
#[test]
fn test_data_type_infer_from_json_integer() {
let dt = DataType::infer_from_json(&serde_json::json!(42));
assert_eq!(dt.type_name, "DECIMAL");
assert_eq!(dt.precision, Some(18));
assert_eq!(dt.scale, Some(0));
}
#[test]
fn test_data_type_infer_from_json_float() {
let dt = DataType::infer_from_json(&serde_json::json!(3.125));
assert_eq!(dt.type_name, "DOUBLE");
}
#[test]
fn test_data_type_infer_from_json_string() {
let dt = DataType::infer_from_json(&serde_json::json!("hello"));
assert_eq!(dt.type_name, "VARCHAR");
assert_eq!(dt.size, Some(2_000_000));
}
#[test]
fn test_data_type_infer_from_json_array() {
let dt = DataType::infer_from_json(&serde_json::json!([1, 2, 3]));
assert_eq!(dt.type_name, "VARCHAR");
}
#[test]
fn test_data_type_infer_from_json_object() {
let dt = DataType::infer_from_json(&serde_json::json!({"key": "value"}));
assert_eq!(dt.type_name, "VARCHAR");
}
}