From 223994ac3f6093d98335f7d0cb9da648f986acd1 Mon Sep 17 00:00:00 2001 From: GeekMasher Date: Sat, 2 Aug 2025 21:58:28 +0100 Subject: [PATCH 1/3] feat(sql): Update queries --- geekorm-sql/src/builder/queries/count.rs | 1 + geekorm-sql/src/builder/queries/delete.rs | 12 +++++++----- geekorm-sql/src/builder/queries/update.rs | 15 +++++++++------ 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/geekorm-sql/src/builder/queries/count.rs b/geekorm-sql/src/builder/queries/count.rs index 0563a03..c9ac094 100644 --- a/geekorm-sql/src/builder/queries/count.rs +++ b/geekorm-sql/src/builder/queries/count.rs @@ -12,6 +12,7 @@ impl QueryType { if let Some(ref alias) = table.alias { table.alias(alias.clone()); } + table.to_sql_stream(&mut full_query, query).unwrap(); // WHERE diff --git a/geekorm-sql/src/builder/queries/delete.rs b/geekorm-sql/src/builder/queries/delete.rs index 9a6e348..eb4ca61 100644 --- a/geekorm-sql/src/builder/queries/delete.rs +++ b/geekorm-sql/src/builder/queries/delete.rs @@ -16,10 +16,12 @@ impl QueryType { table_expr.to_sql_stream(&mut full_query, query).unwrap(); // WHERE - query - .where_clause - .to_sql_stream(&mut full_query, query) - .unwrap(); + if !query.where_clause.is_empty() { + query + .where_clause + .to_sql_stream(&mut full_query, query) + .unwrap(); + } } full_query.push(';'); @@ -56,7 +58,7 @@ mod tests { let table = table(); let query = QueryBuilder::delete() .table(&table) - .where_primary_key(1) + .where_eq("id", 1) .build() .unwrap(); diff --git a/geekorm-sql/src/builder/queries/update.rs b/geekorm-sql/src/builder/queries/update.rs index ca46dc1..229cb55 100644 --- a/geekorm-sql/src/builder/queries/update.rs +++ b/geekorm-sql/src/builder/queries/update.rs @@ -8,11 +8,14 @@ impl QueryType { let mut full_query = "UPDATE ".to_string(); if let Some(table) = query.find_table_default() { - let mut table = TableExpr::new(&table.name); - if let Some(ref alias) = table.alias { - table.alias(alias.clone()); + let mut table_expr = TableExpr::new(table.name); + + table.columns.to_sql_stream(&mut full_query, query).unwrap(); + + if let Some(ref alias) = table_expr.alias { + table_expr.alias(alias.clone()); } - table.to_sql_stream(&mut full_query, query).unwrap(); + table_expr.to_sql_stream(&mut full_query, query).unwrap(); // WHERE if !query.where_clause.is_empty() { @@ -61,10 +64,10 @@ mod tests { #[test] fn test_update_query() { let table = table(); - let query = QueryBuilder::select() + let query = QueryBuilder::update() .backend(QueryBackend::Sqlite) .table(&table) - .where_primary_key("1") + .where_eq("id", 1) .build() .unwrap(); From bf7773cd91f4ec066d06ae34b05db3d17b2c4e39 Mon Sep 17 00:00:00 2001 From: GeekMasher Date: Sat, 2 Aug 2025 22:17:58 +0100 Subject: [PATCH 2/3] fix(sql): Update builder to fix issue --- geekorm-sql/src/builder/columns.rs | 41 ++++++++++++++--------- geekorm-sql/src/builder/mod.rs | 20 +++++------ geekorm-sql/src/builder/queries/select.rs | 8 ++--- 3 files changed, 40 insertions(+), 29 deletions(-) diff --git a/geekorm-sql/src/builder/columns.rs b/geekorm-sql/src/builder/columns.rs index 2d31064..f90683c 100644 --- a/geekorm-sql/src/builder/columns.rs +++ b/geekorm-sql/src/builder/columns.rs @@ -117,6 +117,7 @@ impl ToSql for Column { let name = self.name(); stream.push_str(&name); + if let Some(alias) = &self.alias { if !alias.is_empty() { stream.push_str(" AS "); @@ -124,9 +125,7 @@ impl ToSql for Column { } } - if query.joins.is_empty() { - stream.push_str(&name); - } else { + if !query.joins.is_empty() { let table = query.find_table_default().unwrap(); let fullname = table.get_fullname(name.as_str()); stream.push_str(&fullname); @@ -145,16 +144,19 @@ impl ToSql for Columns { sql.push(col.to_sql(query)?); } - for foreign_key in self.get_foreign_keys() { - let (ctable, ccolumn) = foreign_key.get_foreign_key().unwrap(); - - sql.push("FOREIGN KEY (".to_string()); - sql.push(foreign_key.name.clone()); - sql.push(") REFERENCES ".to_string()); - sql.push(ctable.to_string()); - sql.push("(".to_string()); - sql.push(ccolumn.to_string()); - sql.push(")".to_string()); + if query.query_type == QueryType::Create { + // Only add foreign keys in CREATE queries + for foreign_key in self.get_foreign_keys() { + let (ctable, ccolumn) = foreign_key.get_foreign_key().unwrap(); + + sql.push("FOREIGN KEY (".to_string()); + sql.push(foreign_key.name.clone()); + sql.push(") REFERENCES ".to_string()); + sql.push(ctable.to_string()); + sql.push("(".to_string()); + sql.push(ccolumn.to_string()); + sql.push(")".to_string()); + } } stream.push_str(&sql.join(", ")); @@ -219,7 +221,7 @@ mod tests { )), Column::from(("name".to_string(), ColumnType::Text)), Column::from(("email".to_string(), ColumnType::Text)), - Column::from(("image_id".to_string(), ColumnType::ForeignKey)), + Column::new_foreign_key("image_id", "Images.id"), ]) .into(), } @@ -238,6 +240,15 @@ mod tests { assert_eq!(column_sql.as_str(), "id"); } + #[test] + fn test_column_foreign_key() { + let column = Column::new_foreign_key("image_id", "Images.id"); + let (foreign_key_table, foreign_key_col) = column.get_foreign_key().unwrap(); + + assert_eq!(foreign_key_table, "Images"); + assert_eq!(foreign_key_col, "id"); + } + #[test] fn test_columns_to_sql() { let table = table(); @@ -248,7 +259,7 @@ mod tests { assert_eq!( columns.as_str(), - "id, name, email, image_id, FOREIGN KEY (image_id) REFERENCES images(id)" + "id, name, email, image_id, FOREIGN KEY (image_id) REFERENCES Images(id)" ); } } diff --git a/geekorm-sql/src/builder/mod.rs b/geekorm-sql/src/builder/mod.rs index 1cef8f7..cb965e6 100644 --- a/geekorm-sql/src/builder/mod.rs +++ b/geekorm-sql/src/builder/mod.rs @@ -19,7 +19,7 @@ use crate::{Error, Query, QueryBackend, ToSql, Value, Values}; use columns::Columns; /// Query Type enum -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, PartialEq, Eq)] pub enum QueryType { /// Create Query Create, @@ -226,7 +226,7 @@ impl<'a> QueryBuilder<'a> { .unwrap() } } - } else if let Some(table) = self.find_table("self") { + } else if let Some(table) = self.find_table_default() { table } else { self.set_error(Error::QueryBuilderError { @@ -263,43 +263,43 @@ impl<'a> QueryBuilder<'a> { /// Where clause for equals pub fn where_eq(&mut self, column: &str, value: impl Into) -> &mut Self { - QueryBuilder::add_where(self, column, QueryCondition::Eq, value.into()); + self.add_where(column, QueryCondition::Eq, value.into()); self } /// Where clause for not equals pub fn where_ne(&mut self, column: &str, value: impl Into) -> &mut Self { - QueryBuilder::add_where(self, column, QueryCondition::Ne, value.into()); + self.add_where(column, QueryCondition::Ne, value.into()); self } /// Where clause for like pub fn where_like(&mut self, column: &str, value: impl Into) -> &mut Self { - QueryBuilder::add_where(self, column, QueryCondition::Like, value.into()); + self.add_where(column, QueryCondition::Like, value.into()); self } /// Where clause for greater than pub fn where_gt(&mut self, column: &str, value: impl Into) -> &mut Self { - QueryBuilder::add_where(self, column, QueryCondition::Gt, value.into()); + self.add_where(column, QueryCondition::Gt, value.into()); self } /// Where clause for less than pub fn where_lt(&mut self, column: &str, value: impl Into) -> &mut Self { - QueryBuilder::add_where(self, column, QueryCondition::Lt, value.into()); + self.add_where(column, QueryCondition::Lt, value.into()); self } /// Where clause for greater than or equal to pub fn where_gte(&mut self, column: &str, value: impl Into) -> &mut Self { - QueryBuilder::add_where(self, column, QueryCondition::Gte, value.into()); + self.add_where(column, QueryCondition::Gte, value.into()); self } /// Where clause for less than or equal to pub fn where_lte(&mut self, column: &str, value: impl Into) -> &mut Self { - QueryBuilder::add_where(self, column, QueryCondition::Lte, value.into()); + self.add_where(column, QueryCondition::Lte, value.into()); self } @@ -373,7 +373,7 @@ impl<'a> QueryBuilder<'a> { } fn validate_table_column(&self, column: &str) -> Result { - if let Some(table) = self.find_table("self") { + if let Some(table) = self.find_table_default() { Ok(table.columns.contains(&column)) } else { return Err(Error::QueryBuilderError { diff --git a/geekorm-sql/src/builder/queries/select.rs b/geekorm-sql/src/builder/queries/select.rs index 9b15645..5211618 100644 --- a/geekorm-sql/src/builder/queries/select.rs +++ b/geekorm-sql/src/builder/queries/select.rs @@ -84,7 +84,7 @@ mod tests { )), Column::from(("name".to_string(), ColumnType::Text)), Column::from(("email".to_string(), ColumnType::Text)), - Column::from(("image_id".to_string(), ColumnType::ForeignKey)), + Column::new_foreign_key("image_id", "Images.id"), ]) .into(), } @@ -99,7 +99,7 @@ mod tests { .build() .unwrap(); - assert_eq!(query.query, "SELECT id, name, email FROM Test;"); + assert_eq!(query.query, "SELECT id, name, email, image_id FROM Test;"); } #[test] @@ -114,7 +114,7 @@ mod tests { assert_eq!( query.query, - "SELECT id, name, email FROM Test WHERE name = ?;" + "SELECT id, name, email, image_id FROM Test WHERE name = ?;" ); assert_eq!(query.values.len(), 1); @@ -136,7 +136,7 @@ mod tests { assert_eq!( query.query, - "SELECT id, name, email FROM Test ORDER BY name ASC, email DESC;" + "SELECT id, name, email, image_id FROM Test ORDER BY name ASC, email DESC;" ); } } From b6485242104b14b1c93baa5855a419f1dd3ed8a3 Mon Sep 17 00:00:00 2001 From: GeekMasher Date: Sat, 2 Aug 2025 22:39:48 +0100 Subject: [PATCH 3/3] fix(sql): Update builder queries and tests --- geekorm-sql/src/builder/columns.rs | 48 +++++++++++++---------- geekorm-sql/src/builder/queries/create.rs | 24 ++++++++++-- geekorm-sql/src/builder/table.rs | 9 +++++ 3 files changed, 57 insertions(+), 24 deletions(-) diff --git a/geekorm-sql/src/builder/columns.rs b/geekorm-sql/src/builder/columns.rs index f90683c..475fffc 100644 --- a/geekorm-sql/src/builder/columns.rs +++ b/geekorm-sql/src/builder/columns.rs @@ -102,6 +102,23 @@ impl ColumnOptions { auto_increment: true, } } + + /// Not null options + pub fn not_null() -> Self { + ColumnOptions { + not_null: true, + ..Default::default() + } + } + + /// Unique options + pub fn unique() -> Self { + ColumnOptions { + unique: true, + not_null: true, + ..Default::default() + } + } } impl ToSql for Column { @@ -110,8 +127,11 @@ impl ToSql for Column { QueryType::Create => { stream.push_str(&self.name); stream.push(' '); - self.column_type + + let opts = self + .column_type .to_sql_with_options(&self.column_options, query)?; + stream.push_str(&opts); } _ => { let name = self.name(); @@ -144,21 +164,6 @@ impl ToSql for Columns { sql.push(col.to_sql(query)?); } - if query.query_type == QueryType::Create { - // Only add foreign keys in CREATE queries - for foreign_key in self.get_foreign_keys() { - let (ctable, ccolumn) = foreign_key.get_foreign_key().unwrap(); - - sql.push("FOREIGN KEY (".to_string()); - sql.push(foreign_key.name.clone()); - sql.push(") REFERENCES ".to_string()); - sql.push(ctable.to_string()); - sql.push("(".to_string()); - sql.push(ccolumn.to_string()); - sql.push(")".to_string()); - } - } - stream.push_str(&sql.join(", ")); Ok(()) } @@ -220,7 +225,11 @@ mod tests { ColumnOptions::primary_key(), )), Column::from(("name".to_string(), ColumnType::Text)), - Column::from(("email".to_string(), ColumnType::Text)), + Column::from(( + "email".to_string(), + ColumnType::Text, + ColumnOptions::unique(), + )), Column::new_foreign_key("image_id", "Images.id"), ]) .into(), @@ -257,9 +266,6 @@ mod tests { let columns = Columns::to_sql(&table.columns, &query).unwrap(); - assert_eq!( - columns.as_str(), - "id, name, email, image_id, FOREIGN KEY (image_id) REFERENCES Images(id)" - ); + assert_eq!(columns.as_str(), "id, name, email, image_id"); } } diff --git a/geekorm-sql/src/builder/queries/create.rs b/geekorm-sql/src/builder/queries/create.rs index 991cf60..d2d8ba6 100644 --- a/geekorm-sql/src/builder/queries/create.rs +++ b/geekorm-sql/src/builder/queries/create.rs @@ -8,12 +8,26 @@ impl QueryType { let mut full_query = String::new(); if let Some(table) = query.find_table_default() { full_query.push_str("CREATE TABLE IF NOT EXISTS "); - full_query.push_str(&table.name); + full_query.push_str(table.name); full_query.push_str(" ("); table.columns.to_sql_stream(&mut full_query, query).unwrap(); full_query.push(')'); + + // Only add foreign keys in CREATE queries + for foreign_key in table.get_foreign_keys() { + let (ctable, ccolumn) = foreign_key.get_foreign_key().unwrap(); + + full_query.push_str("FOREIGN KEY ("); + full_query.push_str(&foreign_key.name); + + full_query.push_str(") REFERENCES "); + full_query.push_str(ctable); + full_query.push('('); + full_query.push_str(ccolumn); + full_query.push(')'); + } } full_query.push(';'); @@ -42,7 +56,11 @@ mod tests { ColumnOptions::primary_key(), )), Column::from(("name".to_string(), ColumnType::Text)), - Column::from(("email".to_string(), ColumnType::Text)), + Column::from(( + "email".to_string(), + ColumnType::Text, + ColumnOptions::unique(), + )), ]) .into(), } @@ -58,7 +76,7 @@ mod tests { assert_eq!( sql, - "CREATE TABLE IF NOT EXISTS Test (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, email TEXT UNIQUE);" + "CREATE TABLE IF NOT EXISTS Test (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, email TEXT NOT NULL UNIQUE);" ); } } diff --git a/geekorm-sql/src/builder/table.rs b/geekorm-sql/src/builder/table.rs index dfdba62..e0fd094 100644 --- a/geekorm-sql/src/builder/table.rs +++ b/geekorm-sql/src/builder/table.rs @@ -44,6 +44,15 @@ impl Table { .find(|col| col.foreign_key.as_deref() == Some(&name)) } + /// Get all foreign key columns in the table. + pub fn get_foreign_keys(&self) -> Vec<&Column> { + self.columns + .columns + .iter() + .filter(|col| col.foreign_key.is_some()) + .collect() + } + /// Get a column by its name or alias pub fn find_column(&self, name: &str) -> Option<&Column> { self.columns