rsfbclient_rust/
xsqlda.rs1#![allow(non_upper_case_globals)]
4
5use crate::util::*;
6use bytes::{BufMut, Bytes, BytesMut};
7use rsfbclient_core::{ibase, FbError, StmtType};
8use std::{convert::TryFrom, mem};
9
10use crate::consts;
11
12pub const XSQLDA_DESCRIBE_VARS: [u8; 17] = [
14 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, ];
32
33#[derive(Debug, Default)]
34pub struct XSqlVar {
36 pub sqltype: i16,
38
39 pub scale: i16,
41
42 pub sqlsubtype: i16,
44
45 pub data_length: i16,
47
48 pub null_ind: bool,
50
51 pub field_name: String,
52
53 pub relation_name: String,
54
55 pub owner_name: String,
56
57 pub alias_name: String,
59}
60
61impl XSqlVar {
62 pub fn coerce(&mut self) -> Result<(), FbError> {
64 let sqltype = self.sqltype & (!1);
66 let sqlsubtype = self.sqlsubtype;
67
68 match sqltype as u32 {
71 ibase::SQL_TEXT | ibase::SQL_VARYING => {
72 self.sqltype = ibase::SQL_VARYING as i16 + 1;
73 }
74
75 ibase::SQL_SHORT | ibase::SQL_LONG | ibase::SQL_INT64 => {
76 self.data_length = mem::size_of::<i64>() as i16;
77
78 if self.scale == 0 {
79 self.sqltype = ibase::SQL_INT64 as i16 + 1;
80 } else {
81 self.scale = 0;
83 self.sqltype = ibase::SQL_DOUBLE as i16 + 1;
84 }
85 }
86
87 ibase::SQL_FLOAT | ibase::SQL_DOUBLE => {
88 self.data_length = mem::size_of::<i64>() as i16;
89
90 self.sqltype = ibase::SQL_DOUBLE as i16 + 1;
91 }
92
93 ibase::SQL_TIMESTAMP | ibase::SQL_TYPE_DATE | ibase::SQL_TYPE_TIME => {
94 self.data_length = mem::size_of::<ibase::ISC_TIMESTAMP>() as i16;
95
96 self.sqltype = ibase::SQL_TIMESTAMP as i16 + 1;
97 }
98
99 ibase::SQL_BLOB if sqlsubtype <= 1 => {
100 self.sqltype = ibase::SQL_BLOB as i16 + 1;
101 }
102
103 ibase::SQL_BOOLEAN => {
104 self.sqltype = ibase::SQL_BOOLEAN as i16 + 1;
105 }
106
107 sqltype => {
108 return Err(format!("Unsupported column type ({})", sqltype).into());
109 }
110 }
111
112 Ok(())
113 }
114}
115
116pub fn xsqlda_to_blr(xsqlda: &[XSqlVar]) -> Result<Bytes, FbError> {
118 let mut blr = BytesMut::with_capacity(256);
119 blr.put_slice(&[
120 consts::blr::VERSION5,
121 consts::blr::BEGIN,
122 consts::blr::MESSAGE,
123 0, ]);
125 blr.put_u16_le(xsqlda.len() as u16 * 2);
127
128 for var in xsqlda {
129 let sqltype = var.sqltype as u32 & (!1);
131
132 match sqltype as u32 {
133 ibase::SQL_VARYING => {
134 blr.put_u8(consts::blr::VARYING);
135 blr.put_i16_le(var.data_length);
136 }
137
138 ibase::SQL_INT64 => blr.put_slice(&[
139 consts::blr::INT64,
140 0, ]),
142
143 ibase::SQL_DOUBLE => blr.put_u8(consts::blr::DOUBLE),
144
145 ibase::SQL_TIMESTAMP => blr.put_u8(consts::blr::TIMESTAMP),
146
147 ibase::SQL_BLOB => blr.put_slice(&[consts::blr::QUAD, var.sqlsubtype as u8]),
148
149 ibase::SQL_BOOLEAN => blr.put_u8(consts::blr::BOOL),
150
151 sqltype => {
152 return Err(format!("Conversion from sql type {} not implemented", sqltype).into());
153 }
154 }
155 blr.put_slice(&[consts::blr::SHORT, 0]);
157 }
158
159 blr.put_slice(&[consts::blr::END, consts::blr::EOC]);
160
161 Ok(blr.freeze())
162}
163
164pub struct PrepareInfo {
166 pub stmt_type: StmtType,
167 pub param_count: usize,
168 pub truncated: bool,
169}
170
171pub fn parse_xsqlda(resp: &mut Bytes, xsqlda: &mut Vec<XSqlVar>) -> Result<PrepareInfo, FbError> {
176 if resp.remaining() < 7 || resp[..3] != [ibase::isc_info_sql_stmt_type as u8, 0x04, 0x00] {
178 return err_invalid_xsqlda();
179 }
180 resp.advance(3)?;
181
182 let stmt_type =
183 StmtType::try_from(resp.get_u32_le()? as u8).map_err(|e| FbError::Other(e.to_string()))?;
184
185 if resp.remaining() < 8
187 || resp[..2]
188 != [
189 ibase::isc_info_sql_bind as u8, ibase::isc_info_sql_describe_vars as u8, ]
192 {
193 return err_invalid_xsqlda();
194 }
195 resp.advance(2)?;
196 resp.advance(2)?;
200
201 let param_count = resp.get_u32_le()? as usize;
202
203 while resp.remaining() > 0 && resp[0] == ibase::isc_info_sql_describe_end as u8 {
204 resp.advance(1)?;
206 }
207
208 if resp.remaining() < 8
210 || resp[..2]
211 != [
212 ibase::isc_info_sql_select as u8, ibase::isc_info_sql_describe_vars as u8, ]
215 {
216 return err_invalid_xsqlda();
217 }
218 resp.advance(2)?;
219 resp.advance(2)?;
223
224 let col_len = resp.get_u32_le()? as usize;
225 if col_len > 1024 {
226 return err_invalid_xsqlda();
228 }
229 if xsqlda.is_empty() {
230 xsqlda.reserve(col_len);
231 }
232
233 let truncated = parse_select_items(resp, xsqlda)?;
234
235 Ok(PrepareInfo {
236 stmt_type,
237 param_count,
238 truncated,
239 })
240}
241
242pub fn parse_select_items(resp: &mut Bytes, xsqlda: &mut Vec<XSqlVar>) -> Result<bool, FbError> {
244 if resp.remaining() == 0 {
245 return Ok(false);
246 }
247
248 let mut col_index = 0;
249
250 let truncated = loop {
251 match resp.get_u8()? as u32 {
253 ibase::isc_info_sql_sqlda_seq => {
255 resp.advance(2)?;
257
258 let col = resp.get_u32_le()?;
259 if col == 0 {
260 return err_invalid_xsqlda();
262 }
263 col_index = col as usize - 1;
264
265 if col_index >= xsqlda.len() {
266 xsqlda.push(Default::default());
267 #[cfg(not(feature = "fuzz_testing"))]
269 debug_assert_eq!(xsqlda.len() - 1, col_index);
270 }
271 }
272
273 ibase::isc_info_sql_type => {
274 resp.advance(2)?;
276
277 if let Some(var) = xsqlda.get_mut(col_index) {
278 var.sqltype = resp.get_i32_le()? as i16;
279 } else {
280 return err_invalid_xsqlda();
281 }
282 }
283
284 ibase::isc_info_sql_sub_type => {
285 resp.advance(2)?;
287
288 if let Some(var) = xsqlda.get_mut(col_index) {
289 var.sqlsubtype = resp.get_i32_le()? as i16;
290 } else {
291 return err_invalid_xsqlda();
292 }
293 }
294
295 ibase::isc_info_sql_scale => {
296 resp.advance(2)?;
298
299 if let Some(var) = xsqlda.get_mut(col_index) {
300 var.scale = resp.get_i32_le()? as i16;
301 } else {
302 return err_invalid_xsqlda();
303 }
304 }
305
306 ibase::isc_info_sql_length => {
307 resp.advance(2)?;
309
310 if let Some(var) = xsqlda.get_mut(col_index) {
311 var.data_length = resp.get_i32_le()? as i16;
312 } else {
313 return err_invalid_xsqlda();
314 }
315 }
316
317 ibase::isc_info_sql_null_ind => {
318 resp.advance(2)?;
320
321 if let Some(var) = xsqlda.get_mut(col_index) {
322 var.null_ind = resp.get_i32_le()? != 0;
323 } else {
324 return err_invalid_xsqlda();
325 }
326 }
327
328 ibase::isc_info_sql_field => {
329 let len = resp.get_u16_le()? as usize;
330
331 let mut buff = vec![0; len];
332 resp.copy_to_slice(&mut buff)?;
333
334 if let Some(var) = xsqlda.get_mut(col_index) {
335 var.field_name = String::from_utf8(buff).unwrap_or_default();
336 } else {
337 return err_invalid_xsqlda();
338 }
339 }
340
341 ibase::isc_info_sql_relation => {
342 let len = resp.get_u16_le()? as usize;
343
344 let mut buff = vec![0; len];
345 resp.copy_to_slice(&mut buff)?;
346
347 if let Some(var) = xsqlda.get_mut(col_index) {
348 var.relation_name = String::from_utf8(buff).unwrap_or_default();
349 } else {
350 return err_invalid_xsqlda();
351 }
352 }
353
354 ibase::isc_info_sql_owner => {
355 let len = resp.get_u16_le()? as usize;
356
357 let mut buff = vec![0; len];
358 resp.copy_to_slice(&mut buff)?;
359
360 if let Some(var) = xsqlda.get_mut(col_index) {
361 var.owner_name = String::from_utf8(buff).unwrap_or_default();
362 } else {
363 return err_invalid_xsqlda();
364 }
365 }
366
367 ibase::isc_info_sql_alias => {
368 let len = resp.get_u16_le()? as usize;
369
370 let mut buff = vec![0; len];
371 resp.copy_to_slice(&mut buff)?;
372
373 if let Some(var) = xsqlda.get_mut(col_index) {
374 var.alias_name = String::from_utf8(buff).unwrap_or_default();
375 } else {
376 return err_invalid_xsqlda();
377 }
378 }
379
380 ibase::isc_info_sql_describe_end => {}
382
383 ibase::isc_info_truncated => break true,
385
386 ibase::isc_info_end => break false,
388
389 item => {
390 return Err(FbError::Other(format!(
391 "Invalid item received in the xsqlda: {}",
392 item
393 )));
394 }
395 }
396 };
397
398 Ok(truncated)
399}
400
401fn err_invalid_xsqlda<T>() -> Result<T, FbError> {
402 Err(FbError::Other(
403 "Invalid Xsqlda received from server".to_string(),
404 ))
405}