Skip to main content

tds_driver/tds/codec/
bulk_load.rs

1use asynchronous_codec::BytesMut;
2use futures_util::io::{AsyncRead, AsyncWrite};
3use tracing::{event, Level};
4
5use crate::{
6    client::Connection, sql_read_bytes::SqlReadBytes, BytesMutWithDataColumns, ExecuteResult,
7};
8
9use super::{
10    Encode, MetaDataColumn, PacketHeader, PacketStatus, TokenColMetaData, TokenDone, TokenRow,
11    HEADER_BYTES,
12};
13
14/// A handler for a bulk insert data flow.
15#[derive(Debug)]
16pub struct BulkLoadRequest<'a, S>
17where
18    S: AsyncRead + AsyncWrite + Unpin + Send,
19{
20    connection: &'a mut Connection<S>,
21    packet_id: u8,
22    buf: BytesMut,
23    columns: Vec<MetaDataColumn<'a>>,
24}
25
26impl<'a, S> BulkLoadRequest<'a, S>
27where
28    S: AsyncRead + AsyncWrite + Unpin + Send,
29{
30    pub(crate) fn new(
31        connection: &'a mut Connection<S>,
32        mut columns: Vec<MetaDataColumn<'a>>,
33    ) -> crate::Result<Self> {
34        // The server now waits for the bulk-load message; until `finalize`
35        // ends it, the next request on the connection has to abandon it.
36        connection.set_bulk_load_open(true);
37        let packet_id = connection.context_mut().next_packet_id();
38
39        let collation = connection.context().collation();
40        for column in columns.iter_mut() {
41            column.base.set_variant_collation(collation);
42        }
43        let mut buf = BytesMut::new();
44
45        let cmd = TokenColMetaData {
46            columns: columns.clone(),
47        };
48
49        cmd.encode(&mut buf)?;
50
51        let this = Self {
52            connection,
53            packet_id,
54            buf,
55            columns,
56        };
57
58        Ok(this)
59    }
60
61    /// Adds a new row to the bulk insert, flushing only when having a full packet of data.
62    ///
63    /// A row that fails to encode is not sent, and the request stays usable.
64    /// Dropping the request without [`finalize`] cancels the bulk insert;
65    /// the connection stays usable.
66    ///
67    /// # Warning
68    ///
69    /// After the last row, [`finalize`] must be called to flush the buffered
70    /// data and for the data to actually be available in the table.
71    ///
72    /// [`finalize`]: #method.finalize
73    pub async fn send(&mut self, row: TokenRow<'a>) -> crate::Result<()> {
74        let row_start = self.buf.len();
75        let mut buf_with_columns = BytesMutWithDataColumns::new(&mut self.buf, &self.columns);
76
77        if let Err(e) = row.encode(&mut buf_with_columns) {
78            self.buf.truncate(row_start);
79            return Err(e);
80        }
81        self.write_packets().await?;
82
83        Ok(())
84    }
85
86    /// Ends the bulk load, flushing all pending data to the wire.
87    ///
88    /// This method must be called after sending all the data to flush all
89    /// pending data and to get the server actually to store the rows to the
90    /// table.
91    pub async fn finalize(mut self) -> crate::Result<ExecuteResult> {
92        TokenDone::default().encode(&mut self.buf)?;
93        self.write_packets().await?;
94
95        let mut header = PacketHeader::bulk_load(self.packet_id);
96        header.set_status(PacketStatus::EndOfMessage);
97
98        let data = self.buf.split();
99
100        event!(
101            Level::TRACE,
102            "Finalizing a bulk insert ({} bytes)",
103            data.len() + HEADER_BYTES,
104        );
105
106        self.connection.write_to_wire(header, data).await?;
107        self.connection.set_bulk_load_open(false);
108        self.connection.flush_sink().await?;
109
110        ExecuteResult::new(self.connection).await
111    }
112
113    async fn write_packets(&mut self) -> crate::Result<()> {
114        let packet_size = (self.connection.context().packet_size() as usize) - HEADER_BYTES;
115
116        while self.buf.len() > packet_size {
117            let header = PacketHeader::bulk_load(self.packet_id);
118            let data = self.buf.split_to(packet_size);
119
120            event!(
121                Level::TRACE,
122                "Bulk insert packet ({} bytes)",
123                data.len() + HEADER_BYTES,
124            );
125
126            self.connection.write_to_wire(header, data).await?;
127        }
128
129        Ok(())
130    }
131}
132
133/// Asks for the collation names of the columns `SELECT TOP 0 {columns} FROM
134/// {table}` returns, in order (SQL Server 2012 and later).
135pub(crate) fn collation_names_query(select: &str) -> String {
136    format!(
137        "SELECT collation_name FROM sys.dm_exec_describe_first_result_set(N'{}', NULL, 0) ORDER BY column_ordinal",
138        select.replace('\'', "''")
139    )
140}
141
142/// One INSERT BULK column definition. As in SqlBulkCopy, it names the
143/// column's collation: without one the server reads non-Unicode bytes in the
144/// database default code page.
145pub(crate) fn insert_bulk_column(column: &MetaDataColumn<'_>, collation: Option<&str>) -> String {
146    match collation {
147        Some(collation) => format!("{column} COLLATE {collation}"),
148        None => column.to_string(),
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155    use crate::tds::codec::{BaseMetaDataColumn, ColumnFlag, FixedLenType, TypeInfo};
156    use crate::tds::Collation;
157    use crate::{VarLenContext, VarLenType};
158    use std::borrow::Cow;
159
160    fn column(name: &'static str, ty: TypeInfo) -> MetaDataColumn<'static> {
161        MetaDataColumn {
162            base: BaseMetaDataColumn {
163                flags: ColumnFlag::Nullable.into(),
164                ty,
165            },
166            col_name: Cow::Borrowed(name),
167        }
168    }
169
170    #[test]
171    fn insert_bulk_column_names_the_collation() {
172        let a = column(
173            "a",
174            TypeInfo::VarLenSized(VarLenContext::new(
175                VarLenType::BigVarChar,
176                10,
177                Some(Collation::new(0x00d0_0408, 0)),
178            )),
179        );
180        let b = column("b]", TypeInfo::FixedLen(FixedLenType::Int4));
181
182        assert_eq!(
183            insert_bulk_column(&a, Some("Greek_CI_AS")),
184            "[a] varchar(10) COLLATE Greek_CI_AS"
185        );
186        assert_eq!(insert_bulk_column(&b, None), "[b]]] int");
187    }
188
189    #[test]
190    fn collation_names_query_quotes_the_select() {
191        assert_eq!(
192            collation_names_query("SELECT TOP 0 * FROM [o'brien]"),
193            "SELECT collation_name FROM sys.dm_exec_describe_first_result_set(N'SELECT TOP 0 * FROM [o''brien]', NULL, 0) ORDER BY column_ordinal"
194        );
195    }
196}