Skip to content

Commit

Permalink
Added insert multiple rows support (#8)
Browse files Browse the repository at this point in the history
* Added insert multiple rows support

* Updated inserts to using Vec

* Renamed Insert values property

* Updated insert_many method documentation

* Fixed different value types issue

* Reset main.rs

* Created eloquent_sql_row macro

* Added insert columns validator

* Formatting

---------

Co-authored-by: Tjardo <[email protected]>
  • Loading branch information
Sanchiz1 and tjardoo authored Dec 31, 2024
1 parent 2d08536 commit 1de0c9c
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 20 deletions.
59 changes: 59 additions & 0 deletions eloquent_core/src/checks/cannot_insert_with_different_columns.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use crate::{error::EloquentError, PerformChecks, QueryBuilder};

pub struct CannotInsertWithDifferentColumns;

impl PerformChecks for CannotInsertWithDifferentColumns {
fn check(builder: &QueryBuilder) -> Result<(), EloquentError> {
if builder.inserts.is_empty() {
return Ok(());
}

let column_count = builder
.inserts
.first()
.map(|insert| insert.values.len())
.unwrap_or(0);

let inconsistent_row = builder
.inserts
.iter()
.find(|insert| insert.values.len() != column_count);

if inconsistent_row.is_some() {
return Err(EloquentError::InconsistentInsertColumns);
}

Ok(())
}
}

#[cfg(test)]
mod tests {
use crate::{eloquent_sql_row, error::EloquentError, QueryBuilder, ToSql};

#[test]
fn test_cannot_insert_with_different_columns() {
let result = QueryBuilder::new()
.table("users")
.insert_many(vec![
eloquent_sql_row! {
"name" => "Alice",
"email" => "[email protected]",
"is_active" => true,
},
eloquent_sql_row! {
"name" => "Bob",
"email" => "[email protected]",
"age" => 22,
"is_active" => false,
},
])
.to_sql();

match result {
Err(EloquentError::InconsistentInsertColumns) => (),
Err(_error) => panic!(),
Ok(_value) => panic!(),
}
}
}
1 change: 1 addition & 0 deletions eloquent_core/src/checks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ pub mod having_clause_without_aggregate_function;
pub mod missing_table;
pub mod multiple_crud_actions;
pub mod order_by_without_selected_or_aggregate_function;
pub mod cannot_insert_with_different_columns;
29 changes: 14 additions & 15 deletions eloquent_core/src/compilers/inserts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,27 @@ pub(crate) fn format<'a>(
sql.push_str(table);
sql.push_str(" (");

sql.push_str(
&inserts
.iter()
.map(|insert| insert.column.clone())
.collect::<Vec<String>>()
.join(", "),
);
let columns: Vec<_> = inserts.iter().map(|insert| insert.column.as_str()).collect();
sql.push_str(&columns.join(", "));

sql.push_str(") VALUES ");

sql.push_str(") VALUES (");
let row_count = inserts.first().map_or(0, |insert| insert.values.len());

sql.push_str(
&inserts
let mut value_placeholders = vec![];
for i in 0..row_count {
let row_values: Vec<_> = inserts
.iter()
.map(|insert| {
params.push(&insert.value);
params.push(&insert.values[i]);
"?".to_string()
})
.collect::<Vec<String>>()
.join(", "),
);
.collect();

value_placeholders.push(format!("({})", row_values.join(", ")));
}

sql.push(')');
sql.push_str(&value_placeholders.join(", "));

sql.to_string()
}
5 changes: 5 additions & 0 deletions eloquent_core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub enum EloquentError {
CannotApplyClauseOnUpdate(String),
CannotApplyClauseOnDelete(String),
CannotUseOffsetLimitWithPagination(String),
InconsistentInsertColumns,
}

impl std::error::Error for EloquentError {}
Expand Down Expand Up @@ -57,6 +58,10 @@ impl std::fmt::Display for EloquentError {
EloquentError::CannotUseOffsetLimitWithPagination(clause) => {
write!(f, "Cannot use '{}' with PAGINATION", clause)
}
EloquentError::InconsistentInsertColumns => write!(
f,
"INSERT statement has inconsistent column counts across rows"
),
}
}
}
11 changes: 10 additions & 1 deletion eloquent_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ struct Select {

struct Insert {
column: String,
value: Box<dyn ToSql>,
values: Vec<Box<dyn ToSql>>,
}

struct Update {
Expand Down Expand Up @@ -417,3 +417,12 @@ impl Condition {
}
}
}

#[macro_export]
macro_rules! eloquent_sql_row {
($($key:expr => $value:expr),* $(,)?) => {
vec![
$(($key, Box::new($value) as Box<dyn $crate::ToSql>)),*
]
};
}
55 changes: 51 additions & 4 deletions eloquent_core/src/queries/inserts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,58 @@ impl QueryBuilder {
/// );
/// ```
pub fn insert(mut self, column: &str, value: impl ToSql + 'static) -> Self {
self.inserts.push(Insert {
column: column.to_string(),
value: Box::new(value),
});
self.add_insert(column, Box::new(value));

self
}

/// Insert single or multiple rows into the table.
///
/// ```
/// use eloquent_core::{QueryBuilder, ToSql, eloquent_sql_row};
///
/// let rows = vec![
/// eloquent_sql_row! {
/// "name" => "Alice",
/// "email" => "[email protected]",
/// "age" => 21,
/// "is_active" => true,
/// },
/// eloquent_sql_row! {
/// "name" => "Bob",
/// "email" => "[email protected]",
/// "age" => 22,
/// "is_active" => false,
/// },
/// ];
/// let query = QueryBuilder::new()
/// .table("users")
/// .insert_many(rows);
///
/// assert_eq!(
/// query.sql().unwrap(),
/// "INSERT INTO users (name, email, age, is_active) VALUES ('Alice', '[email protected]', 21, true), ('Bob', '[email protected]', 22, false)"
/// );
/// ```
pub fn insert_many(mut self, rows: Vec<Vec<(&str, Box<dyn ToSql>)>>) -> Self {
rows.into_iter().for_each(|row| self.add_row(row));

self
}

fn add_insert(&mut self, column: &str, value: Box<dyn ToSql>) {
if let Some(insert) = self.inserts.iter_mut().find(|i| i.column == column) {
insert.values.push(value);
} else {
self.inserts.push(Insert {
column: column.to_string(),
values: vec![value],
});
}
}

fn add_row(&mut self, row: Vec<(&str, Box<dyn ToSql>)>) {
row.into_iter()
.for_each(|(column, value)| self.add_insert(column, value));
}
}
1 change: 1 addition & 0 deletions eloquent_core/src/validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ impl QueryBuilder {
cannot_apply_clause_on_update::CannotApplyClauseOnUpdate::check(self)?;
cannot_apply_clause_on_delete::CannotApplyClauseOnDelete::check(self)?;
cannot_use_offset_limit_with_pagination::CannotUseOffsetLimitWithPagination::check(self)?;
cannot_insert_with_different_columns::CannotInsertWithDifferentColumns::check(self)?;

Ok(())
}
Expand Down

0 comments on commit 1de0c9c

Please sign in to comment.