1mod connection;
4mod errors;
5mod typesystem;
6
7pub use self::connection::build_opts;
8pub use self::errors::MySQLSourceError;
9use crate::constants::DB_BUFFER_SIZE;
10use crate::{
11 data_order::DataOrder,
12 errors::ConnectorXError,
13 sources::{PartitionParser, Produce, Source, SourcePartition},
14 sql::{count_query, limit0_query, CXQuery},
15};
16use anyhow::anyhow;
17use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
18use fehler::{throw, throws};
19use log::{debug, warn};
20use r2d2::{Pool, PooledConnection};
21use r2d2_mysql::{
22 mysql::{
23 prelude::{FromValue, Queryable},
24 Binary, Column, QueryResult, Row, Text,
25 },
26 MySqlConnectionManager,
27};
28use rust_decimal::Decimal;
29use serde_json::Value;
30use sqlparser::dialect::MySqlDialect;
31use std::marker::PhantomData;
32pub use typesystem::MySQLTypeSystem;
33
34type MysqlConn = PooledConnection<MySqlConnectionManager>;
35
36pub enum BinaryProtocol {}
37pub enum TextProtocol {}
38
39#[throws(MySQLSourceError)]
40fn get_total_rows(conn: &mut MysqlConn, query: &CXQuery<String>) -> usize {
41 conn.query_first(&count_query(query, &MySqlDialect {})?)?
42 .ok_or_else(|| anyhow!("mysql failed to get the count of query: {}", query))?
43}
44
45pub struct MySQLSource<P> {
46 pool: Pool<MySqlConnectionManager>,
47 origin_query: Option<String>,
48 queries: Vec<CXQuery<String>>,
49 names: Vec<String>,
50 schema: Vec<MySQLTypeSystem>,
51 pre_execution_queries: Option<Vec<String>>,
52 _protocol: PhantomData<P>,
53}
54
55fn column_metadata(columns: &[Column]) -> (Vec<String>, Vec<MySQLTypeSystem>) {
56 columns
57 .iter()
58 .map(|col| {
59 (
60 col.name_str().to_string(),
61 MySQLTypeSystem::from((&col.column_type(), &col.flags(), col.character_set())),
62 )
63 })
64 .unzip()
65}
66
67fn limit0_metadata(
70 conn: &mut MysqlConn,
71 queries: &[CXQuery<String>],
72) -> Result<(Vec<String>, Vec<MySQLTypeSystem>), MySQLSourceError> {
73 for (i, query) in queries.iter().enumerate() {
74 match conn.query_iter(limit0_query(query, &MySqlDialect {})?.as_str()) {
76 Ok(iter) => {
77 let (names, types) = column_metadata(iter.columns().as_ref());
78 if !names.is_empty() {
79 return Ok((names, types));
80 }
81 debug!("no result columns for '{}', try next query", query);
82 }
83 Err(e) if i == queries.len() - 1 => {
84 debug!("cannot get metadata for '{}': {}", query, e);
86 return Err(e.into());
87 }
88 Err(e) => {
89 debug!("cannot get metadata for '{}', try next query: {}", query, e);
90 }
91 }
92 }
93 Err(anyhow!("cannot get metadata: no query returned a result set with columns").into())
94}
95
96impl<P> MySQLSource<P> {
97 #[throws(MySQLSourceError)]
98 pub fn new(conn: &str, nconn: usize) -> Self {
99 let manager = MySqlConnectionManager::new(build_opts(conn)?);
100 let pool = r2d2::Pool::builder()
101 .max_size(nconn as u32)
102 .build(manager)?;
103
104 Self {
105 pool,
106 origin_query: None,
107 queries: vec![],
108 names: vec![],
109 schema: vec![],
110 pre_execution_queries: None,
111 _protocol: PhantomData,
112 }
113 }
114}
115
116impl<P> Source for MySQLSource<P>
117where
118 MySQLSourcePartition<P>:
119 SourcePartition<TypeSystem = MySQLTypeSystem, Error = MySQLSourceError>,
120 P: Send,
121{
122 const DATA_ORDERS: &'static [DataOrder] = &[DataOrder::RowMajor];
123 type Partition = MySQLSourcePartition<P>;
124 type TypeSystem = MySQLTypeSystem;
125 type Error = MySQLSourceError;
126
127 #[throws(MySQLSourceError)]
128 fn set_data_order(&mut self, data_order: DataOrder) {
129 if !matches!(data_order, DataOrder::RowMajor) {
130 throw!(ConnectorXError::UnsupportedDataOrder(data_order));
131 }
132 }
133
134 fn set_queries<Q: ToString>(&mut self, queries: &[CXQuery<Q>]) {
135 self.queries = queries.iter().map(|q| q.map(Q::to_string)).collect();
136 }
137
138 fn set_origin_query(&mut self, query: Option<String>) {
139 self.origin_query = query;
140 }
141
142 fn set_pre_execution_queries(&mut self, pre_execution_queries: Option<&[String]>) {
143 self.pre_execution_queries = pre_execution_queries.map(|s| s.to_vec());
144 }
145
146 #[throws(MySQLSourceError)]
147 fn fetch_metadata(&mut self) {
148 assert!(!self.queries.is_empty());
149
150 let mut conn = self.pool.get()?;
151 let first_query = &self.queries[0];
152
153 let (names, types) = match conn.prep(first_query) {
154 Ok(stmt) => column_metadata(stmt.columns()),
155 Err(e) => {
156 warn!(
157 "mysql prepared statement error: {:?}, switch to limit 0 method",
158 e
159 );
160 limit0_metadata(&mut conn, &self.queries)?
161 }
162 };
163 self.names = names;
164 self.schema = types;
165 }
166
167 #[throws(MySQLSourceError)]
168 fn result_rows(&mut self) -> Option<usize> {
169 match &self.origin_query {
170 Some(q) => {
171 let cxq = CXQuery::Naked(q.clone());
172 let mut conn = self.pool.get()?;
173 let nrows = get_total_rows(&mut conn, &cxq)?;
174 Some(nrows)
175 }
176 None => None,
177 }
178 }
179
180 fn names(&self) -> Vec<String> {
181 self.names.clone()
182 }
183
184 fn schema(&self) -> Vec<Self::TypeSystem> {
185 self.schema.clone()
186 }
187
188 #[throws(MySQLSourceError)]
189 fn partition(self) -> Vec<Self::Partition> {
190 let mut ret = vec![];
191 for query in self.queries {
192 let mut conn = self.pool.get()?;
193
194 if let Some(pre_queries) = &self.pre_execution_queries {
195 for pre_query in pre_queries {
196 conn.query_drop(pre_query)?;
197 }
198 }
199
200 ret.push(MySQLSourcePartition::new(conn, &query, &self.schema));
201 }
202 ret
203 }
204}
205
206pub struct MySQLSourcePartition<P> {
207 conn: MysqlConn,
208 query: CXQuery<String>,
209 schema: Vec<MySQLTypeSystem>,
210 nrows: usize,
211 ncols: usize,
212 _protocol: PhantomData<P>,
213}
214
215impl<P> MySQLSourcePartition<P> {
216 pub fn new(conn: MysqlConn, query: &CXQuery<String>, schema: &[MySQLTypeSystem]) -> Self {
217 Self {
218 conn,
219 query: query.clone(),
220 schema: schema.to_vec(),
221 nrows: 0,
222 ncols: schema.len(),
223 _protocol: PhantomData,
224 }
225 }
226}
227
228impl SourcePartition for MySQLSourcePartition<BinaryProtocol> {
229 type TypeSystem = MySQLTypeSystem;
230 type Parser<'a> = MySQLBinarySourceParser<'a>;
231 type Error = MySQLSourceError;
232
233 #[throws(MySQLSourceError)]
234 fn result_rows(&mut self) {
235 self.nrows = get_total_rows(&mut self.conn, &self.query)?;
236 }
237
238 #[throws(MySQLSourceError)]
239 fn parser(&mut self) -> Self::Parser<'_> {
240 let stmt = self.conn.prep(self.query.as_str())?;
241 let iter = self.conn.exec_iter(stmt, ())?;
242 MySQLBinarySourceParser::new(iter, &self.schema)
243 }
244
245 fn nrows(&self) -> usize {
246 self.nrows
247 }
248
249 fn ncols(&self) -> usize {
250 self.ncols
251 }
252}
253
254impl SourcePartition for MySQLSourcePartition<TextProtocol> {
255 type TypeSystem = MySQLTypeSystem;
256 type Parser<'a> = MySQLTextSourceParser<'a>;
257 type Error = MySQLSourceError;
258
259 #[throws(MySQLSourceError)]
260 fn result_rows(&mut self) {
261 self.nrows = get_total_rows(&mut self.conn, &self.query)?;
262 }
263
264 #[throws(MySQLSourceError)]
265 fn parser(&mut self) -> Self::Parser<'_> {
266 let query = self.query.clone();
267 let iter = self.conn.query_iter(query)?;
268 MySQLTextSourceParser::new(iter, &self.schema)
269 }
270
271 fn nrows(&self) -> usize {
272 self.nrows
273 }
274
275 fn ncols(&self) -> usize {
276 self.ncols
277 }
278}
279
280fn take_cell<T: FromValue, Produced>(
290 row: &mut Row,
291 ridx: usize,
292 cidx: usize,
293) -> Result<T, MySQLSourceError> {
294 match row.take_opt::<T, _>(cidx) {
295 Some(Ok(value)) => Ok(value),
296 Some(Err(e)) => Err(ConnectorXError::cannot_produce::<Produced>(Some(format!(
297 "column `{}` (index {}): {}",
298 row.columns_ref()[cidx].name_str(),
299 cidx,
300 e
301 )))
302 .into()),
303 None => Err(anyhow!("mysql cannot parse at position: ({}, {})", ridx, cidx).into()),
304 }
305}
306
307pub struct MySQLBinarySourceParser<'a> {
308 iter: QueryResult<'a, 'a, 'a, Binary>,
309 rowbuf: Vec<Row>,
310 ncols: usize,
311 current_col: usize,
312 current_row: usize,
313 is_finished: bool,
314}
315
316impl<'a> MySQLBinarySourceParser<'a> {
317 pub fn new(iter: QueryResult<'a, 'a, 'a, Binary>, schema: &[MySQLTypeSystem]) -> Self {
318 Self {
319 iter,
320 rowbuf: Vec::with_capacity(DB_BUFFER_SIZE),
321 ncols: schema.len(),
322 current_row: 0,
323 current_col: 0,
324 is_finished: false,
325 }
326 }
327
328 #[throws(MySQLSourceError)]
329 fn next_loc(&mut self) -> (usize, usize) {
330 let ret = (self.current_row, self.current_col);
331 self.current_row += (self.current_col + 1) / self.ncols;
332 self.current_col = (self.current_col + 1) % self.ncols;
333 ret
334 }
335}
336
337impl<'a> PartitionParser<'a> for MySQLBinarySourceParser<'a> {
338 type TypeSystem = MySQLTypeSystem;
339 type Error = MySQLSourceError;
340
341 #[throws(MySQLSourceError)]
342 fn fetch_next(&mut self) -> (usize, bool) {
343 assert!(self.current_col == 0);
344 let remaining_rows = self.rowbuf.len() - self.current_row;
345 if remaining_rows > 0 {
346 return (remaining_rows, self.is_finished);
347 } else if self.is_finished {
348 return (0, self.is_finished);
349 }
350
351 if !self.rowbuf.is_empty() {
352 self.rowbuf.drain(..);
353 }
354
355 for _ in 0..DB_BUFFER_SIZE {
356 if let Some(item) = self.iter.next() {
357 self.rowbuf.push(item?);
358 } else {
359 self.is_finished = true;
360 break;
361 }
362 }
363 self.current_row = 0;
364 self.current_col = 0;
365
366 (self.rowbuf.len(), self.is_finished)
367 }
368}
369
370macro_rules! impl_produce_binary {
371 ($($t: ty,)+) => {
372 $(
373 impl<'r, 'a> Produce<'r, $t> for MySQLBinarySourceParser<'a> {
374 type Error = MySQLSourceError;
375
376 #[throws(MySQLSourceError)]
377 fn produce(&'r mut self) -> $t {
378 let (ridx, cidx) = self.next_loc()?;
379 take_cell::<$t, $t>(&mut self.rowbuf[ridx], ridx, cidx)?
380 }
381 }
382
383 impl<'r, 'a> Produce<'r, Option<$t>> for MySQLBinarySourceParser<'a> {
384 type Error = MySQLSourceError;
385
386 #[throws(MySQLSourceError)]
387 fn produce(&'r mut self) -> Option<$t> {
388 let (ridx, cidx) = self.next_loc()?;
389 take_cell::<Option<$t>, $t>(&mut self.rowbuf[ridx], ridx, cidx)?
390 }
391 }
392 )+
393 };
394}
395
396impl_produce_binary!(
397 i8,
398 i16,
399 i32,
400 i64,
401 u8,
402 u16,
403 u32,
404 u64,
405 f32,
406 f64,
407 NaiveDate,
408 NaiveTime,
409 NaiveDateTime,
410 Decimal,
411 String,
412 Vec<u8>,
413 Value,
414);
415
416pub struct MySQLTextSourceParser<'a> {
417 iter: QueryResult<'a, 'a, 'a, Text>,
418 rowbuf: Vec<Row>,
419 ncols: usize,
420 current_col: usize,
421 current_row: usize,
422 is_finished: bool,
423}
424
425impl<'a> MySQLTextSourceParser<'a> {
426 pub fn new(iter: QueryResult<'a, 'a, 'a, Text>, schema: &[MySQLTypeSystem]) -> Self {
427 Self {
428 iter,
429 rowbuf: Vec::with_capacity(DB_BUFFER_SIZE),
430 ncols: schema.len(),
431 current_row: 0,
432 current_col: 0,
433 is_finished: false,
434 }
435 }
436
437 #[throws(MySQLSourceError)]
438 fn next_loc(&mut self) -> (usize, usize) {
439 let ret = (self.current_row, self.current_col);
440 self.current_row += (self.current_col + 1) / self.ncols;
441 self.current_col = (self.current_col + 1) % self.ncols;
442 ret
443 }
444}
445
446impl<'a> PartitionParser<'a> for MySQLTextSourceParser<'a> {
447 type TypeSystem = MySQLTypeSystem;
448 type Error = MySQLSourceError;
449
450 #[throws(MySQLSourceError)]
451 fn fetch_next(&mut self) -> (usize, bool) {
452 assert!(self.current_col == 0);
453 let remaining_rows = self.rowbuf.len() - self.current_row;
454 if remaining_rows > 0 {
455 return (remaining_rows, self.is_finished);
456 } else if self.is_finished {
457 return (0, self.is_finished);
458 }
459
460 if !self.rowbuf.is_empty() {
461 self.rowbuf.drain(..);
462 }
463 for _ in 0..DB_BUFFER_SIZE {
464 if let Some(item) = self.iter.next() {
465 self.rowbuf.push(item?);
466 } else {
467 self.is_finished = true;
468 break;
469 }
470 }
471 self.current_row = 0;
472 self.current_col = 0;
473 (self.rowbuf.len(), self.is_finished)
474 }
475}
476
477macro_rules! impl_produce_text {
478 ($($t: ty,)+) => {
479 $(
480 impl<'r, 'a> Produce<'r, $t> for MySQLTextSourceParser<'a> {
481 type Error = MySQLSourceError;
482
483 #[throws(MySQLSourceError)]
484 fn produce(&'r mut self) -> $t {
485 let (ridx, cidx) = self.next_loc()?;
486 take_cell::<$t, $t>(&mut self.rowbuf[ridx], ridx, cidx)?
487 }
488 }
489
490 impl<'r, 'a> Produce<'r, Option<$t>> for MySQLTextSourceParser<'a> {
491 type Error = MySQLSourceError;
492
493 #[throws(MySQLSourceError)]
494 fn produce(&'r mut self) -> Option<$t> {
495 let (ridx, cidx) = self.next_loc()?;
496 take_cell::<Option<$t>, $t>(&mut self.rowbuf[ridx], ridx, cidx)?
497 }
498 }
499 )+
500 };
501}
502
503impl_produce_text!(
504 i8,
505 i16,
506 i32,
507 i64,
508 u8,
509 u16,
510 u32,
511 u64,
512 f32,
513 f64,
514 NaiveDate,
515 NaiveTime,
516 NaiveDateTime,
517 Decimal,
518 String,
519 Vec<u8>,
520 Value,
521);