connectorx/
source_router.rs1use crate::constants::CONNECTORX_PROTOCOL;
2use crate::errors::{ConnectorXError, Result};
3use crate::utils::remove_query_params;
4use anyhow::anyhow;
5use fehler::throws;
6#[cfg(feature = "src_postgres")]
7use redshift_iam::redshift_to_postgres;
8use std::convert::TryFrom;
9use url::Url;
10
11#[derive(Debug, Clone)]
12pub enum SourceType {
13 Postgres,
14 SQLite,
15 MySQL,
16 MsSQL,
17 Oracle,
18 BigQuery,
19 DuckDB,
20 Trino,
21 ClickHouse,
22 Unknown,
23}
24
25#[derive(Debug, Clone)]
26pub struct SourceConn {
27 pub ty: SourceType,
28 pub conn: Url,
29 pub proto: String,
30}
31
32impl TryFrom<&str> for SourceConn {
33 type Error = ConnectorXError;
34
35 fn try_from(conn: &str) -> Result<SourceConn> {
36 let old_url = Url::parse(conn).map_err(|e| anyhow!("parse error: {}", e))?;
37
38 let proto = match old_url.query_pairs().find(|p| p.0 == CONNECTORX_PROTOCOL) {
40 Some((_, proto)) => proto.to_string(),
41 None => "binary".to_string(),
42 };
43
44 let url = remove_query_params(&old_url, &[CONNECTORX_PROTOCOL]);
46
47 match url.scheme().split('+').collect::<Vec<&str>>()[0] {
50 "postgres" | "postgresql" => Ok(SourceConn::new(SourceType::Postgres, url, proto)),
51 #[cfg(feature = "src_postgres")]
52 "redshift-iam" => Ok(SourceConn::new(
53 SourceType::Postgres,
54 redshift_to_postgres(url),
55 "cursor".to_string(),
56 )),
57 "sqlite" => Ok(SourceConn::new(SourceType::SQLite, url, proto)),
58 "mysql" => Ok(SourceConn::new(SourceType::MySQL, url, proto)),
59 "mssql" => Ok(SourceConn::new(SourceType::MsSQL, url, proto)),
60 "oracle" => Ok(SourceConn::new(SourceType::Oracle, url, proto)),
61 "bigquery" => Ok(SourceConn::new(SourceType::BigQuery, url, proto)),
62 "duckdb" => Ok(SourceConn::new(SourceType::DuckDB, url, proto)),
63 "trino" => Ok(SourceConn::new(SourceType::Trino, url, proto)),
64 "clickhouse" => Ok(SourceConn::new(SourceType::ClickHouse, url, proto)),
65 _ => Ok(SourceConn::new(SourceType::Unknown, url, proto)),
66 }
67 }
68}
69
70impl SourceConn {
71 pub fn new(ty: SourceType, conn: Url, proto: String) -> Self {
72 Self { ty, conn, proto }
73 }
74 pub fn set_protocol(&mut self, protocol: &str) {
75 self.proto = protocol.to_string();
76 }
77}
78
79#[throws(ConnectorXError)]
80pub fn parse_source(conn: &str, protocol: Option<&str>) -> SourceConn {
81 let mut source_conn = SourceConn::try_from(conn)?;
82 if let Some(p) = protocol {
83 source_conn.set_protocol(p)
84 }
85 source_conn
86}
87
88#[cfg(test)]
89mod tests {
90 use super::SourceConn;
91 use std::convert::TryFrom;
92
93 #[test]
97 fn keeps_remaining_query_params_verbatim() {
98 let source_conn = SourceConn::try_from(
99 "postgresql://u:p@host:5432/db?options=-c%20statement_timeout%3D1s&cxprotocol=cursor",
100 )
101 .unwrap();
102
103 assert_eq!(
104 source_conn.conn.query(),
105 Some("options=-c%20statement_timeout%3D1s")
106 );
107 assert_eq!(source_conn.proto, "cursor");
108 }
109
110 #[test]
112 fn leaves_the_query_alone_when_there_is_nothing_to_remove() {
113 let source_conn = SourceConn::try_from("mysql://host:3306/db?a=x%20y&b=%2Fz").unwrap();
114
115 assert_eq!(source_conn.conn.query(), Some("a=x%20y&b=%2Fz"));
116 assert_eq!(source_conn.proto, "binary");
117 }
118}