tds_driver/tds/codec/
bulk_load.rs1use 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#[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 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 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 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
133pub(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
142pub(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}