1use std::convert::TryFrom;
2use std::sync::Arc;
3
4use crate::errors::{ConnectorXOutError, OutResult};
5use crate::source_router::{SourceConn, SourceType};
6#[cfg(feature = "src_bigquery")]
7use crate::sources::bigquery::BigQueryDialect;
8#[cfg(feature = "src_clickhouse")]
9use crate::sources::clickhouse::{ClickHouseSource, ClickHouseSourceError};
10#[cfg(feature = "src_mssql_tiberius")]
11use crate::sources::mssql::{mssql_config, FloatN, IntN, MsSQLTypeSystem};
12#[cfg(feature = "src_mysql")]
13use crate::sources::mysql::{build_opts, MySQLTypeSystem};
14#[cfg(feature = "src_oracle")]
15use crate::sources::oracle::{OracleDialect, OracleSource};
16#[cfg(feature = "src_postgres")]
17use crate::sources::postgres::{rewrite_tls_args, PostgresTypeSystem};
18#[cfg(feature = "src_trino")]
19use crate::sources::trino::TrinoDialect;
20#[cfg(feature = "src_sqlite")]
21use crate::sql::get_partition_range_query_sep;
22use crate::sql::{get_partition_range_query, single_col_partition_query, CXQuery};
23use anyhow::anyhow;
24use fehler::{throw, throws};
25#[cfg(feature = "src_bigquery")]
26use gcp_bigquery_client;
27#[cfg(feature = "src_mysql")]
28use r2d2_mysql::mysql::{prelude::Queryable, Pool, Row};
29#[cfg(feature = "src_sqlite")]
30use rusqlite::{types::Type, Connection};
31#[cfg(feature = "src_postgres")]
32use rust_decimal::{prelude::ToPrimitive, Decimal};
33#[cfg(feature = "src_postgres")]
34use rust_decimal_macros::dec;
35#[cfg(feature = "src_clickhouse")]
36use serde::Deserialize;
37#[cfg(feature = "src_clickhouse")]
38use serde_json::Value as JsonValue;
39#[cfg(feature = "src_clickhouse")]
40use sqlparser::dialect::ClickHouseDialect;
41#[cfg(feature = "src_mssql_common")]
42use sqlparser::dialect::MsSqlDialect;
43#[cfg(feature = "src_mysql")]
44use sqlparser::dialect::MySqlDialect;
45#[cfg(feature = "src_postgres")]
46use sqlparser::dialect::PostgreSqlDialect;
47#[cfg(feature = "src_sqlite")]
48use sqlparser::dialect::SQLiteDialect;
49#[cfg(feature = "src_mssql_tiberius")]
50use tiberius::Client;
51#[cfg(feature = "src_mssql_tiberius")]
52use tokio::net::TcpStream;
53#[cfg(any(
54 feature = "src_bigquery",
55 feature = "src_mssql_tiberius",
56 feature = "src_trino"
57))]
58use tokio::runtime::Runtime;
59#[cfg(feature = "src_mssql_tiberius")]
60use tokio_util::compat::TokioAsyncWriteCompatExt;
61use url::Url;
62
63pub struct PartitionQuery {
64 query: String,
65 column: String,
66 min: Option<i64>,
67 max: Option<i64>,
68 num: usize,
69}
70
71impl PartitionQuery {
72 pub fn new(query: &str, column: &str, min: Option<i64>, max: Option<i64>, num: usize) -> Self {
73 Self {
74 query: query.into(),
75 column: column.into(),
76 min,
77 max,
78 num,
79 }
80 }
81}
82
83pub fn partition(part: &PartitionQuery, source_conn: &SourceConn) -> OutResult<Vec<CXQuery>> {
84 let mut queries = vec![];
85
86 if part.num == 0 {
87 throw!(anyhow!("partition count (num) must be greater than zero"));
88 }
89
90 let num = i64::try_from(part.num).map_err(|_| {
91 anyhow!(
92 "partition count (num) is too large to represent safely: {}",
93 part.num
94 )
95 })?;
96
97 let (min, max) = match (part.min, part.max) {
98 (None, None) => get_col_range(source_conn, &part.query, &part.column)?,
99 (Some(min), Some(max)) => (min, max),
100 _ => throw!(anyhow!(
101 "partition_query range can not be partially specified",
102 )),
103 };
104
105 if max < min {
106 throw!(anyhow!(
107 "partition range is invalid: max ({}) must be greater than or equal to min ({})",
108 max,
109 min
110 ));
111 }
112
113 let range_len = max
114 .checked_sub(min)
115 .and_then(|value| value.checked_add(1))
116 .ok_or_else(|| {
117 anyhow!(
118 "partition range overflow: min={}, max={} is too large",
119 min,
120 max
121 )
122 })?;
123
124 let partition_size = range_len / num;
125
126 let final_upper = max.checked_add(1).ok_or_else(|| {
127 anyhow!(
128 "partition upper bound overflow: max={} cannot be incremented safely",
129 max
130 )
131 })?;
132
133 for i in 0i64..num {
134 let lower = i
135 .checked_mul(partition_size)
136 .and_then(|offset| min.checked_add(offset))
137 .ok_or_else(|| {
138 anyhow!(
139 "partition lower bound overflow: min={}, step={}, partition_size={}",
140 min,
141 i,
142 partition_size
143 )
144 })?;
145 let upper = if i == num - 1 {
146 final_upper
147 } else {
148 (i + 1)
149 .checked_mul(partition_size)
150 .and_then(|offset| min.checked_add(offset))
151 .ok_or_else(|| {
152 anyhow!(
153 "partition upper bound overflow: min={}, step={}, partition_size={}",
154 min,
155 i + 1,
156 partition_size
157 )
158 })?
159 };
160 let partition_query = get_part_query(source_conn, &part.query, &part.column, lower, upper)?;
161 queries.push(partition_query);
162 }
163 Ok(queries)
164}
165
166pub fn get_col_range(source_conn: &SourceConn, query: &str, col: &str) -> OutResult<(i64, i64)> {
167 match source_conn.ty {
168 #[cfg(feature = "src_postgres")]
169 SourceType::Postgres => pg_get_partition_range(&source_conn.conn, query, col),
170 #[cfg(feature = "src_sqlite")]
171 SourceType::SQLite => sqlite_get_partition_range(&source_conn.conn, query, col),
172 #[cfg(feature = "src_mysql")]
173 SourceType::MySQL => mysql_get_partition_range(&source_conn.conn, query, col),
174 #[cfg(all(feature = "src_mssql_tiberius", not(feature = "src_mssql_tds")))]
175 SourceType::MsSQL => mssql_get_partition_range(&source_conn.conn, query, col),
176 #[cfg(all(feature = "src_mssql_tiberius", feature = "src_mssql_tds"))]
177 SourceType::MsSQL => match crate::sources::mssql::active_driver() {
178 crate::sources::mssql::MsSQLDriverKind::Tiberius => {
179 mssql_get_partition_range(&source_conn.conn, query, col)
180 }
181 crate::sources::mssql::MsSQLDriverKind::MssqlTds => Ok(
182 crate::sources::mssql::tds_get_partition_range(&source_conn.conn, query, col)?,
183 ),
184 },
185 #[cfg(all(feature = "src_mssql_tds", not(feature = "src_mssql_tiberius")))]
186 SourceType::MsSQL => Ok(crate::sources::mssql::tds_get_partition_range(
187 &source_conn.conn,
188 query,
189 col,
190 )?),
191 #[cfg(feature = "src_oracle")]
192 SourceType::Oracle => oracle_get_partition_range(&source_conn.conn, query, col),
193 #[cfg(feature = "src_bigquery")]
194 SourceType::BigQuery => bigquery_get_partition_range(&source_conn.conn, query, col),
195 #[cfg(feature = "src_trino")]
196 SourceType::Trino => trino_get_partition_range(&source_conn.conn, query, col),
197 #[cfg(feature = "src_clickhouse")]
198 SourceType::ClickHouse => clickhouse_get_partition_range(&source_conn.conn, query, col),
199 _ => unimplemented!("{:?} not implemented!", source_conn.ty),
200 }
201}
202
203#[throws(ConnectorXOutError)]
204pub fn get_part_query(
205 source_conn: &SourceConn,
206 query: &str,
207 col: &str,
208 lower: i64,
209 upper: i64,
210) -> CXQuery<String> {
211 let query = match source_conn.ty {
212 #[cfg(feature = "src_postgres")]
213 SourceType::Postgres => {
214 single_col_partition_query(query, col, lower, upper, &PostgreSqlDialect {})?
215 }
216 #[cfg(feature = "src_sqlite")]
217 SourceType::SQLite => {
218 single_col_partition_query(query, col, lower, upper, &SQLiteDialect {})?
219 }
220 #[cfg(feature = "src_mysql")]
221 SourceType::MySQL => {
222 single_col_partition_query(query, col, lower, upper, &MySqlDialect {})?
223 }
224 #[cfg(feature = "src_mssql_common")]
225 SourceType::MsSQL => {
226 single_col_partition_query(query, col, lower, upper, &MsSqlDialect {})?
227 }
228 #[cfg(feature = "src_oracle")]
229 SourceType::Oracle => {
230 single_col_partition_query(query, col, lower, upper, &OracleDialect {})?
231 }
232 #[cfg(feature = "src_bigquery")]
233 SourceType::BigQuery => {
234 single_col_partition_query(query, col, lower, upper, &BigQueryDialect {})?
235 }
236 #[cfg(feature = "src_trino")]
237 SourceType::Trino => {
238 single_col_partition_query(query, col, lower, upper, &TrinoDialect {})?
239 }
240 #[cfg(feature = "src_clickhouse")]
241 SourceType::ClickHouse => {
242 single_col_partition_query(query, col, lower, upper, &ClickHouseDialect {})?
243 }
244 _ => unimplemented!("{:?} not implemented!", source_conn.ty),
245 };
246 CXQuery::Wrapped(query)
247}
248
249#[cfg(feature = "src_postgres")]
250#[throws(ConnectorXOutError)]
251fn pg_get_partition_range(conn: &Url, query: &str, col: &str) -> (i64, i64) {
252 let (config, tls) = rewrite_tls_args(conn)?;
253 let mut client = match tls {
254 None => config.connect(postgres::NoTls)?,
255 Some(tls_conn) => config.connect(tls_conn)?,
256 };
257 let range_query = get_partition_range_query(query, col, &PostgreSqlDialect {})?;
258 let row = client.query_one(range_query.as_str(), &[])?;
259
260 let col_type = PostgresTypeSystem::from(row.columns()[0].type_());
261 let (min_v, max_v) = match col_type {
262 PostgresTypeSystem::Int2(_) => {
263 let min_v: Option<i16> = row.get(0);
264 let max_v: Option<i16> = row.get(1);
265 (min_v.unwrap_or(0) as i64, max_v.unwrap_or(0) as i64)
266 }
267 PostgresTypeSystem::Int4(_) => {
268 let min_v: Option<i32> = row.get(0);
269 let max_v: Option<i32> = row.get(1);
270 (min_v.unwrap_or(0) as i64, max_v.unwrap_or(0) as i64)
271 }
272 PostgresTypeSystem::Int8(_) => {
273 let min_v: Option<i64> = row.get(0);
274 let max_v: Option<i64> = row.get(1);
275 (min_v.unwrap_or(0), max_v.unwrap_or(0))
276 }
277 PostgresTypeSystem::Float4(_) => {
278 let min_v: Option<f32> = row.get(0);
279 let max_v: Option<f32> = row.get(1);
280 (min_v.unwrap_or(0.0) as i64, max_v.unwrap_or(0.0) as i64)
281 }
282 PostgresTypeSystem::Float8(_) => {
283 let min_v: Option<f64> = row.get(0);
284 let max_v: Option<f64> = row.get(1);
285 (min_v.unwrap_or(0.0) as i64, max_v.unwrap_or(0.0) as i64)
286 }
287 PostgresTypeSystem::Numeric(_) => {
288 let min_v: Option<Decimal> = row.get(0);
289 let max_v: Option<Decimal> = row.get(1);
290 (
291 min_v.unwrap_or(dec!(0.0)).to_i64().unwrap_or(0),
292 max_v.unwrap_or(dec!(0.0)).to_i64().unwrap_or(0),
293 )
294 }
295 _ => throw!(anyhow!(
296 "Partition can only be done on int or float columns"
297 )),
298 };
299
300 (min_v, max_v)
301}
302
303#[cfg(feature = "src_sqlite")]
304#[throws(ConnectorXOutError)]
305fn sqlite_get_partition_range(conn: &Url, query: &str, col: &str) -> (i64, i64) {
306 let conn = Connection::open(&conn.as_str()[9..])?;
308 let (min_query, max_query) = get_partition_range_query_sep(query, col, &SQLiteDialect {})?;
311 let mut error = None;
312 let min_v = conn.query_row(min_query.as_str(), [], |row| {
313 let col_type = row.get_ref(0)?.data_type();
315 match col_type {
316 Type::Integer => row.get(0),
317 Type::Real => {
318 let v: f64 = row.get(0)?;
319 Ok(v as i64)
320 }
321 Type::Null => Ok(0),
322 _ => {
323 error = Some(anyhow!("Partition can only be done on integer columns"));
324 Ok(0)
325 }
326 }
327 })?;
328 match error {
329 None => {}
330 Some(e) => throw!(e),
331 }
332 let max_v = conn.query_row(max_query.as_str(), [], |row| {
333 let col_type = row.get_ref(0)?.data_type();
334 match col_type {
335 Type::Integer => row.get(0),
336 Type::Real => {
337 let v: f64 = row.get(0)?;
338 Ok(v as i64)
339 }
340 Type::Null => Ok(0),
341 _ => {
342 error = Some(anyhow!("Partition can only be done on integer columns"));
343 Ok(0)
344 }
345 }
346 })?;
347 match error {
348 None => {}
349 Some(e) => throw!(e),
350 }
351
352 (min_v, max_v)
353}
354
355#[cfg(feature = "src_mysql")]
356#[throws(ConnectorXOutError)]
357fn mysql_get_partition_range(conn: &Url, query: &str, col: &str) -> (i64, i64) {
358 let pool = Pool::new(build_opts(conn.as_str())?)?;
359 let mut conn = pool.get_conn()?;
360 let range_query = get_partition_range_query(query, col, &MySqlDialect {})?;
361 let row: Row = conn
362 .query_first(range_query)?
363 .ok_or_else(|| anyhow!("mysql range: no row returns"))?;
364
365 let col_type = MySQLTypeSystem::from((
366 &row.columns()[0].column_type(),
367 &row.columns()[0].flags(),
368 row.columns()[0].character_set(),
369 ));
370
371 let (min_v, max_v) = match col_type {
372 MySQLTypeSystem::Tiny(_) => {
373 let min_v: Option<i8> = row
374 .get(0)
375 .ok_or_else(|| anyhow!("mysql range: cannot get min value"))?;
376 let max_v: Option<i8> = row
377 .get(1)
378 .ok_or_else(|| anyhow!("mysql range: cannot get max value"))?;
379 (min_v.unwrap_or(0) as i64, max_v.unwrap_or(0) as i64)
380 }
381 MySQLTypeSystem::Short(_) => {
382 let min_v: Option<i16> = row
383 .get(0)
384 .ok_or_else(|| anyhow!("mysql range: cannot get min value"))?;
385 let max_v: Option<i16> = row
386 .get(1)
387 .ok_or_else(|| anyhow!("mysql range: cannot get max value"))?;
388 (min_v.unwrap_or(0) as i64, max_v.unwrap_or(0) as i64)
389 }
390 MySQLTypeSystem::Int24(_) => {
391 let min_v: Option<i32> = row
392 .get(0)
393 .ok_or_else(|| anyhow!("mysql range: cannot get min value"))?;
394 let max_v: Option<i32> = row
395 .get(1)
396 .ok_or_else(|| anyhow!("mysql range: cannot get max value"))?;
397 (min_v.unwrap_or(0) as i64, max_v.unwrap_or(0) as i64)
398 }
399 MySQLTypeSystem::Long(_) => {
400 let min_v: Option<i64> = row
401 .get(0)
402 .ok_or_else(|| anyhow!("mysql range: cannot get min value"))?;
403 let max_v: Option<i64> = row
404 .get(1)
405 .ok_or_else(|| anyhow!("mysql range: cannot get max value"))?;
406 (min_v.unwrap_or(0), max_v.unwrap_or(0))
407 }
408 MySQLTypeSystem::LongLong(_) => {
409 let min_v: Option<i64> = row
410 .get(0)
411 .ok_or_else(|| anyhow!("mysql range: cannot get min value"))?;
412 let max_v: Option<i64> = row
413 .get(1)
414 .ok_or_else(|| anyhow!("mysql range: cannot get max value"))?;
415 (min_v.unwrap_or(0), max_v.unwrap_or(0))
416 }
417 MySQLTypeSystem::UTiny(_) => {
418 let min_v: Option<u8> = row
419 .get(0)
420 .ok_or_else(|| anyhow!("mysql range: cannot get min value"))?;
421 let max_v: Option<u8> = row
422 .get(1)
423 .ok_or_else(|| anyhow!("mysql range: cannot get max value"))?;
424 (min_v.unwrap_or(0) as i64, max_v.unwrap_or(0) as i64)
425 }
426 MySQLTypeSystem::UShort(_) => {
427 let min_v: Option<u16> = row
428 .get(0)
429 .ok_or_else(|| anyhow!("mysql range: cannot get min value"))?;
430 let max_v: Option<u16> = row
431 .get(1)
432 .ok_or_else(|| anyhow!("mysql range: cannot get max value"))?;
433 (min_v.unwrap_or(0) as i64, max_v.unwrap_or(0) as i64)
434 }
435 MySQLTypeSystem::UInt24(_) => {
436 let min_v: Option<u32> = row
437 .get(0)
438 .ok_or_else(|| anyhow!("mysql range: cannot get min value"))?;
439 let max_v: Option<u32> = row
440 .get(1)
441 .ok_or_else(|| anyhow!("mysql range: cannot get max value"))?;
442 (min_v.unwrap_or(0) as i64, max_v.unwrap_or(0) as i64)
443 }
444 MySQLTypeSystem::ULong(_) => {
445 let min_v: Option<u32> = row
446 .get(0)
447 .ok_or_else(|| anyhow!("mysql range: cannot get min value"))?;
448 let max_v: Option<u32> = row
449 .get(1)
450 .ok_or_else(|| anyhow!("mysql range: cannot get max value"))?;
451 (min_v.unwrap_or(0) as i64, max_v.unwrap_or(0) as i64)
452 }
453 MySQLTypeSystem::ULongLong(_) => {
454 let min_v: Option<u64> = row
455 .get(0)
456 .ok_or_else(|| anyhow!("mysql range: cannot get min value"))?;
457 let max_v: Option<u64> = row
458 .get(1)
459 .ok_or_else(|| anyhow!("mysql range: cannot get max value"))?;
460 (min_v.unwrap_or(0) as i64, max_v.unwrap_or(0) as i64)
461 }
462 MySQLTypeSystem::Float(_) => {
463 let min_v: Option<f32> = row
464 .get(0)
465 .ok_or_else(|| anyhow!("mysql range: cannot get min value"))?;
466 let max_v: Option<f32> = row
467 .get(1)
468 .ok_or_else(|| anyhow!("mysql range: cannot get max value"))?;
469 (min_v.unwrap_or(0.0) as i64, max_v.unwrap_or(0.0) as i64)
470 }
471 MySQLTypeSystem::Double(_) => {
472 let min_v: Option<f64> = row
473 .get(0)
474 .ok_or_else(|| anyhow!("mysql range: cannot get min value"))?;
475 let max_v: Option<f64> = row
476 .get(1)
477 .ok_or_else(|| anyhow!("mysql range: cannot get max value"))?;
478 (min_v.unwrap_or(0.0) as i64, max_v.unwrap_or(0.0) as i64)
479 }
480 _ => throw!(anyhow!("Partition can only be done on int columns")),
481 };
482
483 (min_v, max_v)
484}
485
486#[cfg(feature = "src_mssql_tiberius")]
487#[throws(ConnectorXOutError)]
488fn mssql_get_partition_range(conn: &Url, query: &str, col: &str) -> (i64, i64) {
489 let rt = Runtime::new().expect("Failed to create runtime");
490 let config = mssql_config(conn)?;
491 let tcp = rt.block_on(TcpStream::connect(config.get_addr()))?;
492 tcp.set_nodelay(true)?;
493
494 let mut client = rt.block_on(Client::connect(config, tcp.compat_write()))?;
495
496 let range_query = get_partition_range_query(query, col, &MsSqlDialect {})?;
497 let query_result = rt.block_on(client.query(range_query.as_str(), &[]))?;
498 let row = rt.block_on(query_result.into_row())?.unwrap();
499
500 let col_type = MsSQLTypeSystem::from(&row.columns()[0].column_type());
501 let (min_v, max_v) = match col_type {
502 MsSQLTypeSystem::Tinyint(_) => {
503 let min_v: u8 = row.get(0).unwrap_or(0);
504 let max_v: u8 = row.get(1).unwrap_or(0);
505 (min_v as i64, max_v as i64)
506 }
507 MsSQLTypeSystem::Smallint(_) => {
508 let min_v: i16 = row.get(0).unwrap_or(0);
509 let max_v: i16 = row.get(1).unwrap_or(0);
510 (min_v as i64, max_v as i64)
511 }
512 MsSQLTypeSystem::Int(_) => {
513 let min_v: i32 = row.get(0).unwrap_or(0);
514 let max_v: i32 = row.get(1).unwrap_or(0);
515 (min_v as i64, max_v as i64)
516 }
517 MsSQLTypeSystem::Bigint(_) => {
518 let min_v: i64 = row.get(0).unwrap_or(0);
519 let max_v: i64 = row.get(1).unwrap_or(0);
520 (min_v, max_v)
521 }
522 MsSQLTypeSystem::Intn(_) => {
523 let min_v: IntN = row.get(0).unwrap_or(IntN(0));
524 let max_v: IntN = row.get(1).unwrap_or(IntN(0));
525 (min_v.0, max_v.0)
526 }
527 MsSQLTypeSystem::Float24(_) => {
528 let min_v: f32 = row.get(0).unwrap_or(0.0);
529 let max_v: f32 = row.get(1).unwrap_or(0.0);
530 (min_v as i64, max_v as i64)
531 }
532 MsSQLTypeSystem::Float53(_) => {
533 let min_v: f64 = row.get(0).unwrap_or(0.0);
534 let max_v: f64 = row.get(1).unwrap_or(0.0);
535 (min_v as i64, max_v as i64)
536 }
537 MsSQLTypeSystem::Floatn(_) => {
538 let min_v: FloatN = row.get(0).unwrap_or(FloatN(0.0));
539 let max_v: FloatN = row.get(1).unwrap_or(FloatN(0.0));
540 (min_v.0 as i64, max_v.0 as i64)
541 }
542 _ => throw!(anyhow!(
543 "Partition can only be done on int or float columns"
544 )),
545 };
546
547 (min_v, max_v)
548}
549
550#[cfg(feature = "src_oracle")]
551#[throws(ConnectorXOutError)]
552fn oracle_get_partition_range(conn: &Url, query: &str, col: &str) -> (i64, i64) {
553 let source = OracleSource::new(conn.as_str(), 1)?;
554 let conn = source.get_conn()?;
555 let range_query = get_partition_range_query(query, col, &OracleDialect {})?;
556 let row = conn.query_row(range_query.as_str(), &[])?;
557 let min_v: i64 = row.get(0).unwrap_or(0);
558 let max_v: i64 = row.get(1).unwrap_or(0);
559 (min_v, max_v)
560}
561
562#[cfg(feature = "src_bigquery")]
563#[throws(ConnectorXOutError)] fn bigquery_get_partition_range(conn: &Url, query: &str, col: &str) -> (i64, i64) {
565 let rt = Runtime::new().expect("Failed to create runtime");
566 let url = Url::parse(conn.as_str())?;
567 let sa_key_path = url.path();
568 let client = rt.block_on(gcp_bigquery_client::Client::from_service_account_key_file(
569 sa_key_path,
570 ))?;
571
572 let auth_data = std::fs::read_to_string(sa_key_path)?;
573 let auth_json: serde_json::Value = serde_json::from_str(&auth_data)?;
574 let project_id = auth_json
575 .get("project_id")
576 .ok_or_else(|| anyhow!("Cannot get project_id from auth file"))?
577 .as_str()
578 .ok_or_else(|| anyhow!("Cannot get project_id as string from auth file"))?;
579 let range_query = get_partition_range_query(query, col, &BigQueryDialect {})?;
580
581 let query_result = rt.block_on(client.job().query(
582 project_id,
583 gcp_bigquery_client::model::query_request::QueryRequest::new(range_query.as_str()),
584 ))?;
585 let mut rs = gcp_bigquery_client::model::query_response::ResultSet::new_from_query_response(
586 query_result,
587 );
588 rs.next_row();
589 let min_v = rs.get_i64(0)?.unwrap_or(0);
590 let max_v = rs.get_i64(1)?.unwrap_or(0);
591
592 (min_v, max_v)
593}
594
595#[cfg(feature = "src_trino")]
596#[throws(ConnectorXOutError)]
597fn trino_get_partition_range(conn: &Url, query: &str, col: &str) -> (i64, i64) {
598 use crate::sources::trino::{build_client_from_url, TrinoDialect, TrinoPartitionQueryResult};
599
600 let rt = Runtime::new().expect("Failed to create runtime");
601
602 let client =
603 build_client_from_url(conn).map_err(|e| anyhow!("Failed to build Trino client: {}", e))?;
604
605 let range_query = get_partition_range_query(query, col, &TrinoDialect {})?;
606 let query_result = rt.block_on(client.get_all::<TrinoPartitionQueryResult>(range_query));
607
608 let query_result = match query_result {
609 Ok(query_result) => Ok(query_result.into_vec()),
610 Err(e) => match e {
611 prusto::error::Error::EmptyData => {
612 Ok(vec![TrinoPartitionQueryResult { _col0: 0, _col1: 0 }])
613 }
614 _ => Err(anyhow!("Failed to get query result: {}", e)),
615 },
616 }?;
617
618 let result = query_result
619 .first()
620 .unwrap_or(&TrinoPartitionQueryResult { _col0: 0, _col1: 0 });
621
622 (result._col0, result._col1)
623}
624
625#[cfg(feature = "src_clickhouse")]
626#[throws(ConnectorXOutError)]
627fn clickhouse_get_partition_range(conn: &Url, query: &str, col: &str) -> (i64, i64) {
628 use sqlparser::dialect::ClickHouseDialect;
629
630 let rt = Arc::new(tokio::runtime::Runtime::new().expect("Failed to create runtime"));
631 let clickhouse_source = ClickHouseSource::new(rt.clone(), conn.as_str())
632 .expect("Failed to create ClickHouse client");
633
634 let range_query = get_partition_range_query(query, col, &ClickHouseDialect {})?;
635
636 let response = rt.block_on(async {
637 let mut cursor = clickhouse_source
638 .client
639 .query(range_query.as_str())
640 .fetch_bytes("JSONCompact")
641 .map_err(|e| anyhow!("ClickHouse error: {}", e))?;
642 let bytes = cursor
643 .collect()
644 .await
645 .map_err(|e| anyhow!("ClickHouse error: {}", e))?;
646 Ok::<_, ClickHouseSourceError>(bytes)
647 })?;
648
649 #[derive(Debug, Deserialize)]
650 struct MinMaxResponse {
651 data: Vec<Vec<JsonValue>>,
652 }
653
654 let parsed: MinMaxResponse = serde_json::from_slice(&response)
655 .map_err(|e| anyhow!("Failed to parse min max response: {}", e))?;
656
657 let (min_v, max_v) = if let Some(row) = parsed.data.first() {
658 let min_v = row.first().and_then(|v| v.as_i64()).unwrap_or(0);
659 let max_v = row.get(1).and_then(|v| v.as_i64()).unwrap_or(0);
660
661 (min_v, max_v)
662 } else {
663 (0, 0)
664 };
665
666 (min_v, max_v)
667}
668
669#[cfg(test)]
670mod tests {
671 use super::*;
672
673 fn sqlite_source_conn() -> SourceConn {
677 SourceConn::new(
678 SourceType::SQLite,
679 Url::parse("sqlite://test.db").unwrap(),
680 "binary".to_string(),
681 )
682 }
683
684 fn queries_as_strings(queries: &[CXQuery]) -> Vec<String> {
685 queries.iter().map(|q| q.to_string()).collect()
686 }
687
688 #[test]
689 fn partitions_evenly_divisible_range() {
690 let part = PartitionQuery::new("SELECT * FROM test", "id", Some(0), Some(9), 2);
691 let source_conn = sqlite_source_conn();
692 let queries = partition(&part, &source_conn).unwrap();
693
694 assert_eq!(queries.len(), 2);
695 let strings = queries_as_strings(&queries);
696 assert!(strings[0].contains("0 <=") && strings[0].contains("< 5"));
697 assert!(strings[1].contains("5 <=") && strings[1].contains("< 10"));
698 }
699
700 #[test]
701 fn partitions_range_not_evenly_divisible() {
702 let part = PartitionQuery::new("SELECT * FROM test", "id", Some(0), Some(10), 3);
705 let source_conn = sqlite_source_conn();
706 let queries = partition(&part, &source_conn).unwrap();
707
708 assert_eq!(queries.len(), 3);
709 let strings = queries_as_strings(&queries);
710 assert!(strings[0].contains("0 <=") && strings[0].contains("< 3"));
711 assert!(strings[1].contains("3 <=") && strings[1].contains("< 6"));
712 assert!(strings[2].contains("6 <=") && strings[2].contains("< 11"));
714 }
715
716 #[test]
717 fn single_partition_covers_whole_range() {
718 let part = PartitionQuery::new("SELECT * FROM test", "id", Some(5), Some(15), 1);
719 let source_conn = sqlite_source_conn();
720 let queries = partition(&part, &source_conn).unwrap();
721
722 assert_eq!(queries.len(), 1);
723 let strings = queries_as_strings(&queries);
724 assert!(strings[0].contains("5 <=") && strings[0].contains("< 16"));
725 }
726
727 #[test]
728 fn min_equals_max_single_row_range() {
729 let part = PartitionQuery::new("SELECT * FROM test", "id", Some(7), Some(7), 1);
730 let source_conn = sqlite_source_conn();
731 let queries = partition(&part, &source_conn).unwrap();
732
733 assert_eq!(queries.len(), 1);
734 let strings = queries_as_strings(&queries);
735 assert!(strings[0].contains("7 <=") && strings[0].contains("< 8"));
736 }
737
738 #[test]
739 fn negative_range_partitions_correctly() {
740 let part = PartitionQuery::new("SELECT * FROM test", "id", Some(-10), Some(-1), 2);
741 let source_conn = sqlite_source_conn();
742 let queries = partition(&part, &source_conn).unwrap();
743
744 assert_eq!(queries.len(), 2);
745 let strings = queries_as_strings(&queries);
746 assert!(strings[0].contains("-10 <=") && strings[0].contains("< -5"));
747 assert!(strings[1].contains("-5 <=") && strings[1].contains("< 0"));
748 }
749
750 #[test]
751 fn partially_specified_range_returns_error() {
752 let part = PartitionQuery::new("SELECT * FROM test", "id", Some(0), None, 2);
753 let source_conn = sqlite_source_conn();
754 let result = partition(&part, &source_conn);
755
756 assert!(result.is_err());
757 }
758
759 #[test]
760 fn zero_partitions_returns_error_on_division_by_zero() {
761 let part = PartitionQuery::new("SELECT * FROM test", "id", Some(0), Some(9), 0);
762 let source_conn = sqlite_source_conn();
763 let result = partition(&part, &source_conn);
764 assert!(result.is_err());
765 let err = result.unwrap_err().to_string();
766 assert!(err.contains("partition count (num) must be greater than zero"));
767 }
768
769 #[test]
770 fn inverted_range_returns_error() {
771 let part = PartitionQuery::new("SELECT * FROM test", "id", Some(10), Some(0), 2);
772 let source_conn = sqlite_source_conn();
773 let result = partition(&part, &source_conn);
774 assert!(result.is_err());
775 let err = result.unwrap_err().to_string();
776 assert!(
777 err.contains("partition range is invalid"),
778 "unexpected error message: {}",
779 err
780 );
781 }
782
783 #[test]
784 fn more_partitions_than_range_values_preserves_partition_count() {
785 let part = PartitionQuery::new("SELECT * FROM test", "id", Some(0), Some(1), 4);
786 let source_conn = sqlite_source_conn();
787 let queries = partition(&part, &source_conn).unwrap();
788
789 assert_eq!(queries.len(), 4);
790 let strings = queries_as_strings(&queries);
791 assert!(strings[0].contains("0 <=") && strings[0].contains("< 0"));
792 assert!(strings[1].contains("0 <=") && strings[1].contains("< 0"));
793 assert!(strings[2].contains("0 <=") && strings[2].contains("< 0"));
794 assert!(strings[3].contains("0 <=") && strings[3].contains("< 2"));
795 }
796
797 #[test]
798 fn large_range_does_not_overflow_with_small_num() {
799 let part = PartitionQuery::new(
802 "SELECT * FROM test",
803 "id",
804 Some(0),
805 Some(1_000_000_000_000),
806 4,
807 );
808 let source_conn = sqlite_source_conn();
809 let queries = partition(&part, &source_conn).unwrap();
810
811 assert_eq!(queries.len(), 4);
812 let strings = queries_as_strings(&queries);
813 assert!(strings[0].contains("0 <="));
814 assert!(strings[3].contains("< 1000000000001"));
815 }
816
817 #[test]
818 fn many_small_partitions_produce_correct_count_and_bounds() {
819 let part = PartitionQuery::new("SELECT * FROM test", "id", Some(1), Some(100), 10);
820 let source_conn = sqlite_source_conn();
821 let queries = partition(&part, &source_conn).unwrap();
822
823 assert_eq!(queries.len(), 10);
824 let strings = queries_as_strings(&queries);
825 assert!(strings[0].contains("1 <=") && strings[0].contains("< 11"));
826 assert!(strings[9].contains("91 <=") && strings[9].contains("< 101"));
827 }
828
829 #[test]
830 fn partition_range_overflow() {
831 let part = PartitionQuery::new(
833 "SELECT * FROM test",
834 "id",
835 Some(i64::MIN),
836 Some(i64::MAX),
837 2,
838 );
839 let source_conn = sqlite_source_conn();
840 let res = partition(&part, &source_conn);
841 assert!(res.is_err());
842 let err = res.unwrap_err().to_string();
843 assert!(
844 err.contains("partition range overflow"),
845 "unexpected error message: {}",
846 err
847 );
848 }
849
850 #[test]
851 fn partition_upper_overflow_at_i64_max() {
852 let part = PartitionQuery::new(
855 "SELECT * FROM test",
856 "id",
857 Some(i64::MAX - 5),
858 Some(i64::MAX),
859 1,
860 );
861 let source_conn = sqlite_source_conn();
862 let res = partition(&part, &source_conn);
863 assert!(res.is_err());
864 let err = res.unwrap_err().to_string();
865 assert!(
866 err.contains("partition upper bound overflow"),
867 "unexpected error message: {}",
868 err
869 );
870 }
871
872 #[test]
873 fn partition_upper_overflow_multi_partition() {
874 let part = PartitionQuery::new(
876 "SELECT * FROM test",
877 "id",
878 Some(i64::MAX - 5),
879 Some(i64::MAX),
880 2,
881 );
882 let source_conn = sqlite_source_conn();
883 let res = partition(&part, &source_conn);
884 assert!(res.is_err());
885 let err = res.unwrap_err().to_string();
886 assert!(
887 err.contains("partition upper bound overflow"),
888 "unexpected error message: {}",
889 err
890 );
891 }
892}