diff --git a/Cargo.lock b/Cargo.lock index aaa5a44291..5432cffdb3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2454,6 +2454,14 @@ dependencies = [ "url", ] +[[package]] +name = "sqlx-query-builder" +version = "0.1.0" +dependencies = [ + "sqlx-core", + "thiserror", +] + [[package]] name = "sqlx-rt" version = "0.3.0" diff --git a/Cargo.toml b/Cargo.toml index ea93952103..4c71b0b7db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ members = [ "sqlx-test", "sqlx-cli", "sqlx-bench", + "sqlx-query-builder", "examples/mysql/todos", "examples/postgres/json", "examples/postgres/listen", diff --git a/sqlx-query-builder/Cargo.toml b/sqlx-query-builder/Cargo.toml new file mode 100644 index 0000000000..a98139157f --- /dev/null +++ b/sqlx-query-builder/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "sqlx-query-builder" +version = "0.1.0" +authors = ["Anit Nilay "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +sqlx-core = { version = "0.5.1", default-features = false, path = "../sqlx-core" } +thiserror = "1.0" + +[features] +default = [ "runtime-async-std-native-tls" ] + +# runtimes +runtime-actix-native-tls = [ "sqlx-core/runtime-actix-native-tls", "_rt-actix" ] +runtime-async-std-native-tls = [ "sqlx-core/runtime-async-std-native-tls", "_rt-async-std" ] +runtime-tokio-native-tls = [ "sqlx-core/runtime-tokio-native-tls", "_rt-tokio" ] + +runtime-actix-rustls = [ "sqlx-core/runtime-actix-rustls", "_rt-actix" ] +runtime-async-std-rustls = [ "sqlx-core/runtime-async-std-rustls", "_rt-async-std" ] +runtime-tokio-rustls = [ "sqlx-core/runtime-tokio-rustls", "_rt-tokio" ] + +# for conditional compilation +_rt-actix = [] +_rt-async-std = [] +_rt-tokio = [] + +# offline building support +# offline = ["sqlx-core/offline", "hex", "once_cell", "serde", "serde_json", "sha2"] + +# database +mysql = [ "sqlx-core/mysql" ] +postgres = [ "sqlx-core/postgres" ] +sqlite = [ "sqlx-core/sqlite" ] +mssql = [ "sqlx-core/mssql" ] + +# type +# bigdecimal = [ "sqlx-core/bigdecimal" ] +# decimal = [ "sqlx-core/decimal" ] +# chrono = [ "sqlx-core/chrono" ] +# time = [ "sqlx-core/time" ] +# ipnetwork = [ "sqlx-core/ipnetwork" ] +# uuid = [ "sqlx-core/uuid" ] +# bit-vec = [ "sqlx-core/bit-vec" ] +# json = [ "sqlx-core/json" ] diff --git a/sqlx-query-builder/src/error.rs b/sqlx-query-builder/src/error.rs new file mode 100644 index 0000000000..9ce9ed8e16 --- /dev/null +++ b/sqlx-query-builder/src/error.rs @@ -0,0 +1,9 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Error { + #[error("Error in query: {}", _0)] + InvalidQuery(String) +} + +pub type Result = std::result::Result; diff --git a/sqlx-query-builder/src/lib.rs b/sqlx-query-builder/src/lib.rs new file mode 100644 index 0000000000..ecbd584aef --- /dev/null +++ b/sqlx-query-builder/src/lib.rs @@ -0,0 +1,46 @@ +use select::Select; +use table::Table; + +mod error; +mod mysql; +mod query; +mod select; +mod table; + +pub trait QueryBuilder { + const SYSTEM_IDENTIFIER_START: &'static str; + const SYSTEM_IDENTIFIER_END: &'static str; + + fn build(query: T) -> error::Result<(String, Vec)> + where + T: Into; + + fn build_select(select: Select) -> error::Result<(String, Vec)>; + fn build_table(table: Table) -> error::Result; +} + +#[cfg(test)] +mod tests { + use crate::{select::Select, QueryBuilder}; + use sqlx_core::mysql::MySql; + + #[test] + fn simple_select_statement() { + let (query, _) = + MySql::build(Select::new().and_select("*".into()).and_from("sqlx")).unwrap(); + + assert_eq!(query, "SELECT * FROM sqlx;".to_string()); + } + #[test] + fn select_with_table_alias() { + let (query, _) = MySql::build(Select::new().and_select("*".into()).and_from(("sqlx", "s", "sqlx_db"))).unwrap(); + assert_eq!(query, "SELECT * FROM sqlx_db.sqlx as s;"); + } + + #[test] + fn select_statement_with_column_names() { + let (query, _) = MySql::build(Select::new().and_select("name".into()).from("sqlx")).unwrap(); + + assert_eq!(query, "SELECT name FROM sqlx;".to_string()); + } +} diff --git a/sqlx-query-builder/src/mysql.rs b/sqlx-query-builder/src/mysql.rs new file mode 100644 index 0000000000..e8db5f58fc --- /dev/null +++ b/sqlx-query-builder/src/mysql.rs @@ -0,0 +1,58 @@ +use std::fmt::Write; + +use sqlx_core::mysql::MySql; +use crate::{QueryBuilder, error, query, select::Select, table::{Table, TableType}}; + +impl QueryBuilder for MySql { + const SYSTEM_IDENTIFIER_START: &'static str = "`"; + const SYSTEM_IDENTIFIER_END: &'static str = "`"; + + fn build(query: T) -> error::Result<(String, Vec)> + where + T: Into, + { + match query.into() { + query::QueryType::Select(select) => Self::build_select(select), + _ => Ok(("*".into(), vec![])), + } + } + + fn build_select(select: Select) -> error::Result<(String, Vec)> { + let mut query = String::new(); + query.push_str("SELECT "); + query.push_str(&select.columns.join(", ")); + query.push_str(" FROM "); + query.push_str( + &select + .tables + .into_iter() + .map(|f| Self::build_table(f).unwrap()) + .collect::>() + .join(", "), + ); + query.push_str(";"); + Ok((query, vec![])) + } + + fn build_table(table: Table) -> error::Result { + let mut table_sql = String::new(); + + if let Some(database) = table.database { + table_sql.write_str(&database).unwrap(); + table_sql.write_str(".").unwrap(); + } + + match table.table_type { + TableType::Table(s) => table_sql.write_str(&s).unwrap(), + }; + + if let Some(alias) = table.alias { + table_sql.write_str(" as ").unwrap(); + table_sql.write_str(&alias).unwrap(); + } + + Ok(table_sql) + } + + +} diff --git a/sqlx-query-builder/src/query.rs b/sqlx-query-builder/src/query.rs new file mode 100644 index 0000000000..1bcdb9e584 --- /dev/null +++ b/sqlx-query-builder/src/query.rs @@ -0,0 +1,8 @@ +use crate::select::Select; + +pub enum QueryType { + Select(Select), + Update, + Alter, + Delete, +} diff --git a/sqlx-query-builder/src/select.rs b/sqlx-query-builder/src/select.rs new file mode 100644 index 0000000000..4fc3bf1f2a --- /dev/null +++ b/sqlx-query-builder/src/select.rs @@ -0,0 +1,59 @@ +use crate::{query::QueryType, table::Table}; + +#[derive(Clone, Default)] +pub struct Select { + pub(crate) tables: Vec, + pub(crate) columns: Vec, + pub(crate) conditions: Option, + pub(crate) ordering: Option, + pub(crate) grouping: Option, + pub(crate) having: Option, + pub(crate) limit: Option, + pub(crate) offset: Option, + pub(crate) joins: Option, + pub(crate) ctes: Option, + pub(crate) distinct: bool, +} + +impl Select { + pub fn new() -> Self { + Self { ..Self::default() } + } + + pub fn select(mut self, colums: Vec) -> Self { + self.columns = colums; + self + } + + pub fn and_select(mut self, column: String) -> Self { + self.columns.push(column); + self + } + + pub fn from(mut self, table: T) -> Self + where + T: Into
, + { + self.tables = vec![table.into()]; + self + } + + pub fn and_from(mut self, table: T) -> Self + where + T: Into
, + { + self.tables.push(table.into()); + self + } + + pub fn condition(mut self, condition: Option) -> Self { + self.conditions = condition; + self + } +} + +impl Into for Select { + fn into(self) -> QueryType { + QueryType::Select(self) + } +} diff --git a/sqlx-query-builder/src/table.rs b/sqlx-query-builder/src/table.rs new file mode 100644 index 0000000000..86011e6141 --- /dev/null +++ b/sqlx-query-builder/src/table.rs @@ -0,0 +1,55 @@ +#[derive(Clone)] +pub enum TableType { + Table(String), +} + +impl Default for TableType { + fn default() -> Self { + TableType::Table(String::default()) + } +} + +#[derive(Clone, Default)] +pub struct Table { + pub(crate) table_type: TableType, + pub(crate) alias: Option, + pub(crate) database: Option, +} + +impl From for Table { + fn from(s: String) -> Table { + Table { + table_type: TableType::Table(s), + ..Table::default() + } + } +} + +impl<'a> From<&'a str> for Table { + fn from(s: &'a str) -> Table { + Table { + table_type: TableType::Table(s.into()), + ..Table::default() + } + } +} + +impl<'a> From<(&'a str, &'a str)> for Table { + fn from(s: (&'a str, &'a str)) -> Table { + Table { + table_type: TableType::Table(s.0.into()), + alias: Some(s.1.into()), + ..Table::default() + } + } +} + +impl<'a> From<(&'a str, &'a str, &'a str)> for Table { + fn from(s: (&'a str, &'a str, &'a str)) -> Table { + Table { + table_type: TableType::Table(s.0.into()), + alias: Some(s.1.into()), + database: Some(s.2.into()), + } + } +}