diff --git a/Cargo.toml b/Cargo.toml index 878845f..914a45b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rorm-sql" -version = "0.8.2" +version = "0.10.2" edition = "2021" keywords = ["database", "abstraction-layer", "sqlite", "postgres", "mysql"] categories = ["database"] @@ -17,14 +17,13 @@ chrono = { version = ">=0.4.20", default-features = false } time = { version = "~0.3" } # Serialization library -serde = { version = "~1" } serde_json = { version = "~1" } # Uuid support uuid = { version = "~1" } # SQlite bindings for printf -libsqlite3-sys = { version = "~0.26", optional = true } +libsqlite3-sys = { version = "~0.30", optional = true } # Bitvec support bit-vec = { version = "~0.6", optional = true } @@ -33,10 +32,7 @@ ipnetwork = { version = "~0.20", optional = true } # Mac Address support mac_address = { version = "~1", optional = true } -rorm-declaration = { version = "0.4.0", path = "../rorm-declaration" } - -[build-dependencies] -rustc_version = "0.4.0" +rorm-declaration = { version = "0.4.1", path = "../rorm-declaration" } [package.metadata.docs.rs] all-features = true diff --git a/build.rs b/build.rs deleted file mode 100644 index 1a95d7e..0000000 --- a/build.rs +++ /dev/null @@ -1,12 +0,0 @@ -use rustc_version::{version_meta, Channel}; - -fn main() { - // Set cfg flags depending on release channel - let channel = match version_meta().unwrap().channel { - Channel::Stable => "CHANNEL_STABLE", - Channel::Beta => "CHANNEL_BETA", - Channel::Nightly => "CHANNEL_NIGHTLY", - Channel::Dev => "CHANNEL_DEV", - }; - println!("cargo:rustc-cfg={channel}") -} diff --git a/src/alter_table.rs b/src/alter_table.rs index 095bba6..5464564 100644 --- a/src/alter_table.rs +++ b/src/alter_table.rs @@ -80,9 +80,7 @@ pub enum AlterTableImpl<'until_build, 'post_build> { Postgres(AlterTableData<'until_build, 'post_build>), } -impl<'until_build, 'post_build> AlterTable<'post_build> - for AlterTableImpl<'until_build, 'post_build> -{ +impl<'post_build> AlterTable<'post_build> for AlterTableImpl<'_, 'post_build> { fn build(self) -> Result>)>, Error> { match self { #[cfg(feature = "sqlite")] diff --git a/src/conditional.rs b/src/conditional.rs index 3125901..27266e9 100644 --- a/src/conditional.rs +++ b/src/conditional.rs @@ -39,7 +39,7 @@ pub trait BuildCondition<'a>: 'a { /** This enum represents all available ternary expression. */ -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum TernaryCondition<'a> { /// Between represents "{} BETWEEN {} AND {}" from SQL Between(Box<[Condition<'a>; 3]>), @@ -72,7 +72,7 @@ impl<'a> BuildCondition<'a> for TernaryCondition<'a> { /** This enum represents a binary expression. */ -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum BinaryCondition<'a> { /// Representation of "{} = {}" in SQL Equals(Box<[Condition<'a>; 2]>), @@ -98,6 +98,24 @@ pub enum BinaryCondition<'a> { In(Box<[Condition<'a>; 2]>), /// Representation of "{} NOT IN {}" in SQL NotIn(Box<[Condition<'a>; 2]>), + /// Representation of "{} ILIKE {}" in PostgreSQL + #[cfg(feature = "postgres-only")] + ILike(Box<[Condition<'a>; 2]>), + /// Representation of "{} NOT ILIKE {}" in PostgreSQL + #[cfg(feature = "postgres-only")] + NotILike(Box<[Condition<'a>; 2]>), + /// Representation of "{} << {}" for `inet` in PostgreSQL + #[cfg(feature = "postgres-only")] + Contained(Box<[Condition<'a>; 2]>), + /// Representation of "{} <<= {}" for `inet` in PostgreSQL + #[cfg(feature = "postgres-only")] + ContainedOrEquals(Box<[Condition<'a>; 2]>), + /// Representation of "{} >> {}" for `inet` in PostgreSQL + #[cfg(feature = "postgres-only")] + Contains(Box<[Condition<'a>; 2]>), + /// Representation of "{} >>= {}" for `inet` in PostgreSQL + #[cfg(feature = "postgres-only")] + ContainsOrEquals(Box<[Condition<'a>; 2]>), } impl<'a> BuildCondition<'a> for BinaryCondition<'a> { @@ -120,11 +138,28 @@ impl<'a> BuildCondition<'a> for BinaryCondition<'a> { BinaryCondition::NotRegexp(params) => ("NOT REGEXP", params.as_ref()), BinaryCondition::In(params) => ("IN", params.as_ref()), BinaryCondition::NotIn(params) => ("NOT IN", params.as_ref()), + #[cfg(feature = "postgres-only")] + BinaryCondition::ILike(params) => ("ILIKE", params.as_ref()), + #[cfg(feature = "postgres-only")] + BinaryCondition::NotILike(params) => ("NOT ILIKE", params.as_ref()), + #[cfg(feature = "postgres-only")] + BinaryCondition::Contained(params) => ("<<", params.as_ref()), + #[cfg(feature = "postgres-only")] + BinaryCondition::ContainedOrEquals(params) => ("<<=", params.as_ref()), + #[cfg(feature = "postgres-only")] + BinaryCondition::Contains(params) => (">>", params.as_ref()), + #[cfg(feature = "postgres-only")] + BinaryCondition::ContainsOrEquals(params) => (">>=", params.as_ref()), }; write!(writer, "(")?; lhs.build_to_writer(writer, dialect, lookup)?; write!(writer, " {keyword} ")?; rhs.build_to_writer(writer, dialect, lookup)?; + #[cfg(feature = "sqlite")] + if matches!(dialect, DBImpl::SQLite) && matches!(keyword, "LIKE" | "NOT LIKE") { + // Sqlite does not default it + write!(writer, " ESCAPE '\'")?; + } write!(writer, ")")?; Ok(()) } @@ -133,7 +168,7 @@ impl<'a> BuildCondition<'a> for BinaryCondition<'a> { /** This enum represents all available unary conditions. */ -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum UnaryCondition<'a> { /// Representation of SQL's "{} IS NULL" IsNull(Box>), @@ -177,7 +212,7 @@ impl<'a> BuildCondition<'a> for UnaryCondition<'a> { /** This enum represents a condition tree. */ -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum Condition<'a> { /// A list of [Condition]s, that get expanded to "{} AND {} ..." Conjunction(Vec>), @@ -235,7 +270,7 @@ impl<'a> BuildCondition<'a> for Condition<'a> { #[cfg(feature = "sqlite")] DBImpl::SQLite => { if let Some(table_name) = table_name { - write!(writer, "{table_name}.")?; + write!(writer, "\"{table_name}\".")?; } write!(writer, "{column_name}") } diff --git a/src/create_column.rs b/src/create_column.rs index 2738e58..8be3366 100644 --- a/src/create_column.rs +++ b/src/create_column.rs @@ -101,9 +101,7 @@ pub enum CreateColumnImpl<'until_build, 'post_build> { Postgres(CreateColumnPostgresData<'until_build, 'post_build>), } -impl<'until_build, 'post_build> CreateColumn<'post_build> - for CreateColumnImpl<'until_build, 'post_build> -{ +impl<'post_build> CreateColumn<'post_build> for CreateColumnImpl<'_, 'post_build> { fn build(self, s: &mut String) -> Result<(), Error> { match self { #[cfg(feature = "sqlite")] diff --git a/src/delete.rs b/src/delete.rs index caee2a5..8cae8f9 100644 --- a/src/delete.rs +++ b/src/delete.rs @@ -78,7 +78,7 @@ impl<'until_build, 'post_query> Delete<'until_build, 'post_query> match self { #[cfg(feature = "sqlite")] DeleteImpl::SQLite(mut d) => { - let mut s = format!("DELETE FROM {} ", d.model); + let mut s = format!("DELETE FROM \"{}\" ", d.model); if d.where_clause.is_some() { write!( diff --git a/src/drop_table.rs b/src/drop_table.rs index 4bb5d62..55077dc 100644 --- a/src/drop_table.rs +++ b/src/drop_table.rs @@ -46,7 +46,7 @@ pub enum DropTableImpl<'until_build> { Postgres(DropTableData<'until_build>), } -impl<'until_build> DropTable for DropTableImpl<'until_build> { +impl DropTable for DropTableImpl<'_> { fn if_exists(mut self) -> Self { match self { #[cfg(feature = "sqlite")] @@ -63,7 +63,7 @@ impl<'until_build> DropTable for DropTableImpl<'until_build> { match self { #[cfg(feature = "sqlite")] DropTableImpl::SQLite(d) => format!( - "DROP TABLE {}{};", + "DROP TABLE \"{}\"{};", d.name, if d.if_exists { " IF EXISTS" } else { "" } ), diff --git a/src/insert.rs b/src/insert.rs index 1858eaa..87073b7 100644 --- a/src/insert.rs +++ b/src/insert.rs @@ -69,7 +69,7 @@ pub enum InsertImpl<'until_build, 'post_build> { Postgres(InsertData<'until_build, 'post_build>), } -impl<'until_build, 'post_build> Insert<'post_build> for InsertImpl<'until_build, 'post_build> { +impl<'post_build> Insert<'post_build> for InsertImpl<'_, 'post_build> { fn rollback_transaction(mut self) -> Self { match self { #[cfg(feature = "sqlite")] @@ -89,7 +89,7 @@ impl<'until_build, 'post_build> Insert<'post_build> for InsertImpl<'until_build, // Handle case, if no columns should be inserted, aka an empty insert if d.columns.is_empty() { let mut s = format!( - "INSERT {}INTO {} DEFAULT VALUES", + "INSERT {}INTO \"{}\" DEFAULT VALUES", match d.on_conflict { OnConflict::ABORT => "OR ABORT ", OnConflict::ROLLBACK => "OR ROLLBACK ", @@ -114,7 +114,7 @@ impl<'until_build, 'post_build> Insert<'post_build> for InsertImpl<'until_build, } let mut s = format!( - "INSERT {}INTO {} (", + "INSERT {}INTO \"{}\" (", match d.on_conflict { OnConflict::ABORT => "OR ABORT ", OnConflict::ROLLBACK => "OR ROLLBACK ", @@ -122,7 +122,7 @@ impl<'until_build, 'post_build> Insert<'post_build> for InsertImpl<'until_build, d.into_clause, ); for (idx, x) in d.columns.iter().enumerate() { - write!(s, "{x}").unwrap(); + write!(s, "\"{x}\"").unwrap(); if idx != d.columns.len() - 1 { write!(s, ", ").unwrap(); } @@ -133,7 +133,7 @@ impl<'until_build, 'post_build> Insert<'post_build> for InsertImpl<'until_build, write!(s, "(").unwrap(); for (idx_2, y) in x.iter().enumerate() { match y { - Value::Ident(st) => write!(s, "{}", *st).unwrap(), + Value::Ident(st) => write!(s, "\"{}\"", *st).unwrap(), Value::Choice(c) => write!(s, "{}", sqlite::fmt(c)).unwrap(), Value::Null(NullType::Choice) => write!(s, "NULL").unwrap(), _ => { diff --git a/src/join_table.rs b/src/join_table.rs index 16db45a..95e5d49 100644 --- a/src/join_table.rs +++ b/src/join_table.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::fmt::{Display, Formatter, Write}; use crate::conditional::{BuildCondition, Condition}; @@ -63,13 +64,13 @@ pub trait JoinTable<'post_query> { - `s`: Mutable reference to String to write to. - `lookup`: List of values for bind parameter. */ - fn build(self, s: &mut String, lookup: &mut Vec>); + fn build(&self, s: &mut String, lookup: &mut Vec>); } /** Data of a JOIN expression. */ -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Clone)] pub struct JoinTableData<'until_build, 'post_query> { /// Type of the join operation pub join_type: JoinType, @@ -78,7 +79,7 @@ pub struct JoinTableData<'until_build, 'post_query> { /// Alias for the join table pub join_alias: &'until_build str, /// Condition to apply the join on - pub join_condition: &'until_build Condition<'post_query>, + pub join_condition: Cow<'until_build, Condition<'post_query>>, } /** @@ -86,7 +87,7 @@ Representation of the JOIN expression Should only be constructed via [DBImpl::join_table]. */ -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Clone)] pub enum JoinTableImpl<'until_build, 'post_query> { /** SQLite representation of a JOIN expression. @@ -105,15 +106,13 @@ pub enum JoinTableImpl<'until_build, 'post_query> { Postgres(JoinTableData<'until_build, 'post_query>), } -impl<'until_build, 'post_query> JoinTable<'post_query> - for JoinTableImpl<'until_build, 'post_query> -{ - fn build(self, s: &mut String, lookup: &mut Vec>) { +impl<'post_query> JoinTable<'post_query> for JoinTableImpl<'_, 'post_query> { + fn build(&self, s: &mut String, lookup: &mut Vec>) { match self { #[cfg(feature = "sqlite")] JoinTableImpl::SQLite(d) => write!( s, - "{} {} AS {} ON {}", + "{} \"{}\" AS {} ON {}", d.join_type, d.table_name, d.join_alias, diff --git a/src/lib.rs b/src/lib.rs index c7e65d5..d9a9f32 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ //! The module should be used to create sql queries for different SQL dialects. -#![cfg_attr(all(doc, CHANNEL_NIGHTLY), feature(doc_auto_cfg))] +#![cfg_attr(docsrs, feature(doc_cfg))] #![warn(missing_docs)] #[cfg(not(any(feature = "sqlite", feature = "postgres", feature = "mysql")))] @@ -46,6 +46,8 @@ pub mod value; mod db_specific; +use std::borrow::Cow; + use rorm_declaration::imr::{Annotation, DbType}; use crate::aggregation::SelectAggregator; @@ -383,7 +385,10 @@ impl DBImpl { pub fn delete<'until_build, 'post_query>( &self, table_name: &'until_build str, - ) -> impl Delete<'until_build, 'post_query> { + ) -> impl Delete<'until_build, 'post_query> + where + 'post_query: 'until_build, + { let d = DeleteData { model: table_name, lookup: vec![], @@ -407,8 +412,12 @@ impl DBImpl { */ pub fn update<'until_build, 'post_query>( &self, + table_name: &'until_build str, - ) -> impl Update<'until_build, 'post_query> { + ) -> impl Update<'until_build, 'post_query> + where + 'post_query: 'until_build, + { let d = UpdateData { model: table_name, on_conflict: OnConflict::ABORT, @@ -440,7 +449,7 @@ impl DBImpl { join_type: JoinType, table_name: &'until_build str, join_alias: &'until_build str, - join_condition: &'until_build Condition<'post_query>, + join_condition: Cow<'until_build, Condition<'post_query>>, ) -> JoinTableImpl<'until_build, 'post_query> { let d = JoinTableData { join_type, diff --git a/src/select.rs b/src/select.rs index bd9eafe..282c442 100644 --- a/src/select.rs +++ b/src/select.rs @@ -135,7 +135,7 @@ impl<'until_build, 'post_build> Select<'until_build, 'post_build> } } - write!(s, " FROM {}", d.from_clause).unwrap(); + write!(s, " FROM \"{}\"", d.from_clause).unwrap(); for x in d.join_tables { write!(s, " ").unwrap(); diff --git a/src/select_column.rs b/src/select_column.rs index b773adc..68ea659 100644 --- a/src/select_column.rs +++ b/src/select_column.rs @@ -47,7 +47,7 @@ pub enum SelectColumnImpl<'until_build> { Postgres(SelectColumnData<'until_build>), } -impl<'until_build> SelectColumn for SelectColumnImpl<'until_build> { +impl SelectColumn for SelectColumnImpl<'_> { fn build(&self, s: &mut String) { match self { #[cfg(feature = "sqlite")] @@ -64,10 +64,10 @@ impl<'until_build> SelectColumn for SelectColumnImpl<'until_build> { } if let Some(table_name) = d.table_name { - write!(s, "{table_name}.").unwrap(); + write!(s, "\"{table_name}\".").unwrap(); } - write!(s, "{}", d.column_name).unwrap(); + write!(s, "\"{}\"", d.column_name).unwrap(); if d.aggregation.is_some() { write!(s, ")").unwrap(); diff --git a/src/update.rs b/src/update.rs index 550ef98..b9e6890 100644 --- a/src/update.rs +++ b/src/update.rs @@ -150,11 +150,11 @@ impl<'until_build, 'post_build> Update<'until_build, 'post_build> let update_index = d.updates.len() - 1; for (idx, (name, value)) in d.updates.into_iter().enumerate() { if let Value::Choice(c) = value { - write!(s, "{name} = {}", sqlite::fmt(c)).unwrap(); + write!(s, "\"{name}\" = {}", sqlite::fmt(c)).unwrap(); } else if let Value::Null(NullType::Choice) = value { - write!(s, "{name} = NULL").unwrap(); + write!(s, "\"{name}\" = NULL").unwrap(); } else { - write!(s, "{name} = ?").unwrap(); + write!(s, "\"{name}\" = ?").unwrap(); d.lookup.push(value); } if idx != update_index {