rsfbclient_rust/
blr.rs

1use crate::{client::FirebirdWireConnection, consts};
2use bytes::{BufMut, Bytes, BytesMut};
3use rsfbclient_core::{FbError, SqlType};
4
5/// Maximum parameter data length
6pub const MAX_DATA_LENGTH: usize = 32767;
7
8#[derive(Debug)]
9/// Data for the parameters to send in the wire
10pub struct ParamsBlr {
11    /// Definitions of the data types
12    pub(crate) blr: Bytes,
13    /// Actual values of the data
14    pub(crate) values: Bytes,
15}
16
17/// Convert the parameters to a blr (binary representation)
18pub fn params_to_blr(
19    conn: &mut FirebirdWireConnection,
20    tr_handle: &mut crate::TrHandle,
21    params: &[SqlType],
22) -> Result<ParamsBlr, FbError> {
23    let mut blr = BytesMut::with_capacity(256);
24    let mut values = BytesMut::with_capacity(256);
25
26    blr.put_slice(&[
27        consts::blr::VERSION5,
28        consts::blr::BEGIN,
29        consts::blr::MESSAGE,
30        0, // Message index
31    ]);
32    // Message length, * 2 as there is 1 msg for the param type and another for the nullind
33    blr.put_u16_le(params.len() as u16 * 2);
34
35    if conn.version >= consts::ProtocolVersion::V13 {
36        // Insert a null indicator bitmap
37        null_bitmap(&mut values, params);
38    }
39
40    // Handle blob creation and blr conversion
41    let mut handle_blob = |conn: &mut FirebirdWireConnection,
42                           blr: &mut BytesMut,
43                           values: &mut BytesMut,
44                           data: &[u8]| {
45        let (blob_handle, id) = conn.create_blob(tr_handle)?;
46
47        conn.put_segments(blob_handle, data)?;
48
49        conn.close_blob(blob_handle)?;
50
51        blr.put_u8(consts::blr::QUAD);
52        blr.put_u8(0); // Blob type
53
54        values.put_u64(id.0);
55
56        Ok::<_, FbError>(())
57    };
58
59    for p in params {
60        match p {
61            SqlType::Text(s) => {
62                let bytes = conn.charset.encode(s)?;
63                if bytes.len() > MAX_DATA_LENGTH {
64                    // Data too large, send as blob
65                    handle_blob(conn, &mut blr, &mut values, &bytes)?;
66                } else {
67                    blr.put_u8(consts::blr::TEXT);
68                    blr.put_u16_le(bytes.len() as u16);
69
70                    values.put_slice(&bytes);
71                    if bytes.len() % 4 != 0 {
72                        // 4 byte align
73                        values.put_slice(&[0; 4][..4 - (bytes.len() as usize % 4)])
74                    }
75                }
76            }
77
78            SqlType::Binary(data) => handle_blob(conn, &mut blr, &mut values, data)?,
79
80            SqlType::Integer(i) => {
81                blr.put_slice(&[
82                    consts::blr::INT64,
83                    0, // Scale
84                ]);
85
86                values.put_i64(*i);
87            }
88
89            SqlType::Floating(f) => {
90                blr.put_u8(consts::blr::DOUBLE);
91
92                values.put_f64(*f);
93            }
94
95            SqlType::Timestamp(dt) => {
96                blr.put_u8(consts::blr::TIMESTAMP);
97
98                let ts = rsfbclient_core::date_time::encode_timestamp(*dt);
99                values.put_i32(ts.timestamp_date);
100                values.put_u32(ts.timestamp_time);
101            }
102
103            SqlType::Boolean(b) => {
104                blr.put_u8(consts::blr::BOOL);
105
106                values.put_slice(if *b { &[1, 0, 0, 0] } else { &[0, 0, 0, 0] });
107            }
108
109            SqlType::Null => {
110                // Represent as empty text
111                blr.put_u8(consts::blr::TEXT);
112                blr.put_u16_le(0);
113            }
114        }
115
116        if conn.version < consts::ProtocolVersion::V13 {
117            // Null indicator
118            values.put_i32_le(if p.is_null() { -1 } else { 0 });
119        }
120
121        // Null indicator type
122        blr.put_slice(&[consts::blr::SHORT, 0]);
123    }
124
125    blr.put_slice(&[consts::blr::END, consts::blr::EOC]);
126
127    Ok(ParamsBlr {
128        blr: blr.freeze(),
129        values: values.freeze(),
130    })
131}
132
133/// Create a null indicator bitmap and insert into the `values`
134///
135/// The bitmap is a list of bytes,
136/// the first bit of the first byte will be 0 if the first value is not null
137/// or 1 if it is, and so forth.
138///
139/// Needs to be aligned to 4 bytes, so processing in chunks of 32 parameters (4 bytes = 32 bits)
140fn null_bitmap(values: &mut BytesMut, params: &[SqlType]) {
141    for bitmap in params.chunks(32).map(|params| {
142        params.iter().enumerate().fold(0, |bitmap, (i, p)| {
143            if p.is_null() {
144                bitmap | (1 << i)
145            } else {
146                bitmap
147            }
148        })
149    }) {
150        values.put_u32_le(bitmap);
151    }
152}