rsfbclient_rust/
xsqlda.rs

1//! Structs and functions to parse and send data about the sql parameters and columns
2
3#![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
12/// Data to return about a statement
13pub const XSQLDA_DESCRIBE_VARS: [u8; 17] = [
14    ibase::isc_info_sql_stmt_type as u8, // Statement type: StmtType
15    ibase::isc_info_sql_bind as u8,      // Select params
16    ibase::isc_info_sql_describe_vars as u8, // Param count
17    ibase::isc_info_sql_describe_end as u8, // End of param data
18    ibase::isc_info_sql_select as u8,    // Select columns
19    ibase::isc_info_sql_describe_vars as u8, // Column count
20    ibase::isc_info_sql_sqlda_seq as u8, // Column index
21    ibase::isc_info_sql_type as u8,      // Sql Type code
22    ibase::isc_info_sql_sub_type as u8,  // Blob subtype
23    ibase::isc_info_sql_scale as u8,     // Decimal / Numeric scale
24    ibase::isc_info_sql_length as u8,    // Data length
25    ibase::isc_info_sql_null_ind as u8,  // Null indicator (0 or -1)
26    ibase::isc_info_sql_field as u8,     //
27    ibase::isc_info_sql_relation as u8,  //
28    ibase::isc_info_sql_owner as u8,     //
29    ibase::isc_info_sql_alias as u8,     // Column alias
30    ibase::isc_info_sql_describe_end as u8, // End of column data
31];
32
33#[derive(Debug, Default)]
34/// Sql query column information
35pub struct XSqlVar {
36    /// Sql type code
37    pub sqltype: i16,
38
39    /// Scale: indicates that the real value is `data * 10.pow(scale)`
40    pub scale: i16,
41
42    /// Blob subtype code
43    pub sqlsubtype: i16,
44
45    /// Length of the column data
46    pub data_length: i16,
47
48    /// Null indicator
49    pub null_ind: bool,
50
51    pub field_name: String,
52
53    pub relation_name: String,
54
55    pub owner_name: String,
56
57    /// Column alias
58    pub alias_name: String,
59}
60
61impl XSqlVar {
62    /// Coerces the data types of this XSqlVar as necessary
63    pub fn coerce(&mut self) -> Result<(), FbError> {
64        // Remove nullable type indicator
65        let sqltype = self.sqltype & (!1);
66        let sqlsubtype = self.sqlsubtype;
67
68        // var.null_ind = 1;
69
70        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                    // Is actually a decimal or numeric value, so coerce as double
82                    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
116/// Convert the xsqlda to blr (binary representation)
117pub 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, // Message index
124    ]);
125    // Message length, * 2 as there is 1 msg for the param type and another for the nullind
126    blr.put_u16_le(xsqlda.len() as u16 * 2);
127
128    for var in xsqlda {
129        // Remove nullable type indicator
130        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, // Scale
141            ]),
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        // Nullind
156        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
164/// Data returned for a prepare statement
165pub struct PrepareInfo {
166    pub stmt_type: StmtType,
167    pub param_count: usize,
168    pub truncated: bool,
169}
170
171/// Parses the data from the `PrepareStatement` response.
172///
173/// XSqlDa data format: u8 type + optional data preceded by a u16 length.
174/// Returns the statement type, xsqlda and an indicator if the data was truncated (xsqlda not entirely filled)
175pub fn parse_xsqlda(resp: &mut Bytes, xsqlda: &mut Vec<XSqlVar>) -> Result<PrepareInfo, FbError> {
176    // Asserts that the first 7 bytes are the statement type information
177    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    // Asserts that the next 8 bytes are the start of the parameters data
186    if resp.remaining() < 8
187        || resp[..2]
188            != [
189                ibase::isc_info_sql_bind as u8,          // Start of param data
190                ibase::isc_info_sql_describe_vars as u8, // Param count
191            ]
192    {
193        return err_invalid_xsqlda();
194    }
195    resp.advance(2)?;
196    // Parameter count
197
198    // Assume 0x04 0x00
199    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        // Indicates the end of param data, skip it as it appears only once. has one for each param
205        resp.advance(1)?;
206    }
207
208    // Asserts that the next 8 bytes are the start of the columns data
209    if resp.remaining() < 8
210        || resp[..2]
211            != [
212                ibase::isc_info_sql_select as u8,        // Start of column data
213                ibase::isc_info_sql_describe_vars as u8, // Column count
214            ]
215    {
216        return err_invalid_xsqlda();
217    }
218    resp.advance(2)?;
219    // Column count
220
221    // Assume 0x04 0x00
222    resp.advance(2)?;
223
224    let col_len = resp.get_u32_le()? as usize;
225    if col_len > 1024 {
226        // Absurd quantity of rows, so must be an error (or else could panic trying to allocate too much RAM)
227        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
242/// Fill the xsqlda with data from the cursor, return `true` if the data was truncated (needs more data to fill the xsqlda)
243pub 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        // Get item code
252        match resp.get_u8()? as u32 {
253            // Column index
254            ibase::isc_info_sql_sqlda_seq => {
255                // Assume 0x04 0x00
256                resp.advance(2)?;
257
258                let col = resp.get_u32_le()?;
259                if col == 0 {
260                    // Index received starts on 1
261                    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                    // Must be the same
268                    #[cfg(not(feature = "fuzz_testing"))]
269                    debug_assert_eq!(xsqlda.len() - 1, col_index);
270                }
271            }
272
273            ibase::isc_info_sql_type => {
274                // Assume 0x04 0x00
275                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                // Assume 0x04 0x00
286                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                // Assume 0x04 0x00
297                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                // Assume 0x04 0x00
308                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                // Assume 0x04 0x00
319                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            // End of this column data
381            ibase::isc_info_sql_describe_end => {}
382
383            // Data truncated
384            ibase::isc_info_truncated => break true,
385
386            // End of the data
387            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}